엘라스틱 스택(or ELK 스택)은 애플리케이션 지표의 오류 문제 해결부터 로그 보안 위협 조사, 웹 사이트 및 애플리케이션의 검색 상자 구현에 이르기까지 다양한 사용 사례에서 사용됩니다. Elasticsearch, Kibana, Beats 그리고 Logstash로 구성된 엘라스틱 스택은 모든 종류에 대한 검색 및 분석을 위해 유연하고 다양한 도구를 제공합니다.

각각의 기술을 소개하자면 다음과 같습니다.

  • Elasticsearch는 다양한 용도로 사용가능한 데이터 저장소이자, 전문 검색 엔진입니다. 대용량 데이터를 저장하고 검색과 집계 작업을 신속하게 처리할 수 있습니다.

  • Kibana는 엘라스틱서치용 사용자 인터페이스입니다. 이를 통해 시각화 자료를 검색하고 엘라스틱 서치를 관리할 수도 있습니다.

  • Beats는 다양한 소스 시스템에서 데이터를 수집하고 Elasticsearch나 Logstash로 전송할 수 있습니다.

  • Logstash는 ETL 도구로서 다양한 소스의 데이터를 수집 후 가공해 Elasticsearch에게 제공해주는 역할을 담당합니다.

 


흔히들 많이 알고있는 ELK 스택은 엘라스틱 스택에서 Beats를 포함하지 않은 아키텍처를 의미합니다.
BeatsLogstash는 외부의 데이터 소스로부터 데이터를 수용(Ingest)하는 역할을 담당하는데 각자 가지는 강점이 있습니다. 만약 Beats를 사용하지 않는 아키텍쳐의 경우 ELK 스택이라고도 부를 수도 있겠습니다. 다만, Elasticsearch를 서비스하는 Elastic에서는 ELK라는 이름보단 엘라스틱 스택(Elastic Stack)이라는 이름으로 명명하기를 원하는 듯 합니다.

 

엘라스틱 스택의 진화


