이것이 점프 투 공작소

ElasticSearch의 노드와 샤드 본문

ELK

ElasticSearch의 노드와 샤드

겅겅겅 2023. 3. 26. 16:54

ElasticSearch에서 노드란?

ES에서 클러스터를 구성하는 하나의 인스턴스(instance)입니다.

물리적 서버 하나에 노드 하나를 구성하는 방식을 권장합니다.

 

노드의 종류

1. 마스터 노드

  • 클러스터는 반드시 하나의 마스터 노드를 가집니다.
  • 사용자는 마스터 후보 노드(master-eligible-node)를 지정 할 수 있습니다.
  • 클러스터 유지에 필수적이기에 마스터는 사용자가 정할 수 없고 마스터 후보 노드들끼리의 투표를 통해 과반수 이상의 표를 받은 노드가 마스터노드로 선정됩니다.
  • 투표 전용 노드도 존재합니다. (마스터 후보 노드끼리의 투표로 과반수가 넘지 못할 때를 대비하여 지정합니다)
  • 클러스터의 상태만 관리합니다.

2.  데이터 노드

  • 인덱싱한 도큐먼트를 샤드 형태로 저장합니다 (데이터의 CRUD, 검색, 집계 작업) 
  • 실제 ES의 작업을 수행하는 노드이기에 가장 고사양의 하드웨어로 구성하는것이 좋습니다.
  • 적절한 리소스 배치로 특정 데이터 노드에 부하가 걸리지 않도록 해야합니다 (샤드 재배치 등)
  • 효율적인 사용을 위해 핫, 웜, 콜드 노드 설정을 통해 리소스를 절감할 수 있습니다. (샤드 할당 필터링, 데이터 티어 등으로 설정)

3. 인제스트 노드

  • 도큐먼트의 가공과 정제를 위한 파이프라인이 실행되는 노드입니다.
  • LogStash없이 비트만 설치하여 데이터를 수집 할 때 유용하게 사용 할 수 있습니다.
  • 기본적으로 모든 노드는 인제스트 노드의 역할을 수행 할 수 있으나, 필요시 별도의 전용노드를 구성하는것이 좋습니다.

4. 코디네이터 노드

  • REST API 요청을 처리하는 노드입니다.
  • 클라이언트의 요청을 처리하기 위해 다른 노드들의 데이터를 취합하여 응답해줍니다, 연산 과정에서 리소스가 많이 필요하기에 필요시 전용 노드를 두는것이 좋습니다.
  • 기본적으로 모든 노드가 코디네이팅을 진행할 수 있습니다. 
  • 대규모 ES구성에서는 코디네이터를 사용하는것이 일반적인 구성입니다.

 

ElasticSearch에서 샤드란?

여러 노드를 효율적으로 사용하기 위해 노드에서는 데이터를 샤드라는 단위로 분산 저장합니다.

실제 도큐먼트는 인덱스가 아닌 샤드 내의 세그먼트에서 검색되고 인덱싱 됩니다.

샤드는 크게 원본을 저장하는 프라이머리 샤드와 유실방지 및 가용성 확보를 위한 레플리카 샤드로 나눠집니다.

 

샤드 하나의 크기는 10GB ~ 40GB 정도로 관리하는것이 좋다고 권고되며,

노드 수에 비해 샤드가 너무 많으면 필요 이상으로 리소스를 소비하게되고, 인덱스 검색을 위해서는 모든 샤드에 접근해야하기에 성능에 좋지 않습니다.

세그먼트란?

ES에서 인덱스가 물리적으로 저장되는 가장 작은 단위입니다.

읽기 전용 형태이며 수정이 불가능합니다.

자체적으로 루씬검색이 가능한 구조이며 토큰화된 역인덱스 데이터와 소스 데이터가 들어있습니다.

클러스터의 모든 샤드에서 1초마다 발생하는 리프레시(refresh) 때마가 생성됩니다.

각 노드에 따른 샤드 할당규칙

ES에서 인덱스 생성 시 기본 프라이머리, 레플리카 샤드는 1개로 설정되어있습니다.

PUT index
{
	"settings" : {
    	"number_of_shards": 3, # 프라이머리 샤드 수
        "number_of_replicas": 2 # 레플리카 샤드 수 (프라이머리 샤드를 2세트 추가합니다.)
    }
}

위와 같은 방식으로 인덱스를 생성하게 되면 각 노드당 3개의 샤드를 가지게 됩니다.

레플리카 수가 2개로 되어있음으로 생성된 프라이머리 샤드 3개에 대한 2개의 레플리카 세트를 추가로 생성합니다.

 

총 샤드 개수 구하는 공식

totalShardsCnt = number_of_shards * (number_of_replicas + 1)

즉 프라이머리 샤드 A0, A1, A2가 생성되면

