Data Engineering/Distributed System

[OpenSearch] - 4. 분산 아키텍처 & 각 노드 역할과 샤드

cstory-bo 2024. 1. 6. 22:09

이전에는 OpenSearch가 어떻게 개별 쿼리나 인덱스에 대해 어떻게 처리되는지를 알아봤다면
이번에는 어떠게 대량의 인덱스와 쿼리들을 안정적으로 처리하는 지를 알아보려 한다.

OpenSearch 분산 아키텍처

OpenSearch는 아래 요소들로 구성된다.

  • Cluster (클러스터)
  • Node (노드)
  • Shard (샤드)
  • Segment (세그먼트)

출처:  https://codingexplained.com/coding/elasticsearch/introduction-elasticsearch-architecture

클러스터는 노드의 집합을 말한다.

그리고 노드는 클러스터를 구성하는 하나의 인스턴스로, 도큐먼트가 저장되는 곳이다.
하나의 가상 서버로 보아도 된다.

클러스터는 도큐먼트를 여러 노드에 분산시켜 저장할 수 있다.
모든 노드는 클러스터의 인덱싱과 도큐먼트 검색에 참여한다.
모든 노드가 같은 기능을 수행하는 것은 아니다.

노드에도 다양한 타입이 있으며, 이 타입에 따라 수행하는 역할이 다르다. 
하나의 노드에 복수의 타입을 부여할 수도 있다.

외부 클러스터와는 L7인 HTTP 프로토콜로 소통하지만, 클러스터 내부에서는 노드끼리 L4 프로토콜로 동작한다.
기본적으로 모든 노드는 외부 클라이언트의 HTTP 요청에 응답할 수 있지만, 효율적인 처리를 위해 특정 타입 노드의 역할로 제한한다.

출처:  https://nidhig631.medium.com/primary-shards-replica-shards-in-elasticsearch-269343324f86

OpenSearch의 확장성이 뛰어난 이유 중 하나는 Sharding 때문이다.
샤딩은 데이터를 여러 조각으로 나눠 분산 저장하여 관리하는 기술이며, 샤드는 샤딩을 통해 나눠진 데이터 블록이다.

이전에 도큐먼트들은 인덱스에 저장된다고 했지만 이는 논리적인 단위이며
실질적으로 인덱싱과 검색은 샤드에서 이뤄진다.
분산된 샤드를 논리적 단위로 묶은 것이 인덱스이다.

샤드가 필요한 이유 - 샤드가 있으면, 하나의 큰 인덱스를 여러개의 작은 용량의 노드에 분산되게 저장할 수 있다.

샤딩이 필요한 이유 - 여러 물리 장비를 활용하여 클러스터의 하드웨어 리소스를 수평 확장 할 수 있다.
동일한 인덱스에 대한 요청을 여러 노드에 걸쳐 병렬적으로 처리할 수 있어 성능이 향상된다.
레플리카 샤드(복제된 샤드) 덕분에 HA(High availabillity) 그리고 빠른 처리 속도를 확보할 수 있다.

OpenSearch는 데이터 손실을 대비하여 기본적으로 샤드 복제를 지원한다.
원래의 샤드를 프라이머리 샤드, 복제된 샤드를 레플리카 샤드라고 부른다.

인덱스를 생성할 때 직접 샤드의 개수를 지정할 수 있다. 너무 샤드의 개수가 많아도 데이터가 너무 잘게 쪼개져있어 불필요하게 성능의 손해가 발생하며 샤드가 적으면 병렬 처리 성능이 낮아지기에 자신의 상황에 맞게 적절한 값으로 설정해야 한다.

중간에 샤드의 개수는 변경 할 수 없다. 바꾸고 싶다면 새로운 인덱스를 만들고 데이터를 이동시켜야한다. 변경 할 수 없는 이유는 라우팅 방식 때문이다. 샤드 개수를 반영한 적절히 분산되어 저장시키기 위해 해시함수 이후 샤드 개수로 모드연산으로 라우팅이 진행되기 때문에 샤드의 개수가 바뀌게 되면 라우팅 방식도 바뀌어야한다.

출처:  https://fdv.github.io/running-elasticsearch-fun-profit/003-about-lucene/003-about-lucene.html

샤드는 lucence 인스턴스이기도 하다. 그리고 이 인스턴스는 여러개 lucene 세그먼트를 가지고 있다.

Segment는 인덱스가 물리적으로 저장되는 가장 작은 단위이다.
인덱싱된 도큐먼트는 역인덱스 구조로  하나의 세그먼트로 저장된다.

세그먼트는 innmutable 데이터이다. 그래서 인데스 값이 바뀌면 새로운 세그먼트를 생성하여 대체한다.

보통 세그먼트에 대한 검색은 병렬적으로 수행할 수 없기에
세그먼트 수가 많을 수록 검색 속도가 느려진다.
그래서 정기적으로 백그라운드에서 세그먼트 병합이 진행된다.이 세그먼트 병합은 OpenSearch API로 트리거 할 수 있다. 추가로 병합을 시작하면 CPU, I/O를 많이 사용하기 때문에 대량의 데이터를 인덱싱 할 떄는 비활성화 하는 것이 좋다.

노드 타입

1. Cluster Manager 노드

클러스터 상태를 모니터링하고 샤드를 노드에 할당하는 등의 주요한 역할을 담당한다.
반드시 클러스터에는 하나의 매니저 노드를 가져야한다.

