Elasticsearch overview

elasticsearch와 kibana docker 배포

docker container로 elasticsearch와 kibana를 쉽게 배포할 수 있다.

docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.17.14

port-forward로 host-ip의 9200 port가 container port에 맵핑된 것을 볼 수 있다.

이에 맞춰서 kibana의 ELASTICSEARCH_HOSTS에 host-ip와 port를 넘겨주어야 한다.

docker run  -p 5601:5601 -e "ELASTICSEARCH_HOSTS=http://${HOST_IP}:${HOST_PORT}" docker.elastic.co/kibana/kibana:7.17.14

쉽게 배포가 완료된다.

Node와 Cluster

node는 elasticsearch가 동작하는 인스턴스로, 데이터를 저장하는 공간이다. logical한 instance개념인데 즉, 실제 physical machine을 일컫는 개념과는 다르다. 따라서, 1개의 machine에 elasticsearch node를 여러 개 띄우는 것도 가능하다. 물론, 서로 다른 physical machine에서 여러 개의 node를 구동시킬 수도 있다. 가령, physical machine, virtual machine, docker container 등등 다양한 곳에서 구동 할 수 있다.

그렇다면 어떻게 elasticsearch는 node로 분산된 데이터들을 모아서 검색할 수 있는 것일까? node들은 하나의 cluster에 묶여져 있으며 cluster안에서 node의 데이터들을 일괄적으로 관리할 수 있는 것이다. 그러나 cluster들은 서로 독립적이기 때문에 서로 다른 cluster에 대해서는 일괄적으로 관리하거나 데이터를 공유하기 어렵다. 따라서, cluster를 나누는 목적은 통계용, 성능 분석용으로 나누는 것이 관례이다. 따라서, 일반적으로 하나의 cluster로만 관리하는 경우가 대부분이다.

node는 무조건 cluster이 한 부분으로 존재한다. 심지어는 cluster를 따로 만들지 않고 node를 만들었는데도 불구하고 node는 cluster에 속하게 된다.

Document and index

그렇다면 node에 저장되는 data는 무엇일까? elasticsearch에서는 이를 document라고 부른다. document는 JSON으로 된 데이터로 전달된 original data와 elasticsearch에서 사용하는 metadata가 붙여진 형태이다.

오리지널 데이터가 다음과 같다면,

{
    "name": "Bo",
    "country": "korea"
}

elasticsearch에서는 다음과 같이 저장된다.

{
    "_index": "people",
    "_type": "_doc",
    "_id": "123",
    "_version": 1,
    "_seq_no": 0,
    "_primary_term": 1,
    "_source": {
        "name": "Bo",
        "country": "korea"
    }
}

original data가 _source에 저장되고 metadata들이 붙는 것을 볼 수 있다.

그렇다면 Document들은 어떻게 조직화되는가? 즉, 어떻게 그룹화되는가? 바로 index를 사용해서 document들을 조직화한다.

elasticsearch의 모든 document들은 index로 그룹화된다.

|------people index-----|   |------departments index-----|
|                       |   |                            |
|   |gyu| |andry| |jo|  |   | |design| |development|     |
|                       |   |     |marketing|            |
|-----------------------|   |----------------------------|

index에 있는 document들은 logical하게 관련있는 document들이 묶인 것으로 scalability 등을 위한 configuration 등을 달리 적용할 수 있다.

이제 cluster, node,index, document를 kibana를 통해 확인해보도록 하자. 먼저 앞서 배포한 kibana에 접속하도록 하자.

http://${HOST_IP}:5601/app/dev_tools#/console

다음의 링크에 접속하면 kibana의 Dev Tool화면이 나온다. 여기에 elasticsearch REST API를 사용해 정보를 얻어올 수 있다. 기존에 있는 query문을 삭제하고 아래의 query문을 치도록 하자.

GET /_cluster/health

GET /_cat/nodes?v

GET /_cat/indices?v

GET /_cat/indices?v&expand_wildcards=all

