프로젝트를 하려면 elasticsearch에 대해서 얼추 아는게 아니라 정확히 알고있어야 하지 않겠어?
Elasticsearch?
- 검색 엔진
- 단순 텍스트 매칭 검색이 아닌 전문 검색(full-text search)이 가능하고 다양한 종류의 검색 쿼리를 지원한다.
- 검색 엔진이기 때문에 역색인을 사용하여 검색 속도도 매우 빠르다.
- 분산처리
- 데이터를 여러 노드에 분산 저장하며 검색이나 집계 작업 등을 수행할 때도 분산 처리를 지원한다.
- 고가용성 제공
- 클러스터를 구성하고 있는 일부 노드에 장애가 발생해도 복제본 데이터를 이용해 중단 없이 서비스를 지속할 수 있다.
- 엘라스틱서치는 다시 복제본을 만들어 복제본의 개수를 유지하면서 노드 간 데이터의 균형을 자동으로 맞춘다.
- 수평적 확장성
- 새로운 노드에 엘라스틱서치를 설치하여 클러스터에 참여시키는 것만으로도 확장이 된다.
- 새 노드에 데이터를 복제하거나 옮기는 작업도 엘라스틱서치가 자동 수행한다.
- JSON 기반의 REST API 제공
- 엘라스틱서치에 작업 요청을 보낼 때도 JSON 기반의 REST API를 사용한다.
- 데이터 안정성
- 데이터 색인 요청 후 200 OK를 받았다면 그 데이터는 확실히 디스크에 기록된다.
- 다양한 플러그인을 통한 기능 확장 지원
- 준실시간 검색
- 준실시간 검색(near real-time search)을 지원한다.
- 데이터를 색인하자마자 조회하는 것은 가능하지만, 데이터 색인 직후의 검색 요청은 성공하지 못할 가능성이 높다.
- 엘라스틱서치가 역색인을 구성하고 이 역색인으로부터 검색이 가능해지기까지 시간이 걸리기 때문이다.
- 기본 설정으로 엘라스틱서치를 운영할 경우 최대 1초 정도 시간이 걸린다.
- 트랙잭션이 지원되지 않음
- 엘라스틱서치는 RDBMS와는 다르게 트랜잭션을 지원하지 않는다.
- 사실상 조인을 지원하지 않음
- 기본적으로 조인을 염두에 두고 설계되지 않았다.
- join이라는 특별한 데이터 타입이 있지만 이는 굉장히 제한적인 상황을 위한 기능이며 성능도 떨어진다.
- 기본적으로 RDBMS와는 다르게 데이터를 비정규화해야 한다. 서비스와 데이터 설계에 있어 조인을 아예 사용하지 않는다고 생각해야 한다.
엘라스틱서치 구조
- 문서(document): 엘라스틱서치가 저장하고 색인을 생성하는 JSON 문서를 뜻한다.
- 인덱스(index): 문서를 모아 놓은 단위가 인덱스다. 클라이언트는 이 인덱스 단위로 엘라스틱서치에 검색을 요청하게 된다.
- 샤드(shard): 인덱스는 그 내용을 여러 샤드로 분리하여 분산 저장한다. 또한 엘라스틱서치는 고가용성을 제공하기위해 샤드의 내용을 복제해 둔다. 원본 역할을 담당하는 샤드를 주 샤드(primary shard)라고 하고 복제본을 복제본 샤드(replication shard)라고 한다.
- _id: 인덱스 내 문서에 부여되는 고유한 구분자다. 인덱스 이름과 _id의 조합은 엘라스틱서치 클러스터 내에서 고유하다.
- 타입: 엘라스틱서치는 과거에 하나의 인덱스 안에 있는 여러 문서를 묶어서 타입이라는 논리 단위로 나눴다.
엘라스틱서치 클러스터
- 노드(node): 엘라스틱서치 프로세스 하나가 노드 하나를 구성한다. 엘라스틱서치 노드 하나는 여러 개의 샤드를 가진다. 엘라스틱서치는 고가용성을 제공하기 위해 같은 종류의 샤드를 같은 노드에 배치하지 않는다.
- 클러스터(cluster): 노드 여러 개가 모여 하나의 클러스터를 구성한다.
- 노드의 역할: 엘라스틱서치의 노드는 데이터 노드, 마스터 노드, 조정 노드 등 여러 역할 중 하나 이상의 역할을 맡아 수행한다. 샤드를 보유하고 샤드에 실제 읽기와 쓰기 작업을 수행하는 노드를 데이터 노드(Data Node)라고 한다.
클러스터를 관리하는 중요한 역할을 하는 노드를 마스터 노드(Master Node)라고 한다. 마스터 노드는 마스터 후보 노드 중에서 1대가 선출된다. 클라이언트의 요청을 받아서 데이터 노드에 요청을 분배하고 클라이언트에게 응답을 돌려주는 노드는 조정 노드(Ingest Node) 라고 한다.
엘라스틱서치 내부 구조와 루씬
루씬(Lucene) Flush
문서 색인 요청이 들어오면 루씬은 문서를 분석해서 역색인을 생성한다. 최초 생성 자체는 메모리 버퍼에 들어간다.
문서 색인, 업데이트, 삭제 등의 작업이 수행되면 루씬은 이러한 변경들을 메모리에 들고 있다가 주기적으로 디스크에 flush 한다.
루씬 동작 원리
Refresh
@Override
protected ElasticsearchDirectoryReader refreshIfNeeded(ElasticsearchDirectoryReader referenceToRefresh) throws IOException {
return (ElasticsearchDirectoryReader) DirectoryReader.openIfChanged(referenceToRefresh);
}
엘라스틱 서치는 내부적으로 루씬의 DirectoryReader라는 클래스를 이용해 파일을 열고, 루씬의 색인에 접근할 수 있는 IndexReader 객체를 얻는다. 엘라스틱서치는 변경 내용을 검색에 반영하기 위해 루씬의 DirectoryReader.openIfChanged를 호출해 변경 사항이 적용된 새 IndexReader를 열어 준 뒤 기존 IndexReader를 안전하게 닫는다.
이 단계까지 온 데이터가 검색 대상이 된다. 이것을 Refresh 라고 한다.
- 색인 요청 처리 과정
- 문서 분석 (Document Analysis)
- 색인 요청이 들어오면 루씬은 먼저 문서를 Analyzer를 통해 처리한다.
- Analyzer는 텍스트를 토큰으로 나누고(예: 단어 단위로) 이를 정규화 한다. (소문자로 변환, 불용어 제거 등)
- 분석된 토큰은 역색인(Inverted Index) 구조로 저장된다.
- 메모리 버퍼에 저장 (RAM Buffer)
- 분석된 역색인은 메모리에 있는 RAM Buffer에 저장된다.
- 이 RAM Buffer는 빠른 처리를 위해 설계된 메모리 내 임시 저장소이다.
- 색인된 문서 정보뿐만 아니라 업데이트 및 삭제 요청도 포함된다.
- 문서 분석 (Document Analysis)
- Flush 메커니즘: 루씬은 메모리의 데이터를 디스크에 flush 하는 과정을 통해 영구 저장을 수행한다.
- RAM Buffer가 가득 찼을 때: RAM Buffer는 제한된 크기를 가지고, 설정된 크기를 초과하면 flush가 발생한다.
- Commit 요청이 있을 때: 명시적으로 커밋(commit)을 요청하면 flush가 수행된다.
- 주기적인 자동 flush: 루씬 내부 스케줄러에 의해 주기적으로 flush가 일어나기도 한다.
- Flush 과정 상세
- 세그먼트(Segment) 생성
- RAM Buffer에 있는 데이터를 디스크로 내리면서 세그먼트(Segment)라는 단위로 저장한다.
- 세그먼트(Segment)에 대해서 잠깐 알아보쟈.
- 디스크에 기록된 파일들이 모이면 세그먼트라는 단위가 된다.
- 이 세그먼트가 루씬의 검색 대상이다.
- 세그먼트 자체는 불면(immutable)인 데이터로 구성돼 있다.
- 새로운 문서가 들어오면 새 세그먼트가 생성된다.
- 기존 문서를 삭제하는 경우 삭제 플래그만 표시해 둔다.
- 기존 문서에 업데이트가 발생한 경우에는 삭제 플래스를 표시하고 새 세그먼트를 생성한다.
- 세그먼트(Segment)에 대해서 잠깐 알아보쟈.
- 세그먼트는 변경 불가능(Immutable)한 구조로 저장되며, 이를 통해 동시성 문제를 방지하고 읽기 성능을 최적화 한다.
- RAM Buffer에 있는 데이터를 디스크로 내리면서 세그먼트(Segment)라는 단위로 저장한다.
- 역색인 데이터 작성
- 세그먼트에는 문서 ID와 토큰 정보가 포함된 역색인 데이터가 저장된다.
- 문서의 원본 데이터(Stored Fields), 필드별 통계 정보(Term Vectors)등도 저장된다.
- 삭제된 문서 처리
- 문서 삭제 요청은 즉시 반영되지 않고, 삭제된 문서의 ID가 삭제목록(Deletion List)에 추가된다.
- flush시 삭제 목록도 함께 디스크로 내려간다.
- 세그먼트(Segment) 생성
- 색인 후 세그먼트 병합(Merge): Flush로 인해 여러 세그먼트가 생성되면, 디스크에 많은 세그먼트가 쌓인다.
- 병합의 목적
- 검색 성능 최적화: 세그먼트가 많을수록 검색 시 여러 파일을 읽어야 하므로 속도가 느려진다.
- 삭제된 문서 정리: 병합 시 삭제된 문서의 데이터를 실제로 제거한다.
- 병합 실행 과정
- 여러 작은 세그먼트를 하나의 큰 세그먼트로 통합한다.
- 병합된 세그먼트는 새로운 파일로 생성되고, 병합 전에 사용된 세그먼트는 삭제 된다.
- 병합의 목적
- 동작 원리의 장점
- 빠른 색인: RAM Buffer를 활용해 디스크 I/O를 최소화하므로 색인 작업이 매우 빠르다.
- 동시성 및 안정성: 세그먼트가 불면(Immutable)이므로 동시 읽기/쓰기가 안전하게 수행된다.
- 효율적인 디스크 사용: 삭제된 문서와 오래된 세그먼트를 병합 및 정리함으로써 디스크 공간을 효율적으로 관리 한다.
- Flush와 관련된 주요 설정
- index.buffer.size: RAM Buffer의 크기를 설정한다. 기본값은 일반적으로 16MB이다.
- index.merge.policy: 세그먼트 병합 정책을 설정한다.(예: TieredMergePolicy, LogByteSizeMergePolicy)
- index.commit.policy: Commit과 flush의 동작을 조정하는 정책이다.
루씬 Commit
루씬(Lucene)의 commit은 데이터를 디스크에 영구적으로 저장하고 읽기 가능 상태로 만드는 과정을 의미한다. 색인 작업 중에는 대부분의 데이터가 메모리(RAM)에 임시 저장되기 때문에, commit은 데이터를 디스크로 내려 영구 저장 및 안정성을 보장하는 중요한 단계 이다.
commit의 주요 역할
- 색인 데이터의 영구 저장
- 색인된 데이터를 RAM Buffer에서 디스크로 flush한 뒤, 디스크 상에서 이를 확정(commit)하여 안정적으로 저장한다.
- 세그먼트를 읽기 가능 상태로 전환
- 새롭게 생성된 세그먼트를 검색에서 사용할 수 있도록 공개한다.
- commit 이전에는 새로운 세그먼트가 검색에 반영되지 않는다.
- 트랜잭션 성격 제공
- commit은 데이터를 저장하는 단위 작업 처럼 동작하며, commit 이전의 변경사항은 취소될 수 있다.
- 예: commit이 이루어지지 않으면 루씬 인덱스는 이전 상태로 되돌아갈 수 있다.
commit 과정
- RAM Buffer flush
- 메모리(RAM)에 저장된 색인 데이터를 디스크로 flush하여 새로운 세그먼트를 생성한다.
- 세그먼트 정보 갱신(segments_N 파일)
- 루씬은 디스크에 저장된 세그먼트를 관리하기 위해 segments_N 파일을 사용한다.
- segments_N 파일에는 현재 인덱스에서 참조 중인 세그먼트 목록이 포함되어 있다.
- commit 시점에 새로운 세그먼트를 포함한 최신 세그먼트 목록이 기록된다.
- 파일 동기화(fsync)
- 디스크로 저장된 데이터가 손실되지 않도록, 파일 시스템 동기화(fsync)를 통해 모든 변경 사항을 디스크에 완전히 기록한다.
- 검색 가능 상태로 전환
- 새로 생성된 세그먼트가 검색 엔진에서 즉시 사용할 수 있도록 준비 된다.
commit과 flush의 차이점
기능 | flush | commit |
주요 목적 | 메모리에 있는 데이터를 디스크로 내리는 작업 | 디스크에 저장된 데이터를 영구 저장 및 읽기 가능 상태로 만듦 |
실행 주기 | RAM Buffer가 가득 차거나 설정된 조건이 충족될 때 자동 실행 | 명시적으로 호출해야 실행 (IndexWriter.commit()) |
결과 | 새로운 세그먼트 생성, 검색에는 즉시 반영되지 않음 | 새로운 세그먼트가 검색 엔진에서 사용 가능 해짐 |
루씬 인덱스와 엘라스틱서치 인덱스
- 여러 세그먼트가 모이면 하나의 루씬 인덱스가 된다.
- 루씬은 이 인덱스 내에서만 검색이 가능하다.
- 엘라스틱서치 샤드는 이 루씬 인덱스 하나를 래핑(wrap)한 단위다.
- 엘라스틱서치 샤드 여러개가 모이면 엘라스틱서치 인덱스가 된다.
- 엘라스틱서치 레벨에서는 여러 샤드에 있는 문서를 모두 검색할 수 있다.
- 새 문서가 들어오면 해당 내용을 라우팅하여 여러 샤드에 분산시켜 저장, 색인 한다.
- 이러한 구조를 통해 루씬 레벨에서는 불가능한 분산 검색을 엘라스틱서치 레벨에서는 가능하게 만들었다.
Translog (Transaction Log)
Translog(Transaction Log)는 엘라스틱서치에서 데이터의 무결성과 복구를 보장하기 위한 중요한 구성 요소이다. Translog는 색인 작업이 실제로 디스크에 반영되기 전에, 일시적으로 모든 작업을 기록해두는 로그 파일이다. 이 로그는 시스템 장애 발생 시 데이터 손실을 방지하고, 색인 작업의 안정성을 높이는 역할을 한다.
Translog의 역할
- 데이터 무결성 보장
- 색인 작업이 디스크에 기록되기 전에, Translog에 해당 작업을 기록한다.
- 시스템 장애(예: 서버 크래시) 발생 시, 작업이 완료되지 않거나 데이터가 손실되는 것을 방지한다.
- 장애 복구(Crash Recovery)
- 만약 엘라스틱서치가 비정상적으로 종료되면, 시스템이 재시작할 때 Translog에 기록된 내용이 기반이 되어 누락된 색인 작업을 복구한다.
- Translog는 데이터 손실을 방지하기 위해 최근에 변경된 데이터를 디스크에 반영하기 전에 기록된 모든 작업을 보유한다.
- 성능 최적화(Write-Behind 방식)
- 색인 작업은 바로 디스크에 반영되는 것이 아니라, Translog에 기록된 후 주기적으로 실제 색인 파일(세그먼트)에 반영된다.
- 이 방식은 디스크에 직접 쓰는 빈도를 줄여 성능을 향상 시킨다.
Translog의 동작 원리
- 색인 작업 기록
- 문서가 색인되거나 업데이트되면, 이 작업은 즉시 Translog에 기록 된다.
- 이후 디스크에 있는 실제 세그먼트 파일에 반영된다.
- 주기적인 Flush와 Commit
- 주기적으로 flush 작업이 실행되어, translog에 기록된 작업이 실제 루씬 인덱스에 반영된다.
- flush 시점에, translog에 있는 내용은 디스크로 저장되고, 해당 작업이 완료되었음을 의미한다.
- 커밋(commit) 후에는, translog에 기록된 내용이 삭제되거나 정리된다.
- Translog 파일 관리
- Translog는 시간이 지나면서 크기가 커질 수 있기 때문에, 주기적인 정리(cleaning)가 필요하다.
- flush 작업 후, 오래된 translog 파일은 자동으로 삭제된다.
Translog의 구성
파일 경로: Translog 파일은 각 샤드별로 관리되며, 기본적으로 translog 디렉터리 안에 저장된다.
파일 이름: Translog 파일은 보통 translog-1, translog-2, ... 처럼 번호가 붙어 관리 된다.
Translog 설정 (엘라스틱서치)
엘라스틱서치에서는 Translog의 동작 방식을 다음과 같이 설정할 수 있다.
- index.translog.durability
- request: 각 색인 요청이 디스크에 기록될 때마다 trnaslog에 기록된다. 더 높은 내구성을 보장하지만 성능에 영향을 미칠 수 있다.
- async: 색인 요청이 디스크에 기록되기 전에 바로 translog에 기록되고, 성능이 향상되지만 시스템 장애 시 데이터 손실이 발샐할 수 있다.
- index.translog.sync_interval
- Translog가 주기적으로 디스크에 반영되는 간격을 설정한다.
- 기본값은 5초이다.
- index.translog.retention.age
- Translog 파일을 보유할 최대 시간을 설정한다.
- 이 값을 조정하여 Translog 파일이 너무 오래 보존되지 않도록 할 수 있다.
설정파일
Elasticsearch 설정 파일
config/elasticsearch.yml
클러스터의 정보, 각 노드의 정보, 데이터 파일을 저장할 경로 등 대부분의 핵심적인 엘라스틱서치 설정이 이파일에 위치한다.
cluster.name: "docker-cluster"
node.name: lima-node01
network.host: 0.0.0.0
path:
data: /my/path/to/elasticsearch/data
logs: /my/path/to/elasticsearch/logs
discovery.type: "single-node"
xpack.security.enabled: false
- cluster.name: 클러스터의 이름을 지정한다. 이 이름을 기준으로 클러스터가 구성된다.
- node.name: 노드의 이름을 지정한다. 노드의 이름은 클러스터 내에서 고유해야 한다.
- network.host: 엘라스틱서치와 바인딩될 네트워크 주소를 입력한다. 나는 테스트를 위하여 단일 노드의 개발 모드로 설정할꺼기 때문에 127.0.0.1로 지정하였다. 클러스터를 실제 구성할 때는 루프백이 아닌 값을 지정해야 한다.
- path.logs: 로그를 저장할 경로를 지정한다.
- path.data: 엘라스틱서치의 데이터를 저장할 경로를 지정한다. 엘라스틱서치가 저장할 문서 내용이나 역색인, 클러스터 설정 등 다양한 데이터를 저장한다. 둘 이상의 경로를 지정할 수도 있다.
- discovery.type: 이 값을 single-node로 지정하면 엘라스틱서치는 단일 노드의 개발 모드로 동작하며 다른 노드의 클러스터 참가 신청을 받아들이지 않는다.
- xpack.security.enabled: 엘라스틱서치에서 제공하는 보안 관련 기능을 사용할 것인지를 지정한다. 이 값을 true로 설정하면 노드 간의 TLS 통신 적용, 유저와 역할을 기반으로 한 인증과 권한 분리, 감사 증적 등의 기능이 제공된다.
config/jvm.options
힙 사이즈나 GC 옵션, 힙 덤프 등 JVM설정을 지정한다.
################################################################
## IMPORTANT: JVM heap size
################################################################
##
## The heap size is automatically configured by Elasticsearch
## based on the available memory in your system and the roles
## each node is configured to fulfill. If specifying heap is
## required, it should be done through a file in jvm.options.d,
## which should be named with .options suffix, and the min and
## max should be set to the same value. For example, to set the
## heap to 4 GB, create a new file in the jvm.options.d
## directory containing these lines:
## 이부분이 heap 메모리를 설정하는 부분! 현재는 주석처리 되어있다!
## -Xms4g
## -Xmx4g
##
## See https://www.elastic.co/guide/en/elasticsearch/reference/8.14/heap-size.html
## for more information
##
################################################################
-XX:+UseG1GC
## JVM temporary directory
-Djava.io.tmpdir=${ES_TMPDIR}
# Leverages accelerated vector hardware instructions; removing this may
# result in less optimal vector performance
20-:--add-modules=jdk.incubator.vector
## heap dumps
# generate a heap dump when an allocation from the Java heap fails; heap dumps
# are created in the working directory of the JVM unless an alternative path is
# specified
-XX:+HeapDumpOnOutOfMemoryError
# exit right after heap dump on out of memory error
-XX:+ExitOnOutOfMemoryError
# specify an alternative path for heap dumps; ensure the directory exists and
# has sufficient space
-XX:HeapDumpPath=data
# specify an alternative path for JVM fatal error logs
-XX:ErrorFile=logs/hs_err_pid%p.log
## GC logging
-Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,level,pid,tags:filecount=32,filesize=64m
키바나 설정 파일
kibana.yml
# Default Kibana configuration for docker target
server.host: "0.0.0.0"
server.shutdownTimeout: "5s"
elasticsearch.hosts: [ "http://elasticsearch:9200" ]
monitoring.ui.container.elasticsearch.enabled: true
- server.port: 키바나 백엔드 서버의 포트를 지정한다. (내 파일에는 server.port가 없었다)
- server.host: 키바나 백엔드 서버의 호스트를 지정한다. 외부에서 접근할 수 있게 하려면 서버의 IP 주소나 DNS 이름으로 지정하면 된다.
- server.publicBaseUrl: 사용자가 외부에서 어떤 URL로 키바나에 접근하는지를 지정한다. / 문자로 끝나면 안된다.
- elasticsearch.hosts: 엘라스틱서치 쿼리를 전달할 엘라스틱서치의 호스트 URL 목록을 지정한다. 이곳에 지정된 호스트는 모두 같은 클러스터 소속이어야 한다.
와 내용이 정말 많다...
** 그냥 하루하루 개인 공부한 것을 끄적 거리는 공간입니다.
이곳 저곳에서 구글링한 것과 강의 들은 내용이 정리가 되었습니다.
그림들은 그림밑에 출처표시를 해놓았습니다.
문제가 될시 말씀해주시면 해당 부분은 삭제 하도록하겠습니다. **
'public void static main > Book' 카테고리의 다른 글
[토비의스프링] 2장-테스트 (1) | 2025.01.29 |
---|---|
[토비의스프링] 1장-오브젝트와 의존관계 (1) | 2025.01.28 |
[대규모 시스템 설계 기초] 3장, 4장 (1) | 2025.01.08 |
[대규모 시스템 설계 기초] 1장, 2장 (1) | 2025.01.06 |
[JVM 밑바닥] 7장 클래스 로딩 메커니즘 (1) | 2024.11.17 |
댓글