Elasticsearch - 엘라스틱서치와 루씬의 관계 - 1

2021. 4. 19. 01:05 Elastic Stack/ElasticSearch

 

엘라스틱서치의 구성요소

엘라스틱서치는 기본적으로 클러스터라는 단위로 데이터를 제공한다. 클러스터는 하나 이상의 물리적인 노드로 이루어져 있으며 각 노드는 모두 데이터 색인 및 검색 기능을 제공하는 일종의 물리적인 서버와 같다. 내부에는 루씬 라이브러리를 사용하고 있으며 루씬은 엘라스틱서치의 근간을 이루는 핵심 모듈이다.

 

1)클러스터

클러스터는 데이터를 실제로 가지고 있는 노드의 모음이다. 엘라스틱서치에서는 관련된 모든 노드들을 논리적으로 묶어서 클러스터라고 부른다. 또한 노드들은 같은 클러스터 내부의 데이터만 서로 공유가 가능하다. 같은 클러스터를 구성하는 노드들을 같은 클러스터 이름으로 설정해야한다. 엘라스틱서치는 설정된 클러스터 이름을 이용해 같은 클러스터의 구성원으로 인식된다. 같은 클러스터 내부의 노드는 평소 데이터 색인이나 검색작업을 함께 수행하게 되고 장애가 발생했을 때 데이터 복구를 위한 다양한 작업도 서로 협력해서 함께 진행한다.

 

Cross Cluster Search

일반적으로 검색 시 하나의 클러스터 데이터만 검색하는 것이 원칙이긴 하지만 최초 설계 시 전혀 관련성 없어 보이던 데이터들을 시간이 지나서 데이터가 점점 많이 쌓일 수록 데이터 연관성이 생길 수도 있다. 엘라스틱서치에서는 이처럼 다양한 필요에 따라 다수의 클러스터를 한 번에 검색할 수 있는 Cross Cluster Search라는 기능을 제공한다.

 

www.elastic.co/guide/en/elasticsearch/reference/current/modules-cross-cluster-search.html

 

2)노드

물리적으로 실행된 런타임 상태의 엘라스틱서치를 노드라고 부른다. 노드는 위에서 설명한 클러스터를 이루는 구성원의 일부이며 실제 데이터를 물리적으로 가지고 있는 단일 서버이기도 하다. 실행 시 노드는 클러스터에 의해 UUID가 할당되고 클러스터 내에서는 할당된 UUID로 서로를 식별한다. 기본 값으로 부여되는 UUID를 원하지 않는다면 직접 이름을 설정할 수도 있다.(하지만 이름은 유일해야한다.) 노드는 내부에 다수의 인덱스를 가지고 있으며, 각 인덱스는 다수의 문서를 가지고 있다. 색인 작업을 통해 엘라스틱서치로 전송한 데이터는 인덱스라는 논리적인 자료구조 속에 문서라는 단위로 저장된다. 같은 클러스터 내부에서 존재하는 모든 노드는 서로 다른 노드와 수시로 정보를 주고 받는다. 기본적으로 모든 노드는 마스터 노드와 데이터 노드의 역할을 동시에 수행할 수 있도록 설정되어있지만 실제 대용량의 운영환경에서는 각각 용도에 맞는 노드를 적절히 분리하여 클러스터링하는 것이 좋다.

 

2)-1 노드의 형태

  • 마스터 노드(Master Node) : node.master 설정이 true로 설정된 노드다. 클러스터의 제어를 담당한다.
  • 데이터 노드(Data Node) : node.data 설정이 true로 설정된 노드다. 데이터를 보유하고 CRUD, 검색, 집계 등 데이터 관련 작업을 담당한다.
  • 인제스트 노드(Ingest Node) : node.ingestrk true로 설정된 노드다. 색인 전 전처리 작업을 담당한다.
  • 코디네이팅 노드(Coordinating Node) : 검색이나 집계 시 분산 처리만을 목적으로 설정된 노드다. 대량의 데이터를 처리할 경우에 효율적으로 사용할 수 있는 노드이다.

3)인덱스

엘라스틱서치 인덱스는 유사한 특성을 가지고 있는 문서를 모아둔 문서들의 모임이다. 클러스터 내부에 생성되는 모든 인덱스는 클러스터 내에서 유일한 인덱스명을 가져야한다. 또한 인덱스명은 모두 소문자로 설정해야 한다. 또한 과거 버전과는 다르게 현재 버전들은 하나의 인덱스에 하나의 타입만 생성해야 한다.

 