위의 query문은 사실 elasticsearch의 REST API이다. kibana에서 이를 wrapping하여 더 쉽게 호출하도록 할 뿐이지, 내부는 elasticsearch REST API를 호출할 뿐이다. elasticsearch REST API의 자세한 내용은 다음을 참고하자.

https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-nodes.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-info.html

먼저 cluster의 상태를 확인할 수 있는 query문을 먼저 실행해보도록 하자.

GET /_cluster/health

필자는 다음의 결과가 나왔다.

{
  "cluster_name" : "docker-cluster",
  "status" : "yellow",
  "timed_out" : false,
  "number_of_nodes" : 1,
  "number_of_data_nodes" : 1,
  "active_primary_shards" : 8,
  "active_shards" : 8,
  "relocating_shards" : 0,
  "initializing_shards" : 0,
  "unassigned_shards" : 1,
  "delayed_unassigned_shards" : 0,
  "number_of_pending_tasks" : 0,
  "number_of_in_flight_fetch" : 0,
  "task_max_waiting_in_queue_millis" : 0,
  "active_shards_percent_as_number" : 88.88888888888889
}

cluster가 한 개 있는 것을 알 수 있고, statusyellow인 것을 확인 할 수 있다.

더 나아가 node가 어떻게 구성되어있는 지 확인해보도록 하자.

GET /_cat/nodes?v

다음의 응답이 나왔다.

ip         heap.percent ram.percent cpu load_1m load_5m load_15m node.role   master name
172.17.0.2            9          98   6    1.57    1.39     1.32 cdfhilmrstw *      4dd1202da696

한 개의 node로 구성되어있는 것을 알 수 있다. node들은 각자마다 자신의 role이 있고 responsibility가 있다. 그러나 여기서는 간단하게 하나의 node로 제어하고 있는 것을 알 수 있다.

다음은 index들이 어떤 것들이 있는 지 확인해보도록 하자.

GET /_cat/indices?v

다음의 결과가 나왔다.

health status index                            uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   logstash-2023.10.25              VPAv78AgQ2SSfyEa7SXlFw   1   1       2955            0      1.3mb          1.3mb
green  open   .apm-custom-link                 bIHXi12iRBeI-xozWpEITg   1   0          0            0       227b           227b
green  open   .apm-agent-configuration         O9mFrC6iSSWmqa4FTEI9IA   1   0          0            0       227b           227b
green  open   .kibana_task_manager_7.17.14_001 3s8png5wR5e3x7pWBbjs-A   1   0         17         1533    211.7kb        211.7kb
green  open   .kibana_7.17.14_001              i9c4qDs0SZaqA-LOucIzdw   1   0        180           16      2.4mb          2.4mb

logstash index빼고는 모두 green인 것을 알 수 있다. status에 대해서는 추후에 더 자세히 알아보도록 하자.

더 나아가서 system index들도 query해보도록 하자. system index들이 나와야 하기 때문에 expand_wildcards=all로 표현하였다.

GET /_cat/indices?v&expand_wildcards=all

다음의 결과가 나왔다.

health status index                                                         uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   logstash-2023.10.25                                           VPAv78AgQ2SSfyEa7SXlFw   1   1       2955            0      1.4mb          1.4mb
green  open   .ds-.logs-deprecation.elasticsearch-default-2023.10.25-000001 Ln0Z5WY3Q_OCyVfZdg5_0w   1   0          3            0     29.8kb         29.8kb
green  open   .apm-custom-link                                              bIHXi12iRBeI-xozWpEITg   1   0          0            0       227b           227b
green  open   .apm-agent-configuration                                      O9mFrC6iSSWmqa4FTEI9IA   1   0          0            0       227b           227b
green  open   .kibana_task_manager_7.17.14_001                              3s8png5wR5e3x7pWBbjs-A   1   0         17         1584    244.7kb        244.7kb
green  open   .ds-ilm-history-5-2023.10.25-000001                           DpDCxadKRSy8KmDNPvJexQ   1   0          9            0     19.7kb         19.7kb
green  open   .kibana_7.17.14_001                                           i9c4qDs0SZaqA-LOucIzdw   1   0        180           16      2.4mb          2.4mb
green  open   .kibana-event-log-7.17.14-000001                              QBROhomlT4me3y-8RU5_5w   1   0          1            0        6kb            6kb