2. Cluster Manager Eligible 노드

매니저 후보 노드는 매니저 노드 선출 과정에 참여하며 선출 과정을 통해 매니저 노드가 될 수 있다.
그렇기에 매니저 노드와 항상 sync를 맞추려한다.

매니저 노드 선출하기 위해서는
우선, 매니저 후보 노드끼리 서로에게 투표한다.
과반수 득표를 얻은 노드가 매니저 노드가 된다.

3. Data 노드

인덱싱, 검색, 집계 등 모든 데이터 관련 작업을 담당한다. 

실질적인 데이터 처리를 담당하기 때문에 가장 부하를 많이 받는 노드 타입이다.
그래서 모니터링을 통해 데이터 노드의 부하 상태를 체크하는 것이 중요하다.
특정 노드에 유독 부하가 많으면 해당 노드의 샤드를 다른 데이터 노드로 재배치하여 부하를 분산시킬 수 있다.

매니저 노드와 데이터 노드는 전용 노드(하나의 역할만 가진 노드)로 구성하는 것이 좋다.
데이터 노드가 부하가 많아 전반적인 중요한 관리를 하는 매니저 노드에 영향을 줄 수 있기 때문이다.

4. Ingest 노드

출처:  https://hevodata.com/learn/elasticsearch-ingest-pipeline/

인제스트 파이프라인은 도큐먼트를 인덱싱하기 전에 전처리하는 역할을 담당한다.
인제스트 노드는 이 파이프라인을 실행하는 노드이다.

많은 데이터를 수집하거나 복잡한 데이터 전처리 파이프라인을 실행할 때 많이 사용된다.

5. Coordinator 노드

출처:  https://levelup.gitconnected.com/elastic-search-simplified-part-2-342a55a1a7c7

코디네이터 노드는 외부 클라이언트의 HTTP 요청을 처리하는 역할을 한다.
로드밸런서와 비슷한 역할을 하며, 요청을 데이터 노드에게 전달하고 결과를 수집하여 하나의 최종 결과로 집계하여 클라이언트에 응답한다.

코디네이터 노드가 요청 받은 쿼리를 처리하는 순서이다.
1. 외부 클라이언트로부터 쿼리를 요청받는다.
2. 클러스터의 모든 노드에게 동일한 쿼리를 요청한다.(클라이언트 요청에 _routing 값이 포함되어있으면 _routing 을 통해 어떤 노드의 어떤 샤드가 데이터를 갖고 있는지 알 수 있으므로 해당 노드에게만 쿼리 요청한다.)
3. 2에서 요청한 노드로부터 결과를 받아 집계한다.
4. 집계 결과를 클라이언트로 보낸다.

집계할 때 CPU와 메모리를 많이 사용하기 때문에 조심해야한다.

Replica 샤드

출처:  https://codingexplained.com/coding/elasticsearch/understanding-replication-in-elasticsearch

장애가 발생했을 때 백업 역할을 수행하여 HA(고가용성)을 제공하기 위한 것이 목적이다.
그렇기에 프라이머리 샤드와 레플리카 샤드는 같은 노드에 할당하지 않는다.

부수적인 효과로 검색 성능도 높여준다. 프라이머리 샤드가 위치한 노드의 응답이 늦어지면 레플리카 샤드가 위치한 노드에 요청하여 안정적인 성능을 낼 수 있다.

샤드 동기화 프로세스

출처:  https://codingexplained.com/coding/elasticsearch/understanding-replication-in-elasticsearch

백업 역할을 위해선 레플리카 샤드와 프라이머리 샤드는 동기화된 상태를 유지해야 한다. 

  1. 먼저 인덱스에 영향을 미치는 모든 요청은 프라이머리 샤드로 전달된다.
  2. 프라이머리 샤드는 요청된 작업을 검증하고 유효하지 않는 경우 거부한다. ( 예를 들면 맞지 않는 타입의 필드 요청)
  3. 로컬에서 요청된 작업을 수행한다.
  4. 모든 in-sync 레플리카 샤드에 동일한 작업을 요청하며 여러 개의 in-sync 레플리카 샤드가 있는 경우 병렬적으로 수행한다.
    이때 클러스터 매니저 노드가 프라이머리 샤드로 부터 동기화되어야 하는 레플리카 샤드 목록을 관리한다. 이 샤드들을 in-sync 샤드라고 한다. 모두 동기화 되어야 사용자에게 성공적으로 처리됐다고 알린다.
  5. 모든 in-sync 레플리카 샤드가 성공적으로 작업을 수행하고 프라이머리 샤드에 응답하면 프라이머리 샤드는 클라이언트에게 요청된 작업을 승인한다.

도중에 장애가 발생하면, 매니저 노드가 레플리카 샤드 중 하나를 프라이머리로 승격시킨다. 그리고 인덱싱 작업은 새로운 프라이머리 샤드로 전송한다. 
레플리카 샤드에서 문제가 생기면, 프라이머리 샤드가 매니저 샤드에게 해당 레플리카 샤드를 in-sync 레플리카 목록에서 제거해달라고 요청한다. 제거가 승인된 경우에만 최종족으로 클라이언트의 요청을 승인하고 매니저 노드는 다시 다른 노드에 레플리카 샤드 생성을 요청한다.