4)문서

문서는 검색 대상이 되는 실제 물리적인 데이터를 뜻한다. 문서는 인덱스를 생성할 수 있는 기본적인 정보 단위이고 엘라스틱서치에서는 JSON형식으로 문서를 표현한다.

 

5)샤드

인덱스에는 매우 많은 양의 문서가 저장될 수 있다. 일반적으로 하나의 하드웨어에서 제공되는 리소스 이상의 데이터를 저장할 수 없지만 물리적인 한계를 뛰어넘기 위해 샤드라는 개념을 도입했다. 이를 이용하면 데이터를 분산 저장하는 가능하다. 엘라스틱서치에서는 인덱스를 생성할 때 기본적으로 5개의 샤드로 데이터가 분산되도록 생성되고 설정에 의해 샤드의 개수를 원하는 만큼 변경할 수도 있다. 하나의 샤드는 인덱스의 부분 집합이다. 하지만 해당 부분집합으로만으로도 독립적인 검색 서비스가 가능하다. 실제로 인덱스에 질의를 요청하면 인덱스가 가지고 있는 모든 샤드로 검색요청을 보내고 각 샤드의 결과를 취합하여 하나의 결과로 제공한다.

 

6)레플리카

샤드의 복제본을 레플리카라고 한다. 엘라스틱서치에서는 인덱스를 생성할 때 기본적으로 1개의 레플리카를 생성한다. 장애 복구만이 아니고 검색에도 활용되기 때문에 이를 이용하면 읽기 분산에 유리해진다. 엘라스틱서치는 노드의 장애시 페일오버 메커니즘을 레플리카를 이용하여 제공하고 있다. 인덱스가 생성될 때 샤드 개수와 레플리카 개수를 자유롭게 설정할 수 있다. 하지만 인덱스가 생성된  이후에는 샤드 개수를 변경하는 것이 불가능하다. 만약 샤드의 개수를 늘리고 싶다면 샤드개수를 늘린 인덱스를 새로 만들고 기존 인덱스에서 새로 생성한 인덱스로 ReIndex하는 방법 밖에 없다. 데이터가 아주 크다면 Reindex 시간이 아주 길기 때문에 애초에 최적의 샤드의 개수를 정하는 것이 중요하다. 이에 반해 레플리카 개수는 인덱스를 생성한 후에도 자유롭게 변경하는 것이 가능하다.

 

6)-1 엘라스틱서치의 고가용성

엘라스틱서치에서는 샤드나 노드에 장애가 발생할 경우 즉각적인 복구가 가능하기 때문에 안정적인 클러스터 운영이 가능하다. 페일오버 메커니즘을 레플리카를 이용하기 때문에 원본 샤드가 존재하지 않는 노드에 레플리카 샤드를 생성한다. 또한 검색 시 샤드와 레플리카에서 병렬로 실행될 수 있기 때문에 검색 성능이 좋아지는 결과도 있다.

 

7)세그먼트

문서들은 빠른 검색에 유리하도록 설계된 특별한 자료구조로 저장된다. 루씬에 데이터가 색인되면 데이터는 토큰 단위로 분리되고 특수한 형태의 세그먼트라는 단위로 저장이 된다. 이러한 세그먼트는 읽기에 최적화된 역색인이라는 형태로 변환되어 물리적인 디스크에 저장된다. 검색엔진의 특성상 쓰기 연산보다 읽기 연산의 비중이 비교적 높기때문에 읽기에 최적화된 역색인이라는 구조로 저장하는 것이다.

 

 

엘라스틱서치와 RDB비교

엘라스틱서치 RDB
인덱스 데이터베이스
샤드 파티션
타입 테이블
문서
필드
매핑 스키마
Query DSL SQL

 

엘라스틱서치는 루씬 라이브러리를 샤드 내부에 가지고 있으며, 이 루씬 라이브러리가 핵심 모듈이라고 설명했다. 루씬은 검색 라이브러리이고 이 라이브러리에서 중요한 클래스가 바로 IndexWriter & IndexSearcher이다. 간단히 전자는 데이터를 색인하는 역할이고 후자는 검색하는 역할이다. 이 두개를 가지고 색인과 검색 역할을 제공하는 루씬 인스턴스를 루씬 인덱스라고 하는데, 사실 하나의 엘라스틱서치 샤드는 하나의 루씬 인덱스라고 설명할 수 있다. 즉, 샤드가 독립적으로 검색을 제공하는 이유이기도 하다. 이말은 각 샤드마다 데이터를 위한 물리적인 파일을 가지게 될 것이다. 이것을 간단히 표로 표현하면

 

 

