[Elasticsearch] 아키텍처와 동작 원리
Cluster
데이터를 다루는 노드들로 구성되며 엘라스틱서치는 클러스터로 대규모 데이터를 효율적으로 관리한다.
Node
클러스터의 구성 요소로 데이터를 다루는 단일 실행 인스턴스이다.
클러스터 내 각 노드들은 다른 노드들과 통신한다.
Index
RDB의 테이블과 유사한 개념으로 Document를 모아둔 집합이다.
인덱스는 샤드로 분할돼 클러스터 내 여러 노드에 분산 저장해 데이터를 효율적으로 처리한다.
각 샤드에는 레플리카가 설정돼 특정 노드에 장애가 발생했을 때 데이터를 보존하거나 읽기 부하를 분산시킨다.
인덱스에 Document를 저장하는 과정을 인덱싱이라고 부른다.
Document
엘라스틱서치에서 다루는 데이터의 기본 단위로 인덱스에 저장된 JSON 형식의 레코드이다.
도큐먼트는 인덱스에 저장되고, 인덱스는 노드에 저장되고, 클러스터는 노드로 구성된다.
클러스터는 전체 시스템을 구성하는 단위로 데이터를 분산하고 관리하는 역할을 수행한다.
대부분 단일 클러스터로 구성해도 웬만한 요구사항을 충족할 수 있고, 규모가 작은 경우 단일 클러스터와 단일 노드로도 충분할 수 있다.
엘라스틱서치 엔진 내부에는 스프링부트처럼 HTTP 웹 서버가 내장되어있고, 엘라스틱서치가 제공하는 RESTful API를 통해 들어오는 HTTP 요청을 처리한다.
Tomcat, Netty 같은 잘 알려진 WAS를 사용하는건 아니고 엘라스틱서치에 최적화된 서버를 사용한다.
엘라스틱서치는 데이터가 증가할 때 새로운 노드를 추가하는 방식으로 쉽게 확장에 대응할 수 있다.
샤드는 Apache Lucene 기반으로 설계되어있고, 각 샤드들은 독립적인 Lucene 인덱스로 구현되었으며 엘라스틱서치는 Lucene 인덱스를 관리하고 검색하는 분산 시스템을 제공한다.
검색 요청이 들어오면 각 샤드에 대해 병렬로 검색하고 결과를 취합해 최종 결과를 반환한다.
RDBMS에서의 샤딩은 물리적으로 데이터베이스를 나누고 나눈 데이터베이스에 데이터를 분산 저장해 대용량 데이터를 처리하는 작업을 의미하지만, 엘라스틱서치에서의 샤딩은 기본 구성 요소인 인덱스를 여러 Lucene 기반 인덱스로 분할하는 작업을 의미한다.
모두 대규모 데이터를 처리하기 위해 고안된 기술이지만 서로 다른 개념이다.
샤딩은 인덱스 수준에서 처리된다.
인덱스는 하나 이상의 샤드로 구성되고, 데이터의 분할은 인덱스 단위로 이루어져 인덱스에서는 기본 샤드와 복제본 샤드를 설정해 데이터를 성격에 따라 분산시킨다.
하나의 인덱스에는 Primary 샤드와 Replica 샤드가 최소 하나씩은 포함되어있고, Replica 샤드는 항상 Primary 샤드와 다른 노드에 저장돼 노드에 장애가 발생하는 경우 데이터 손실 발생에 대비한다.
클러스터 내 특정 노드가 죽더라도 다른 노드에 저장된 Replica 샤드를 통해 계속 데이터에 접근할 수 있고, 검색 요청 시에도 Replica 샤드와 Primary 샤드를 병렬로 읽어 읽기 성능을 끌어올릴 때도 사용한다.
Primary 샤드가 생성되면 클러스터 설정에 따라 자동으로 Replica 샤드를 생성하고 다른 노드에 분배해준다.
Primary 샤드는 데이터의 원본 역할을 수행하고, 모든 변경사항은 가장 먼저 Primary 샤드에 등록되고 Replica 샤드는 Primary 샤드의 데이터를 실시간으로 동기화해 데이터의 안정성을 높인다.
새로운 Document를 추가해달라는 HTTP 요청이 도착하면 엘라스틱서치는 문서 ID를 해싱한 후 저장할 Primary 샤드를 결정한 후 Primary 샤드와 Replica 샤드에 Document를 저장하고 색인한다.
엘라스틱에서 노드는 클러스터를 구성하는 요소로, 실질적으로 데이터를 저장하고 색인하고 검색하는 독립적인 실행 인스턴스이다.
Master Node - 클러스터를 관리하는 노드로 인덱스 관리 / 노드 추적 / 샤드 할당 등 시스템 관리 관련 작업을 수행한다.
Data Node - 데이터를 저장하고 검색, 색인 요청을 처리하는 노드로 Primary 샤드와 Replica 샤드가 저장된다.
Ingest Node - 데이터가 인덱스에 추가되기 전 파이프라인을 실행해 전처리 작업을 수행한다. (로직이 복잡하면 Logstash)
Coordinating Node - 클라이언트의 요청을 받고 해석해 작업을 데이터 노드로 분배하는 로드밸런서 역할을 수행한다.
노드마다 고유한 yml파일이 제공되고 해당 파일을 통해 노드를 정의할 수 있다.
Documnet를 추가할 때는 JSON 형식의 데이터를 인덱스에 삽입한다.
엘라스틱서치 엔진이 JSON 데이터를 분석해 인덱스에 추가해주는데, auto_index 옵션을 설정해주면 기존에 정의되지 않은 인덱스라도 문서를 삽입할 때 자동으로 인덱스를 생성해준다.
curl -X PUT "http://localhost:9200/my_index/_doc/3" -H 'Content-Type: application/json' -d'
{
"title": "Title1",
"author": "Author1"
}'
my_index 라는 인덱스가 없다면 auto_index 옵션으로 알아서 인덱스를 추가해준다.
{
"_index": "my_index",
"_type": "_doc",
"_id": "AUTOMATIC_ID_GENERATED",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
결과 메세지의 예시로 문서가 추가된 인덱스의 이름, 샤드, 문서ID 등을 보여준다.
Document는 내부적으로 불변객체로 관리되니 PUT 메서드로 기존 Document를 수정하거나, Scripting으로 기존 Document를 수정하더라도 내부적으로는 기존 문서를 삭제하고 새로운 문서를 추가하는 방식으로 작동한다.
Document가 어떤 인덱스에 저장될지는 사용자가 직접 지정할 수 있고, 어떤 샤드에 저장되고 업데이트되는지를 결정할 때는 해시와 라우팅 전략에 따라 다르다.
Document의 ID를 해싱해서 어떤 샤드에 저장될지를 결정한다. (아래는 기본 라우팅 공식)
shard = hash(document_id) % number_of_shards
샤드의 수는 인덱스가 생성될 때 설정되고 고정된다.
인덱스를 처음 생성할 때 적절한 수를 설정해야 하는데.. 라우팅 공식을 보면 알 수 있듯 샤드 수를 라우팅 공식에 그대로 사용하기에 샤드 수가 변경되면 기존의 해싱 결과도 달라지게 돼 문서를 제대로 매핑하기 위해 다시 인덱싱 해야하니 시스템에 부하를 줄 수 있으니 조심하자.
Document를 쓰거나 수정할 때 오류가 발생해 샤드에 문제가 생긴 경우 Replica 샤드가 Primary 샤드로 승격된다.
각 Document를 CRUD 할 때 시퀀스 번호가 할당되고, Primary 샤드가 문서를 처리할 때 마다 시퀀스 번호가 증가되는데, 이 시퀀스 번호를 사용해 Replica 샤드가 동일한 순서로 Primary 샤드의 상태를 반영한다.
이전까지는 시퀀스 번호와 함께 부여된 버전 관리 번호로 동시성을 처리했지만, 7.x버전부터는 Primary Term과 시퀀스 번호를 사용해 동시성을 처리한다.
클라이언트가 문서를 조회할 때, 결과에는 _primary_term 과 _seq_no 값이 포함되고 이 두 값을 업데이트 요청의 파라미터로 포함시키게 된다.
엘라스틱서치는 요청에 포함된 두 값이 현재 상태와 일치하는지 확인하고 성공 여부를 결정한다.
이전 방식과 비교했을 때 내부적으로 관리되는 값을 통해 충돌을 감지한다는 개념은 같지만, 충돌 감지에 사용하는 값만 다르다.
RDBMS처럼 락을 사용하지 않아 성능 저하와 데드락이 발생하지 않지만, 충돌이 발생한 경우 엘라스틱서치에서 충돌 해결 로직을 제공하지 않으니 엘라스틱서치와 연결된 백엔드 시스템에서 직접 충돌 로직을 구현해야 한다.
사용자의 요청이 몰릴 때, 100개의 요청을 엘라스틱서치 쿼리 100개로 날리는 대신 한 번의 bulk 쿼리로 처리할 수 있다.
POST /_bulk
{ "index": { "_index": "my_index", "_id": "1" } }
{ "field1": "value1", "field2": "value2" }
{ "update": { "_id": "1", "_index": "my_index" } }
{ "doc": { "field1": "new_value" } }
{ "delete": { "_index": "my_index", "_id": "2" } }
요청 엔드포인트는 /_bulk 또는 /index/_bulk 를 사용하고, 요청 페이로드는 NDJSON 형식을 사용한다.
JSON 객체가 한 줄로 작성되고 각 행은 개별 JSON 객체를 나타내며 개행 문자로 명령과 데이터를 구분한다. (마지막 행에도 개행 필요)
네트워크 오버헤드를 줄일 수 있고, 한 번에 여러 문서를 일괄 처리해 엔진이 내부적으로 최적화를 수행할 수 있다.
동시성 문제는 기존 쿼리에서와 같은 방식으로 Primary Term과 시퀀스 넘버를 통해 해결한다.
엘라스틱서치에서의 데이터 저장은 RDB보다는 NoSQL쪽에 더 가깝다.
RDB의 스키마처럼 미리 정해진 매핑이 있지만 비교적 유연하게 사용할 수 있고, 클러스터링과 샤딩으로 수평 확장이 쉽다.
설계 목표를 기억하자. 엘라스틱서치는 빠른 검색과 데이터 분석을 위해 도입된 기술이다.
데이터 복구나 장기적 저장보다는 성능과 확장성에 초점을 맞춰 개발된 기술이기에 트랜잭션을 완벽하게 지원하지 않고, 모든 데이터를 역색인 구조로 저장해 RDB NoSQL 데이터 저장소와 비교했을 때 더 많은 공간을 차지한다.
엘라스틱서치를 Primary Data Store로 사용하기 보다는, RDB나 NoSQL 기반 데이터베이스를 메인 데이터베이스로 두고 검색, 데이터 분석이 필요한 부분에만 엘라스틱서치를 활용하는 쪽으로 사용하자.
'Database > Elasticsearch' 카테고리의 다른 글
[Elasticsearch] 정보 검색과 검색 쿼리 (0) | 2024.12.11 |
---|---|
[Elasticsearch] 매핑과 데이터 타입 (0) | 2024.11.27 |
댓글
이 글 공유하기
다른 글
-
[Elasticsearch] 정보 검색과 검색 쿼리
[Elasticsearch] 정보 검색과 검색 쿼리
2024.12.11 -
[Elasticsearch] 매핑과 데이터 타입
[Elasticsearch] 매핑과 데이터 타입
2024.11.27