1. 루씬(Lucene)이라는 오픈소스 검색 엔진을 활용하여 Compass라는 트랜젝션 OSEM(Object/Search Engine Mapping)이 개발되었습니다. (https://github.com/kimchy/compass)

2. 그러나 Compass는 인프라 확장 면에서 한계를 보였습니다. 이 한계를 극복하기 위해 Elasticsearch라는 분산 검색 엔진이 개발되었고, JSON 기반의 HTTP RESTful API로 구현되어 자바 외의 다른 언어로도 쉽게 상호작용이 가능하게 되었습니다.

3. 이후, Logstash가 등장했습니다. 초기에 Logstash는 여러 데이터 목적지 중 하나로 Elasticsearch를 지원하였습니다.

4. Elasticsearch의 데이터 시각화를 위해 Kibana가 개발되었습니다. Kibana는 Elasticsearch의 REST API를 활용하여 데이터 검색 및 시각화를 중개하는 인터페이스를 제공합니다.

5. Elasticsearch를 관리하는 Elastic에서 Elasticsearch에 대한 클라우드 솔루션을 출시하였습니다.

6. 네트워크 패킷 데이터 수집에 특화된 Packetbeat라는 오픈소스 도구가 등장했으며, Packetbeat는 Elasticsearch를 위한 전용 솔루션으로 설계되었습니다. Packetbeat에 이어 다양한 유형의 데이터 처리를 위한 Beats들이 등장하였고, 이는 Beats 프로젝트로 확대되었습니다.

7. Elasticsearch에 있는 데이터에 대한 이상 탐지 유스케이스를 지원하기 위해 머신러닝 기능이 Elasticsearch와 Kibana에 추가되었습니다.

8. APM(Applicaton Performance Monitoring) 기능이 Elastic Stack에 통합되었습니다.

9. Kibana는 SIEM(Security Information and Event Management) 기능의 일부로 보안 분석 기능을 갖게 되었습니다.

10. 일종의 Elasticsearch의 확장팩 역할을 수행하는 X-Pack이 오픈소스화되었습니다.

11. EDR(Endpoint Detection and Response) 기능이 Elastic Stack에 추가되었습니다. EDR과 SIEM을 중심으로 한 보안 솔루션을 제공하게 되었습니다.

12. 웹 사이트와 애플리케이션, 내부 사이트에서 바로 사용가능한 검색 기능이 Enterprise Search 솔루션에 추가되었습니다.


Elasticsearch의 주요한 특징

  • 방대한 양의 데이터를 효과적으로 검색, 분석, 집계하는데 활용됩니다.

  • JSON 형태의 데이터를 저장하는 문서 기반의 NoSQL 저장소입니다.

  • 별도의 고정 스키마를 요구하지 않습니다.

  • Apache Lucene을 기반으로 하며, 이 위에 분산 처리와 다양한 기능을 추가하여 클러스터링, 복제 및 샤딩 같은 기능을 제공합니다.

  • 서로 IOPS 사양이 다른 노드 간에 데이터를 이동시켜, 느린 디스크 드라이브의 비용 절감 효과와 더 빠른 성능 효과를 동시에 얻을 수 있습니다.


프라이머리 샤드와 레플리카 샤드

1. 프라이머리 샤드 (Primary Shard)

  • 인덱스 생성 시점에 정의되며, 이후에는 프라이머리 샤드의 수를 변경할 수 없습니다.

  • 실제 데이터의 Write (CRUD 작업) 연산이 이 샤드에서 이뤄집니다.

  • 인덱스의 모든 데이터를 보유하므로, 대량의 데이터를 효과적으로 관리하기 위해서는 샤드의 수를 적절하게 분산하는 것이 필요합니다.

  • 프라이머리 샤드가 손실될 경우 (예: 노드의 다운), 해당 샤드의 레플리카는 프라이머리 샤드로 승격될 수 있습니다.

2. 레플리카 샤드 (Replica Shard)

  • 프라이머리 샤드의 복사본으로, 프라이머리 샤드의 데이터를 동기화하여 유지합니다.

  • 주로 Read 작업을 위해 사용되며, 실패한 노드의 프라이머리 샤드를 대체할 수도 있습니다.

  • 여러 레플리카를 통해 읽기 처리량을 늘릴 수 있습니다. 레플리카가 여러 개 있으면 동시에 여러 읽기 요청을 처리할 수 있게 됩니다.

  • 레플리카는 데이터의 무결성과 복구 능력을 강화하기 위해 사용됩니다. 프라이머리 샤드가 손실되거나 노드에 문제가 발생할 경우, 레플리카 샤드는 프라이머리로 승격되어 시스템의 안정성을 보장합니다.

 

레플리카 샤드는 READ 작업을 처리할 수 있지만, 실제 데이터의 Write 작업은 오직 프라이머리 샤드에서만 이루어집니다.

프라이머리 샤드에서 Write 작업이 완료되면, 변경사항은 레플리카 샤드로 비동기적으로 복제됩니다. 이로 인해 장애가 발생해도 데이터의 안정성이 보장됩니다.

Elasticsearch에서의 샤드의 복제는 상당히 어려운 주제입니다. 아래에서는 그림들을 통해 간단하게 Elasticsearch에서 데이터의 안정성을 어떻게 보장하는 간략하게 설명합니다.

5개의 프라이머리 샤드와 복제본이 4개의 노드에 분산되어 저장된 예

 

노드가 1개만 있는 경우 프라이머리 샤드만 존재하고 복제본은 생성되지 않습니다. Elasticsearch 는 아무리 작은 클러스터라도 데이터 가용성과 무결성을 위해 최소 3개의 노드로 구성 할 것을 권장하고 있습니다.

 

Node-3 노드가 유실되어 0번, 4번 샤드가 다른 노드에 복제본을 새로 생성한 예

 

프라이머리 샤드가 유실된 경우에는 새로 프라이머리 샤드가 생성되는 것이 아니라, 남아있던 복제본이 먼저 프라이머리 샤드로 승격이 되고 다른 노드에 새로 복제본을 생성하게 됩니다.

 

노드가 3개로 줄었을 때도 전체 데이터 유지

 

노드의 확장 뿐만 아니라 노드가 감소하는 경우에도 위의 예시처럼 전체 샤드 수를 유지하게 됩니다. 그 과정에서 레플리케이션 샤드의 승격과 삭제될 노드에 위치한 샤드의 이동과 같은 추가적인 작업이 수행됩니다.


다양한 IOPS를 가진 노드를 통한 비용 최적화

Elasticsearch에서는 디스크 성능과 관련된 가격 편차를 최적화하여 이용할 수 있습니다. 일반적으로 IOPS(입출력 연산 속도)가 높은 디스크는 그 성능에 비례하여 더 비싼 가격이 책정됩니다. 반대로 IOPS가 낮은 디스크는 상대적으로 저렴하게 구매할 수 있습니다.

Elasticsearch는 이런 특성을 활용하여 데이터를 효율적으로 관리합니다. 구체적으로, 자주 사용되지 않는 데이터나 장기간 보관되어야 하는 데이터는 성능이 낮아도 되는 저렴한 디스크(Cold Storage)로 이동시킬 수 있습니다. 반면에 자주 접근되거나 중요한 데이터는 성능이 높은 고가의 디스크(Hot Storage)에 위치시킬 수 있습니다.

이렇게 데이터의 성격과 용도에 따라 다른 스토리지 계층으로 이동시키는 전략을 사용함으로써, 더 비싼 저장 공간에 위치할 필요가 없는 'Cold Data'를 저렴한 'Cold Storage'로 옮기는 등의 최적화를 통해 전반적인 저장 비용을 효과적으로 절감할 수 있습니다.


역인덱스 (Inverted Index)

Elasticsearch는 현대의 데이터 기반 애플리케이션에서 빠른 검색 및 분석 능력을 제공합니다. 이러한 빠른 검색 능력은 "역인덱스"를 통해 달성할 수 있습니다.

Elasticsearch는 대량의 데이터에서 원하는 정보를 실시간으로 빠르게 검색하는 기능을 제공해야 합니다. 전통적인 데이터베이스의 인덱싱 방식만으로는 수백만, 수십억의 문서에서 특정 단어나 구문을 즉시 찾아내기 어렵습니다. 여기서 역인덱스의 역할이 중요하게 나타납니다.


빠른 검색

 

전통적 검색 방식에서는 문서를 하나씩 확인하며 원하는 키워드가 포함되는지 여부를 검사합니다. 이렇게 하면 문서의 수가 많아질 수록 검색하는 데 시간이 기하급수적으로 증가하게 됩니다.

그런데 역인덱스를 사용하면, 우리가 원하는 키워드에 대한 정보(어떤 문서들에 해당 키워드가 포함되어 있는지)를 미리 정리해놓기 때문에, 검색 시 해당 키워드에 대한 문서들을 즉시 알 수 있습니다. 즉, 문서의 수에 관계없이 일정한 시간 내에 원하는 키워드를 포함하는 문서들을 찾아낼 수 있게 됩니다.


공간 효율성

 

전통적인 방법으로 각 문서에 어떤 단어들이 포함되어 있는지를 저장하려면, 각 문서마다 모든 단어의 목록과 그 위치를 저장해야 합니다. 이렇게 되면 데이터의 중복이 상당히 많아지게 됩니다.

그러나 역인덱스를 사용하면, 단어별로 어떤 문서들에 포함되어 있는지를 저장하게 됩니다. 예를 들어 "Elasticsearch"라는 단어가 1,000개의 문서에 포함되어 있다면, "Elasticsearch"라는 항목 아래에 해당 문서의 ID만 저장하면 됩니다. 이렇게 하면 각 단어에 대해 한 번씩만 정보를 저장하므로, 공간을 효율적으로 사용할 수 있습니다.


역인덱스 테이블의 예시

 

예를 들어 다음 3개의 문서가 Elasticsearch에 저장되어 있다고 가정합니다.

I love Elasticsearch.
Elasticsearch is powerful.
People love powerful tools.

 

역인덱스는 각 토큰(단어 혹은 용어)을 기준으로 해당 토큰이 어떤 문서에 위치하는지를 지정합니다. 기존의 전통적인 인덱싱 방식은 문서를 중심으로 해당 문서에 어떤 단어들이 있는지를 리스트화하는 구조를 가집니다. 반면, 역인덱스는 이와 정반대의 구조를 가지며, 각 토큰을 중심으로 해당 토큰이 어떤 문서들에 위치하는지를 매핑합니다.

이러한 구조의 장점은 특정 단어나 구문을 검색할 때 해당 단어나 구문이 어떤 문서에 있는지 바로 알 수 있어서 검색이 매우 빠르게 수행될 수 있습니다.

즉, 역인덱스는 '어떤 단어가 어떤 문서들에 포함되어 있는가?'를 효율적으로 파악하기 위한 데이터 구조입니다. 이를 통해 검색 시스템은 사용자의 쿼리에 해당하는 문서들을 빠르게 찾아낼 수 있습니다.

예를 들어 "love" 라는 단어를 검색하게 되면, 역인덱스 구조를 통해 Elasticsearch는 해당 토큰이 문서 1과 문서 3에 포함되어 있음을 빠르게 파악할 수 있습니다. 이처럼 역인덱스는 검색 쿼리가 들어올 때 해당 키워드가 포함된 문서를 즉시 알아낼 수 있게 해주므로, 매우 빠른 검색 성능을 제공합니다.

토큰(Token) 문서 ID (Document IDs)
----------------------------------------------------------------------- -----------------------------------------------------------------------
I 1
love 1, 3
Elasticsearch 1, 2
is 2
powerful 2, 3
People 3
tools 3

 

 


참고. 위의 테이블을 전통적 방식의 인덱스로 표현한다면

 

문서 ID (Document IDs) 토큰(Token)
----------------------------------------------------------------------- -----------------------------------------------------------------------
1 I, love, Elasticsearch
2 Elasticsearch, is, powerful
3 People, love, powerful, tools

 

전통적인 인덱싱 방식과의 비교

데이터베이스에서 주로 사용되는 전통적인 인덱스는, 주로 B-tree, B+-tree, Hash 등의 구조를 사용합니다. 이때 인덱스를 사용하는 주된 목적은 특정 키 값을 기반으로 연관된 값을 빠르게 찾아내는 것입니다. 이는 범위 검색이나 정렬된 데이터의 검색에 유용합니다. 전통적인 인덱스의 경우에는 키 값을 기반으로하는 조회작업에 대해서 빠른 속도를 보장합니다. 단, 텍스트 기반 검색에 대해서는 역인덱스만큼의 최적화되어있지 않기 때문에, 복잡한 쿼리나 풀 텍스트 검색에 대해서는 비교적 느린 속도를 보입니다.

텍스트 기반의 검색에 대해서는 역인덱스 구조가 전통적 인덱싱 방식에 비해 많은 이점을 가집니다. 역인덱스 구조 자체가 대량의 텍스트 검색에 특화된 인덱싱 방식이라고 이해할 수 있겠습니다. 또한, 텍스트를 기반으로 한 데이터 구성 시, 역인덱스는 메모리 효율성 면에서 큰 장점을 가지게 됩니다. 이전에 제시한 텍스트 문서를 역인덱스 방식과 전통적인 인덱스 방식으로 표현한 예를 통해 이를 명확히 파악할 수 있습니다. 전통적인 인덱스 방식에서는 중복된 단어로 인해 데이터 저장 구조의 중복이 불가피합니다. 반면, 역인덱스에서는 각 단어(토큰)를 키로 활용해 해당 단어가 포함된 문서의 ID를 매핑합니다. 이는 방대한 양의 데이터를 저장함에 있어 메모리 측면에서 매우 강력한 이점으로 작용합니다.

결론적으로, 전통적인 인덱싱과 역인덱싱은 각각 특별한 장점을 가지고 있습니다. 전통적인 인덱스는 정렬된 데이터나 범위 검색 같은 특정 케이스에서 뛰어난 성능을 발휘합니다. 이와는 대조적으로, 역인덱싱은 텍스트 기반의 검색에서 매우 효과적이며, 대량의 문서나 데이터를 처리할 때 그 진가를 발휘합니다. 특히, 복잡한 쿼리나 풀 텍스트 검색에 있어서, 역인덱싱은 전통적인 인덱싱 방식보다 월등히 빠른 성능을 보여줍니다. 이는 역인덱싱이 텍스트 데이터를 토큰화하여 그 토큰들의 출현 위치나 빈도 등을 효율적으로 관리하기 때문입니다.

때문에 대용량의 텍스트 정보를 신속하게 검색하거나 분석하는 서비스, 예를 들면 Elasticsearch와 같은 검색 엔진이나 로그 분석 서비스에서는 역인덱스의 활용이 필수적으로 보입니다.


Elasticsearch의 한계

1.  관계형 데이터의 처리가 어렵다.

Elasticsearch는 RDB의 "조인" 같은 연산을 지원하는 메커니즘을 제공합니다. 그러나 Elasticsearch가 기본적으로 분산 시스템 위에 구축되어 있기 때문에, 전통적인 RDB의 조인만큼의 효율적인 성능을 기대하기는 어렵습니다.

  • Nested Type: 'nested' 타입은 문서 내에서 배열 형태의 복잡한 객체 정보를 저장하고, 이러한 내부 객체 간의 관계를 기반으로 "조인"과 유사한 조회를 실행할 수 있게 해줍니다. 즉, 하나의 문서 안에서 객체 간의 깊은 관계를 표현하고 조회하는 데 유용합니다.

  • Parent-Child Relationship: 이 관계를 이용하면, 한 문서를 '부모'로 설정하고, 다른 문서를 그 '자식'으로 지정할 수 있습니다. 이런 방식으로 구성된 문서 간의 관계는 상호 연관성을 가지며, 이를 기반으로 한 검색 쿼리가 가능해집니다.


  • Join Field: RDB의 조인 연산을 모방한 것입니다. 다양한 타입의 문서들을 공통 필드를 통해 연결하고, 이 연결된 관계를 통해 효과적인 검색을 수행할 수 있게 도와줍니다.

하지만, Elasticsearch에서 조인 연산을 너무 자주 사용하는 것은 권장되지 않습니다. 왜냐하면 분산 시스템의 특성 상 조인 연산을 수행하기 위해서는 RDBMS에 비해 추가적인 처리와 함께 부하를 야기하는 원인으로 작용할 수 있습니다. 따라서 가능한 데이터를 비정규화하여 조인의 필요성을 줄이는 전략을 취하는 것이 성능 향상에 더욱 도움이 됩니다.


2.  트랜젝션 단위의 ACID 보장이 어렵다.

Elasticsearch는 기본적으로 요청 단위의 ACID 특성을 지원하되, RDBMS처럼 사용자가 직접 트랜잭션을 정의하거나 제어할 수는 없습니다. 

또한 분산처리 저장소로서 Elasticsearch는 ACID를 보장하는 방식도 기존의 RDBMS보다는 NoSQL과 유사한 측면이 있습니다. 다음은 Elasticsearch에서 ACID를 어떻게 보장하는지에 대한 설명입니다.

원자성(Atomicity)

쓰기 요청은 모든 활성화된 샤드에 전달됩니다. 이러한 요청은 모든 샤드에 성공적으로 기록되거나, 어느 하나의 샤드에서라도 실패하면 전체 요청이 실패합니다.

일관성(Consistency)

Elasticsearch와 같은 분산 검색 및 분석 엔진에서의 일관성은 쓰기 작업 후 모든 노드나 샤드에서 동일한 데이터를 조회할 수 있는 상태를 말합니다. 여기서의 일관성이란 "최종적 일관성(eventual consistancy)"을 의미합니다. 이는 전통적인 RDBMS의 "강한 일관성 (strong consistancy)"와는 살짝 다른 의미로 사용됩니다. 

"최종적 일관성"이란, 어떠한 변경 작업 후에 시스템의 모든 노드나 샤드가 결국에는 동일한 데이터 값을 갖게 된다는 것을 의미합니다. 즉, 변경이 발생하고 나서 즉시 모든 노드나 샤드가 동일한 값을 가질 수는 없지만, 어느 정도의 시간이 지나면 모든 샤드가 동기화되어 동일해집니다.

예를 들어 다음의 상황을 생각해봅시다.

1. 사용자가 샤드 A에 쓰기 요청을 보냅니다.

2. A는 해당 쓰기 요청을 처리하고 성공적으로 완료되었다는 응답을 사용자에게 반환합니다. 

3. 이때, 새로운 유저 B와 C가 등장합니다. 유저 B, C는 샤드 A에 대해서 읽기 요청을 보냅니다. 단, 유저 B의 요청은 프라이머리 샤드에서 유저 C의 요청은 레플리카 샤드에서 처리됩니다.

4. 아직 프라이머리 샤드와 레플리카 샤드 간의 동기화 작업이 아직 일어나지 않아, 프라이머리 샤드에만 최신 내용이 기록되어있다고 가정합니다. 그렇다면 유저 B의 응답은 최신 내용을 담고 있겠으나, 유저 C의 요청은 최신 이전의 내용을 담게 됩니다. 

5. 그 이후 Elasticsearch 클러스터 내 샤드 A에 대한 동기화 작업이 백그라운드로 진행됩니다. 프라이머리 샤드에 반영된 최신 데이터가 레플리카 샤드들로 동기화되기 시작합니다.

6. 이번에도 동일하게 유저 B와 C가 샤드 A에 대해서 읽기 요청을 보냅니다. 마찬가지로 우연히 유저 B의 요청은 여전히 프라이머리 샤드에서 유저 C의 요청은 레프리카 샤드에서 처리되었다고 가정하겠습니다.

7. 동기화 작업이 완료되었기에, 두 유저가 받는 데이터는 동일합니다. 즉 모든 샤드에서 동일한 데이터를 조회할 수 있게 됩니다.

결론적으로 Elasticsearch는 "강한 일관성"을 보장하지는 않지만, 시간이 지나면 모든 샤드가 동일한 상태를 갖게되는 "최종적 일관성"을 보장합니다.

참고. 대략적인 쓰기 요청의 처리 과정

1. 쓰기 요청: 사용자로부터 인덱스, 업데이트, 삭제 등의 쓰기 요청이 Elasticsearch 클러스터로 전송됩니다.


2. 트랜잭션 로그 (Translog)에 기록: 쓰기 요청이 받아지면, 해당 변경 사항은 트랜잭션 로그 (또는 Translog)에 기록됩니다. Translog는 쓰기 요청이 빠르게 기록되도록 돕는 장치로, 빠른 응답 시간을 보장하는데 중요한 역할을 합니다.


3. 버퍼에 데이터 저장: 쓰기 요청은 메모리 버퍼에 임시로 저장됩니다.


4. 리프레시: refresh_interval 설정에 따라 (기본적으로 1초마다) Elasticsearch는 메모리 버퍼에 있는 데이터를 새로운 Lucene segment로 만듭니다. 이 시점에서 데이터는 검색 가능해집니다. 그러나 아직 데이터는 디스크에 영구적으로 저장되지 않았습니다.


5. 커밋: 주기적으로, 또는 조건에 따라 Elasticsearch는 Lucene segment를 디스크에 영구적으로 저장합니다. 이 때 Translog에서 해당 데이터에 대한 기록도 삭제됩니다.


6. 머지: 시간이 지나면서 여러 개의 작은 Lucene segment들은 큰 segment로 병합(머지)되기도 합니다. 이는 디스크 공간을 효율적으로 활용하고 검색 성능을 향상시키기 위한 작업입니다.


독립성(Isolation)

각 RESTful 요청 간에는 독립성이 보장됩니다. 이는 곧, 여러 요청이 동시에 수행된다고 했을때 각 요청끼리는 독립적으로 작용한다고 이해할 수 있겠습니다. 단, 이는 Elasticsearch만의 고유한 기능이라고 볼 수는 없으며 RESTful API 기반의 통신을 수행한다면 당연히 발생하는 특징(statelessness)이라고 이해하였습니다. 

 

지속성(Durability)

문서의 변경 사항은 즉시 Lucene Segment에 반영되지 않습니다. 대신, 이러한 변경 사항은 "translog"에 먼저 기록됩니다. 만약 어떠한 장애가 발생한다면, 이 "translog"를 통해 데이터를 복구할 수 있게 됩니다.

"translog"는 Elasticsearch에서 데이터의 지속성(Durability)을 보장하기 위해 사용하는 메커니즘 중 하나입니다. Lucene Segment는 변경 불가능하기 때문에, 문서가 새로 추가되거나 수정될 때마다 바로 Segment에 반영하는 것은 효율적이지 않습니다. 그래서, 이러한 변경 사항은 일단 translog에 먼저 기록됩니다.


이 translog는 Lucene의 Segment와는 다르게 변경이 가능합니다. 따라서 데이터가 변경될 때마다 빠르게 translog에 쓸 수 있습니다. 그리고 주기적으로, 또는 설정된 조건에 따라 메모리에 있는 변경 사항들과 translog에 있는 변경 사항들이 디스크의 Lucene Segment에 반영(flush)됩니다.


만약 Elasticsearch 노드에 장애가 발생하면, 가장 최근의 Lucene Segment 상태로 복구한 다음 translog에 남아있는 변경 사항을 적용함으로써 데이터의 일관성을 유지할 수 있습니다. 이렇게 함으로써, Elasticsearch는 데이터의 손실 없이 복구가 가능합니다.

추가적으로 Lucene Segment의 특징을 다음과 같이 정리할 수 있습니다.

Write segment

Lucene의 Segment는 변경이 불가능한(immutable) 데이터 구조입니다. 이는 한 번 생성된 Segment는 수정되거나 삭제될 수 없다는 것을 의미합니다.

새로운 문서가 추가되거나, 기존 문서가 수정/삭제될 때마다 바로 해당 Segment 파일에 반영되는 것이 아닙니다. 대신, 수정/삭제는 새로운 Segment를 생성하여 이를 반영하고, 원본 문서는 "삭제" 표시만 되는 방식으로 동작합니다.

여러 변경 작업들이 메모리에 쌓인 후 일정한 시점이나 크기에 도달하면, 그 변경들은 새로운 Segment로 디스크에 플러시(flush)됩니다.

Merging segment

시간이 지남에 따라 많은 작은 Segments가 생길 수 있습니다. 이렇게 되면 검색 성능에 부정적인 영향을 줄 수 있습니다.
Lucene는 백그라운드에서 자동으로 이런 작은 Segments를 큰 Segment로 병합하는 작업을 수행합니다. 이 과정을 "Segment Merging"이라고 합니다. 병합 과정에서 "삭제" 표시된 문서들은 실제로 제거되며, 최종적으로 하나의 큰, 최신 Segment가 생성됩니다.

Update segment

새로운 데이터가 쓰여질 때나, 기존 데이터가 수정될 때마다 Segment가 바로 업데이트되는 것은 아닙니다. 일반적으로는 변경 작업들이 일정한 버퍼(메모리)에 축적되고, 이 버퍼가 특정 크기에 도달하거나 일정 시간이 경과하면 해당 변경들이 Segment로 디스크에 플러시됩니다. Elasticsearch의 경우, refresh_interval 설정으로 이 시점을 조절할 수 있습니다. 기본값은 1초입니다.

Elasticsearch과 같은 분산처리 시스템에서는 "optimistic concurrency control" 이라는 추가적인 메커니즘을 통해 보다 높은 수준의 일관성을 보장을 지원합니다. 이때 해당 매커니즘이 적용되면 각 요청이 특정 버전의 문서를 참조하도록 하여, 동시에 여러 쓰기나 업데이트 요청이 있을 경우, 해당 버전의 문서에만 영향을 주게 됩니다.

https://www.elastic.co/guide/en/elasticsearch/reference/current/optimistic-concurrency-control.html

 

Optimistic concurrency control | Elasticsearch Guide [8.10] | Elastic

Optimistic concurrency controledit Elasticsearch is distributed. When documents are created, updated, or deleted, the new version of the document has to be replicated to other nodes in the cluster. Elasticsearch is also asynchronous and concurrent, meaning

www.elastic.co


만약 클라이언트가 업데이트하려는 문서의 버전과 Elasticsearch에 저장된 문서의 버전이 다르면, 업데이트 요청은 거부됩니다. 보다 자세한 내부 동작 방식은 다음과 같습니다.

버전 관리(Versioning): Elasticsearch의 버전 관리는 문서와 샤드, 두 레벨에서 발생합니다. 먼저 문서의 경우 "if_seq_no"를 통해 optimistic concurrency control을 수행합니다. 이때 "seq_no"는 "sequence number" 즉 순서를 기록한 번호를 의미하며 이는 쓰기 작업을 통해 문서의 생성, 수정, 삭제 등의 변화가 발생할 때마다 증가합니다.

동시에 모든 샤드는 "if_primary_term"을 통해 샤드 레벨에서 버전을 관리합니다. 이때 "primary_term"은 프라이머리 샤드의 변화가 발생하였을 경우 증가하게 됩니다. 이때의 변화라 함은 기존의 레플리카 샤드가 프라이머리 샤드로 승격되는 등의 동작을 의미합니다. 이때 "primary_term"은 프라이머리 샤드가 계속 바뀌더라도 샤드의 일관성을 보장하는 데 주요한 역할을 수행합니다.

PUT products/_doc/1567?if_seq_no=362&if_primary_term=2
{
  "product": "r2d2",
  "details": "A resourceful astromech droid",
  "tags": [ "droid" ]
}

 

해당 요청에서는 "seq_no"와 "primary_term"의 값이 현재 Elasticsearch 내부의 값과 일치하는 경우에만 작업이 수행됩니다. 만약 일치하지 않으면 해당 작업은 실패합니다.

Elasticsearch에서는 If_seq_no와 if_primary_term을 동시에 활용하여, 동시적으로 문서를 업데이트하는 경우 각 문서들을 올바른 순서로 관리하고 기존의 업데이트 내용을 실수로 덮어쓰는 등의 불상사를 방지해줍니다.

실제로 Elasticsearch에 GET 요청을 통해 다음과 예시와 같은 응답을 받을 수 있는데, seq_no와 primary_term이 포함되어 있음을 확인할 수 있습니다.

{
  "_index": "my-index-000001",
  "_id": "0",
  "_version": 1,
  "_seq_no": 0,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "@timestamp": "2099-11-15T14:12:12",
    "http": {
      "request": {
        "method": "get"
      },
      "response": {
        "status_code": 200,
        "bytes": 1070000
      },
      "version": "1.1"
    },
    "source": {
      "ip": "127.0.0.1"
    },
    "message": "GET /search HTTP/1.1 200 1070000",
    "user": {
      "id": "kimchy"
    }
  }
}

 

마치며

읽기와 쓰기 요청을 처리하는 Elasticsearch의 방식이 흥미로웠습니다. 배우기에 다소 난이도가 있지만, 이는 분산처리 솔루션이기에 발생하는 특징으로 다른 분산처리 데이터베이스를 학습할 때도 동일하게 발생하는 어려움이라고 생각합니다.

앞으로 Elasticsearch의 보다 구체적인 사용방법과 그 원리를 설명하는 글을 작성해볼 수 있을거 같습니다.

아래의 글은 Elasticsearch의 읽기와 쓰기가 어떻게 처리되는지에 대한 꽤나 자세한 내용을 담고 있는 공식 문서입니다. 이해에 도움이 되시면 좋겠습니다.

https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-replication.html#basic-write-model

 

Reading and Writing documents | Elasticsearch Guide [8.10] | Elastic

Reading and Writing documentsedit Introductionedit Each index in Elasticsearch is divided into shards and each shard can have multiple copies. These copies are known as a replication group and must be kept in sync when documents are added or removed. If we

www.elastic.co