A0에 대한 복제본 2개, A1에 대한 복제본 2개 , A2에 대한 복제본 2개 가 생성되어 총 9개의 샤드가 노드에 적재됩니다.

 

 

샤드의 저장 위치 결정법 (샤드 라우팅)

인덱스가 생성될 때 코디네이터 노드는 기본적으로 랜덤ID를 생성하고 _routing 파라미터가 명시되지 않았다면 ID를 통해

도큐먼트가 인덱싱될 샤드를 결정합니다.

shard = hash(_routung) % 프라이머리 샤드 수

 

샤드 할당 과정

샤드는 미할당(UNASSIGNED) -> 초기화(INITIALIZING) -> 동작(STARTED) -> 재할당(RELOCATING) 의 순서로 할당됩니다.

 

1. 미할당 (UNASSIGNED)

샤드는 존재하지만 노드에 할당되지 않은 상태입니다.

2 초기화 (INITIALIZING)

샤드를 노드에 로딩 중인 상태를 의미합니다.

세그먼트들을 검색 가능한 상태로 메모리에 띄워두는 과정입니다.

메모리에 적재된 상태는 아니기에 샤드를 사용할 수는 없습니다.

마스터 노드는 데이터 노드 중에서 샤드를 생성할 수 있는 노드를 찾고 프라이머리 샤드를 적재합니다.

3. 동작 (STARTED)

샤드가 메모리에 올라간 형태입니다. 

초기화 단계에서 프라이머리 샤드를 적재하고 동작 (STARTED) 단계에서 레플리카 샤드를 적재합니다.

4. 재할당 (RELOCATING)

샤드가 재배치되는 단계입니다.

노드에 문제가 발생하여 추가 및 삭제될때마다 샤드의 재배치가 일어납니다.

프라이머리 샤드는 반드시 갯수에 맞게 존재해야하기에 필요시 레플리카 샤드가 프라이머리 샤드로 변경됩니다.

 

샤드 크기 관리

rollover API

인덱스가 특정 조건에 도달했을 때 새로운 인덱스를 생성하는 API 입니다.

 

1. alias가 존재하는 인덱스 생성

PUT index-1 # rollover를 사용하기 위해 인덱스명은 인덱스명-숫자 형태로 생성해야합니다.

{
	"aliases": { # 별칭 생성
    	"rollover-alias" : { # 별칭 명
        	"is_write_index": true
        }
    }
}

위 방법으로 인덱스를 생성하면 별칭을 사용하는 인덱스를 생성합니다.

별칭을 통해 인덱스간의 그룹핑 효과를 얻을 수 있고, "is_write_index": true 옵션을 사용하여 별칭으로 접근할 때 해당 인덱스에 도큐먼트를 쓸 수 있게됩니다.

2.  rollover API 요청

POST rollover-alias/_rollover
{
	"conditions": {
    	"max_age": "30d", # 인덱스 생성 후 경과일
        "max_docs": 100, # 인덱스 도큐먼트 최대 갯수
        "max_size": "50gb" # 인덱스 최대 크기
    }
}

위 코드처럼 rollover를 요청헀을 때 조건에 맞는 상황이라면 새로운 인덱스 index-2 가 생성된다.

rollover가 되면 쓰기의 경우 rollover되어 새로 생긴 index-2가 처리하고 읽기는 별칭이 같은 모든 인덱스가 참여하게 됩니다.

shrink API

기존 인덱스의 프라이머리 샤드 수를 줄이기 위해 사용합니다. (오버샤딩 방지)

핫 노드에서 웜 노드로 이동하는 인덱스에도 사용 할 수 있습니다.

 

1. 모든 프라이머리 노드를 특정 노드로 이동시킵니다.

PUT shrink-1/_settings
{
	"index" : {
    	"number_of_replicas": 0, # 레플리카 샤드 비활성
        "routing.allocation.require._name": "지정노드", # 모든 샤드가 '지정노드'로 옮겨집니다.
        "blocks.write": true # shrink 진행 중 도큐먼트 인덱싱 방지 (읽기 전용모드 지정)
    }
}

쉬링크 후에 합쳐진 프라이머리 샤드의 이동을 빠르게 하기위해 레플리카 샤드를 임시로 비활성화 합니다.

 

2. shrink API 사용요청

POST shrink-1/_shrink/변경할-인덱스명 # shrink인덱스를 변경합니다.
{
	"settings": {
    	"index.number_of_shards": 1, # 1개의 프라이머리 샤드
        "index.number_of_replicas": 1, # 레플리카 샤드 활성화
        "index.routing.allocation.require._name": null, # 인덱스가 다시 분배되도록 설정
        "index.blocks.write": null, 
        "codec": "best_compression" # 저장소를 더 작게 사용하기 위한 설정
    }
}