아까 보이지 않았던 부분들이 나왔다.

이렇게 kibana console뿐만 아니라 curl을 이용해서 elasticsearch에 직접 API를 요청해도된다. elasticsearch가 9200 port로 포트 포워딩되어 있으므로 다음과 같이 요청을 보내면 된다.

curl localhost:9200

{
  "name" : "4dd1202da696",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "MbegD0NJQGqmgMyFlSTdCA",
  "version" : {
    "number" : "7.17.14",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "774e3bfa4d52e2834e4d9d8d669d77e4e5c1017f",
    "build_date" : "2023-10-05T22:17:33.780167078Z",
    "build_snapshot" : false,
    "lucene_version" : "8.11.1",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

그러나 curl로 elasticsearch를 제어하고 데이터를 요청하는 방법은 매우 장황하고 귀찮으므로 kibana를 다들 사용하는 것이다.

Sharding and scalability

cluster가 하나 이상의 node를 가질 수 있다고 했는데, 이는 elasticsearch가 데이터 저장공간에 대해서 스케일링하는 일반적인 방법 중 하나이다.

가령, data들을 전부 합해서 1테라바이트의 크기를 가졌지만 elasticsearch가 single node로 구성되어있고, 해당 node에 500기가바이트 밖에 없다면, 이는 수용할 수 없을 것이다. 그러나 elasticsearch의 node를 두 개로 늘려 각각 500기가바이트를 처리할 수 있다면 이는 수용할 수 있게 된다.


|-------------------------cluster1----------------------|
|    |---------Node1--------| |---------Node2--------|  |
|    |        500GB         | |        500GB         |  |
|    |----------------------| |----------------------|  |
|-------------------------------------------------------|
                            ▲
                            |
                            |
    |---------------------index------------------------|
    |                     600GB                        |
    |--------------------------------------------------|                            

그럼 이것이 어떻게 실제로 동작하는 것일까? 만약, 600GB의 index가 있고 500GB로 구성된 두 node가 있다면 600GB의 index를 500GB node하나에 통째로 넣을 수는 없다. 이를 위해서 elasticsearch에서는 sharding을 지원한다.


|---------------------------------cluster1-----------------------------------|
|    |--------------Node1-------------| |---------------Node2------------|   |
|    | shard1(100GB), shard2(100GB)   | | shard3(100GB), shard4(100GB),  |   |
|    |                                | | shard5(100GB), shard6(100GB)   |   |
|    |--------------------------------| |--------------------------------|   |
|----------------------------------------------------------------------------|

sharding은 database에 나오는 sharding과 완전히 동일하다. data를 담은 index를 작은 단위인 shard로 자르고 node에 나누어 할당하는 것이다. 위의 예제에서는 600GB의 index를 6개의 shard로 나누어 6개의 shard를 두 node에 각각 나누어 할당하는 것이 가능하다는 것이다.

shard는 사실 apache lucene index이다. 따라서 6개의 shard를 가진다는 것은 6개의 lucene index를 가진다는 것이다. 이는 각각의 shard들이 서로 독립적이라는 것도 의미한다.

shard는 정해진 사이즈가 딱히 있는 것은 아니고, document들이 추가되면 추가될 수록 점점 커진다. 또한, shard는 약 2 bilion document까지만 가질 수 있다. 즉, limit이 존재하는 것이다.

이렇게 shading을 사용함으로서, 더욱더 많은 document들을 저장할 수 있었고 large index들을 node에 할당할 수 있었으며, 성능 향상에 큰 도움이 된다. 왜냐면 index들이 shard단위로 나뉘었기 때문에 각각의 shard에 대해서 search API가 병렬적으로 호출되어 결과가 도출될 수 있기 때문이다.

elasticsearch7 이전만 해도 기본적으로 shard는 한 개의 index당 5개였지만, 현재는 1개이다. default로 1개의 index당 1개의 shard이다. 왜냐하면 너무 많은 shard는 오히려 성능을 떨어뜨리는 문제들이 있었기 때문이다. 따라서, index들을 더 잘게 나누는 것이 더 좋은 방법이 되었고, 하나의 index 당 하나의 shard가 되어버린 것이다.

shard의 수를 늘릴 수 없는 것은 아니다. split, shrink API를 사용하면 shard를 늘리고, 줄일 수 있다. 그렇다면 얼마나 shard를 유지해야 최적화인 것을까? 사실 여기에 관련된 공식은 없고, 수많은 환경에 따라 다르게 적용된다. 일반적으로 index가 너무 방대해지면 shard를 늘리는 것이 좋을 때가 많다. 이는 환경적인 부분들이 성능에 큰 영향을 미치므로, 때에 따라 다르다.

Replication, Snapshots

그런데 만약 데이터를 담은 disk가 고장난다면 어떻게 될까? 복제본이 없기 때문에 data를 손실할 것이다. 다행스러운 것은 elasticsearch에서 기본적으로 어떠한 configuration설정없이 replication을 지원한다는 것이다.

replication config는 index level에서 설정되는데, 이는 replication이 shard의 복제본인 replica shards를 만들기 때문이다. 복제의 원본은 primary shard라고 하며, replica shardsprimary shards를 합쳐서 replication group이라고 한다.

index가 shard A, shard B로 구성되어있다면 다음과 같이 replication groups가 있다.


|----------------------replication group A-----------------------|
|                                                                |
| |------------------| |------------------| |------------------| |
| | primary shard A  | | replica shard A1 | | replica shard A2 | |
| |------------------| |------------------| |------------------| |
|                                                                |
|----------------------------------------------------------------|

|----------------------replication group B-----------------------|
|                                                                |
| |------------------| |------------------| |------------------| |
| | primary shard A  | | replica shard A1 | | replica shard A2 | |
| |------------------| |------------------| |------------------| |
|                                                                |
|----------------------------------------------------------------|

두 개의 replication group이 존재하는 것을 볼 수 있다.

그렇다면, replica shard를 통해서 어떻게 데이터 복원을 할 수 있는 것일까?? 여기에는 한 가지 조건이 필요한데, node가 두 개 이상이어야 한다는 것이다. replica shard는 primary shard와 다른 node에 배정이 되어 저장된다. 이 과정에 있어서 single node인 경우는 replica shard가 primary shard와 다른 node에 있을 수 없어 어디에도 할당되지 않는다. 따라서 single node로 구성된 elasticsearch cluster는 단지 development용이라는 것을 알 수 있다.

|------------------|
| replica shard A  |
|------------------|

|------------------|
| replica shard B  |
|------------------|

|-----------------Node 1--------------------|
|                                           |
| |------------------| |------------------| |
| | primary shard A  | | primary shard B  | |
| |------------------| |------------------| |
|                                           |
|-------------------------------------------|

위의 그림과 같이 replica shard Areplica shard B는 어디에도 할당받지 못하고 primary shard Aprimary shard BNode 1에 할당된 것을 볼 수 있다. 이 경우에는 Node 1이 다운되어도 replication 기능을 제공받을 수 없다는 문제가 있다.

만약 Node가 하나 더 생겨서 Node1, Node2가 된다면 primary shard의 반대쪽에 replica shard가 다음과 같이 들어갈 수 있다.

|-----------------Node 1--------------------|
|                                           |
| |------------------| |------------------| |
| | primary shard A  | | primary shard B  | |
| |------------------| |------------------| |
|                                           |
|-------------------------------------------|

|-----------------Node 2--------------------|
|                                           |
| |------------------| |------------------| |
| | replica shard A  | | replica shard B  | |
| |------------------| |------------------| |
|                                           |
|-------------------------------------------|

다음과 같이 replica shard Areplica shard B가 primary shard가 있는 Node1이 아닌 Node 2에 모두 할당된 것을 볼 수 있다. 이와 같이 할당되므로 Node1이나 Node2가 다운되어도 replica가 있어서 문제없이 복구된다. 단, 두 Node가 모두 다운되는 경우에는 복구되지 않는데, 이를 위해서 두 Node를 다른 하드웨어로 다른 장소에 저장하도록 하는 방법이 있다.

각 shard에 대한 replia shard 수도 조절할 수 있는데, 이는 data가 얼마나 중요한가에 따라 다르게 설정하면 된다.

또 다른 data replication 기능으로 elasticsearch는 snapshot을 지원한다. snapshot은 백업용으로 해당 시간의 데이터를 복원할 수 있도록 한다. 또한, snapshot은 index level에서 동작거나 전체 cluster level에서도 동작할 수 있다.

그렇다면 snapshotreplication은 무엇이 다를까?? replicationlive data에만 유효한 것이다. 즉, 현재 index에서 그룹핑된 document에 대해서만 의미가 있는 것일 뿐이다. 만약 내가 1년 전에 사용했던 데이터들이 필요하다면 어떻게 할까?? 이때 필요한 것이 바로 snapshot이다.

따라서, 이렇게 구분할 수 있다.
1. snapshot: 백업용으로 특정 시점에서의 data를 되살릴 수 있다.
2. replication: 데이터의 보존성과 높은 가용성, 성능을 위해 존재한다.

그런데, replication이 왜 높은 가용성, 성능을 위해 존재하는 지 의문 일 수 있다. 이는 replica shard가 완전히 index level에서 동작하는 shard와 동일하게 취급되기 때문이다. 즉, elasticsearch에서는 replica shardprimary shard나 동일하게 취급한다. client의 query가 주어지면 elasticsearch는 index에서 shard를 통해서 query의 검색 내용을 찾는데, 이 때 elasticsearch는 client의 request를 타겟의 shard로 라우팅하는 알고리즘을 통해서 best shard를 찾는다.

즉, replica shard가 있다면 best shard로 평가되는 shard가 많아질 수 있으며, 요청이 여러 곳에서 많이 발생한다해도 하나의 shard에서 request를 처리하는 것이 아니라 여러 shard에서 request를 처리할 수 있는 것이다. 이는 CPU 병렬성을 통해서 성능을 끌어올릴 수 있다.

가령, 1개의 primary shard와 2개의 replica shard가 있고 3개의 같은 내용의 query가 들어오면 다음과 같이 처리된다.

query1 ---> |primary shard |
query2 ---> |replica shard1|
query3 ---> |replica shard2|

3개의 동일한 요청에 대해서 3개의 shard가 병렬적으로 처리가 가능한 것이다. 따라서 성능이 향상될 수 밖에 없다. 이것이 가능한 이유는 CPU core를 통해 thread를 구동해 병렬적으로 수행할 수 있기 때문인 것이다.

정리하자면 replication들이 추가됨에 따라 client이 data를 요청할 수 있는 가용성이 높아졌고, replica shard 덕분에 CPU core당 thread로 따로 처리가 가능하므로 산출량도 높아져 성능이 높아졌다고 할 수 있다.

shard와 replica shard를 kibana dev tools를 통해서 확인해보도록 하자.

GET /_cat/indices?v

아래의 결과를 보면 logstash부분의 pri 1개, rep 1개로 되어있다. 이는 priprimary shard의 수이고 repreplication의 수이다. logstash에는 replication이 있지만 다른 .kibana같은 system index들은 replication이 없는 것을 알 수 있다. 이는 kibana를 배포할 때 configuration으로 설정할 수 있는 부분인데, node의 수 - 1로 결정된다. 따라서, node의 수가 3개면 system index들은 replica shard를 2개씩 갖는다.

health status index                            uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   logstash-2023.10.25              VPAv78AgQ2SSfyEa7SXlFw   1   1       2955            0      4.1mb          4.1mb
green  open   .apm-custom-link                 bIHXi12iRBeI-xozWpEITg   1   0          0            0       227b           227b
green  open   .apm-agent-configuration         O9mFrC6iSSWmqa4FTEI9IA   1   0          0            0       227b           227b
green  open   .kibana_task_manager_7.17.14_001 3s8png5wR5e3x7pWBbjs-A   1   0         17         8253        1mb            1mb
green  open   .kibana_7.17.14_001              i9c4qDs0SZaqA-LOucIzdw   1   0        184            9      2.4mb          2.4mb

다음으로 shard를 직접 보도록 하자.

GET /_cat/shards

logstash index를 보면 하나는 primary shard인 p이고 하나는 replica shard인 r이다. replica shardUNASSIGNED인 것을 확인할 수 있는데, 현재 배포 상황이 single node이기 때문이다. 위에서 logstash index가 yello status를 가졌다고 말했는데, 이는 replica shard가 어느 node에도 배정받지 못해 발생하였기 때문인 것이다.

.kibana_7.17.14_001                                           0 p STARTED     184   2.9mb 172.17.0.2 4dd1202da696
.kibana-event-log-7.17.14-000001                              0 p STARTED                 172.17.0.2 4dd1202da696
.apm-custom-link                                              0 p STARTED       0    227b 172.17.0.2 4dd1202da696
.ds-ilm-history-5-2023.10.25-000001                           0 p STARTED                 172.17.0.2 4dd1202da696
.ds-.logs-deprecation.elasticsearch-default-2023.10.25-000001 0 p STARTED                 172.17.0.2 4dd1202da696
.kibana_task_manager_7.17.14_001                              0 p STARTED      17 978.1kb 172.17.0.2 4dd1202da696
logstash-2023.10.25                                           0 p STARTED    2955   4.1mb 172.17.0.2 4dd1202da696
logstash-2023.10.25                                           0 r UNASSIGNED                         
.apm-agent-configuration                                      0 p STARTED       0    227b 172.17.0.2 4dd1202da696

Node

sharding은 index를 스케일하여 data volume늘려주지만 node를 필요로 한다. node를 늘려주는 방법은 다양한 방법이 있지만 여기서는 다루지 않겠다.

단, node를 늘릴 때 주의할 것이 있는데 node가 1~2개면 single node로도 동작하기 때문에 2개의 node 중에 하나라도 다운되어도 상관없이 elasticsearch service가 동작한다. 그런데 3개의 node로 동작 중이라면 이야기가 좀 달라진다. 3개 중 하나는 master node가 되는데, 2개가 down되어버리면 어떤 node를 master node로 삼을 지 모르기 때문에 elasticsearch service가 동작하지 않는다. 왜 그런가? 남은 node가 master node로 동작하면 되지않을까?

master node는 자동으로 설정되는 것이 아니라, node가 cluster의 master node를 선출하기 때문이다. 때문에 3개부터 master node개념이 도입되는데, 2개의 node가 죽어버리니 master node를 투표할 node가 없어 master node를 선택하지 못하는 것이다. 때문에 elasticsearch service가 죽는다.

그렇다면 master node는 무엇인가? 그리고 master node의 자격은 무엇인가?
1. master node는 다른 node들 간의 indices를 삭제하고 생성해주는 역할을 한다.
2. master node는 다른 node들로부터 선출되는 것이지, 사용자가 선택하는 것은 아니다.

master node는 cluster를 관리하는 node라고 생각할 수 있다.

master node말고도 data node가 있다. data node는 data를 저장하고 data에 관련된 쿼리들을 수행하는데 가장 대표적으로, search query들을 수행한다.

data node를 정리하면 다음과 같다.
1. data를 저장하는 node이다.
2. data에 관련된 query들을 수행한다.

또한, ingest라는 node도 있다. ingest node는 ingest pipeline을 수행하도록 하는데, document를 indexing할 때 사용한다. 가령, document의 IP를 받고 위도, 경도를 알아내어 변환한 document로 새로 저장하도록 할 수 있다. 사실상 logstash의 작은 버전인 것이다.

ingest node를 정리하면 다음과 같다.
1. ingest pipeline을 수행한다.
2. ingest pipeline을 통해서 들어온 data(document)를 변환한다. 즉, logstash와 비슷한 기능을 한다.

이 밖에 machine learning node, coordination node, voting-only node들이 있다.

0개의 댓글

Powered by GraphCDN, the GraphQL CDN