엘라스틱서치 인덱스 구조

Elasticsearch Index
Elasticsearch shard Elasticsearch shard Elasticsearch shard Elasticsearch shard
Lucene Index Lucene Index Lucene Index Lucene Index
segment segment segment segment segment segment segment segment

 

루씬 인덱스는 독립적으로 자기가 가지고 있는 세그먼트 내에서만 검색이 가능하다. 하지만 엘라스틱서치는 이러한 루씬 인덱스를 가진 샤드들을 하나의 엘라스틱서치 인덱스로 묶여 있으므로, 다수의 루씬 인덱스에서 동시에 검색이 가능한 것처럼 기능을 제공하게 되는 것이다.

 

 

색인 작업 시 세그먼트의 동작 방식

하나의 루씬 인덱스는 내부적으로 다수의 세그먼트로 구성돼 있다. 읽기 성능이 중요한 검색엔진에서는 하나의 세그먼트로 검색 요청을 처리하는 것보다 다수의 세그먼트를 생성해서 나눠서 처리하는 것이 훨씬 효율적일 것이다. 루씬은 검색 요청을 받으면 다수의 작은 세그먼트 조각들이 각각 검색 결과 조각을 만들어 내고 이를 통합하여 하나의 결과로 합쳐서 응답하도록 설계돼 있다. 이러한 검색 방식을 세그먼트 단위 검색이라고 한다. 세그먼트는 역색인 구조를 지닌 파일 자체를 의미하는데 세그먼트 내부에는 실제로 색인된 데이터가 역색인 구조로 저장돼있다.

루씬은 세그먼트들을 관리하기 위한 용도로 커밋 포인트라는 자료구조를 제공한다. 커밋 포인트는 여러 세그먼트의 목록 정보를 가지고 있으며, 검색 요청 시 이를 적극적으로 활용한다. 최초 색인 작업 요청이 루씬에 들어오면 IndexWriter에 의해 색인 작업이 이루어지고 결과물로 하나의 세그먼트가 생성된다. 그 후 색인 작업이 추가로 요청될 때마다 새로운 세그먼트가 추가로 생성되고 커밋 포인트에 기록된다. 즉, 색인 작업이 일어날 때마다 세그먼트 개수는 점점 늘어난다. 하지만 너무 많은 세그먼트가 생성되면 읽기 성능이 저하될 수 있기 때문에 루씬은 백그라운드에서  주기적으로 세그먼트 파일을 병합하는 작업을 수행하고 이를 통해 모든 세그먼트들을 물리적으로 하나의 파일로 병합한다.

즉, 일정시간이 지나고 추가 색인 작업이 없는 상태라면 최종적으로 하나의 커다란 세그먼트만 남는다. 

색인 작업이 계속 될때마다 세그먼트는 계속 추가적으로 생성된다. 이말은 즉, 세그먼트는 불변성을 가지게 되고 병합과정 이외에는 절대 수정되지 않는다.

 

루씬의 색인 동작과정

  • 최초 색인 요청 - 1) IndexWriter가 세그먼트를 생성 -> 2)IndexSearch가 생성된 세그먼트를 읽어 검색결과 제공
  • 추가 색인 요청 - 1) IndexWriter가 세그먼트를 추가 생성 -> 2)세그먼트가 추가 생성되는 동안 기존 세그먼트만 읽어 검색 결과 제공 -> 3)세그먼트 생성이 완료되면 생성된 모든 세그먼트를 읽어 검색 결과 제공
  • 주기적으로 세그먼트 병합 작업이 일어날 경우 - 1)IndexWriter가 병합 대상이 되는 세그먼트들을 복제 -> 2)IndexWriter가 복제한 세그먼트들을 하나의 세그먼트로 병합 -> 3) 병합 과정 중에는 기존 원본 세그먼트로 검색 결과 제공 -> 4) 병합작업 완료시 원본 세그먼트와 병합 세그먼트를 교체하고 원본 세그먼트 삭제 

 

여기까지 내용을 끊고 다음 포스팅에서 이어서 설명합니다.



출처: https://coding-start.tistory.com/176?category=757916 [코딩스타트]