Elastic Search

[Elasticsearch] 검색

꾸준함. 2024. 6. 28. 22:03

Elasticsearch Cluster 구조

  • Elasticsearch는 분산형 검색 엔진으로써 데이터를 여러 노드에 분산해서 저장
  • 각 노드는 여러 개의 인덱스를 가질 수 있으며
  • 각 인덱스는 여러 개의 샤드로 구성
  • 샤드는 primary 샤드와 replica 샤드로 구성
  • 샤드는 Apache Lucene 인스턴스로써 독립적으로 작동하며 각 샤드는 데이터의 일부분을 가지고 있음

 

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

 

검색 요청의 처리 과정

  • Elasticsearch의 검색 요청은 크게 두 단계로 나뉨
    • Query phase
    • Fetch phase

 

  • 이 때문에 query then fetch라고도 불림

 

1. Query Phase

  • 검색 요청이 수행되어야 하는 샤드를 결정하고 각 샤드에서 검색을 수행하는 단계
    • client 요청은 L4 등을 통해 특정 노드로 라우팅
    • 요청을 받은 노드는 인덱스의 데이터를 가지고 있는 전체 샤드에 broadcasting
    • 요청을 받은 샤드는 각 샤드 내에서 쿼리를 수행
    • 각 샤드는 매칭이 되는 문서의 id와 score 값을 반환
    • coordinating node는 각 샤드로부터 반환된 결과를 모아 가장 점수가 높은 문서들을 선별

 

2. Fetch Phase

  • Query Phase에서 선별된 문서들의 실제 내용을 가져오는 단계
    • coordinating node는 Query Phase에서 선별한 내용을 기반으로 각 샤드에게 해당 문서들의 실제 내용을 요청
    • 각 샤드는 요청받은 문서의 내용을 반환
    • coordinating node는 이들 문서를 모아 최종적인 검색 결과를 생성

 

https://blog.insightdatascience.com/anatomy-of-an-elasticsearch-cluster-part-i-7ac9a13b05db

 

부연 설명

  • 두 단계로 분리함에 따라 Query Phase에서는 가능한 한 적은 양의 데이터만 처리하여 네트워크 트래픽과 메모리 사용량을 최소화
    • 실제로 필요한 문서의 내용만 Fetch Phase에서 가져와서 처리 효율을 높임

 

  • score 계산은 개별 샤드에서 수행
    • 하지만 일반적인 BM25 알고리즘에서 보면 TF는 문서 내에서 계산이 가능하지만 IDF는 전체 문서의 정보를 알아야지만 계산이 가능
    • 그러나 IDF를 글로벌하게 계산하면 리소스가 많이 들기 때문에 일반적으로는 수행하지 않음
    • 그리고 데이터가 충분하고 문서가 샤드마다 잘 분산이 되어 있을 경우 로컬에서도 거의 동일한 값으로 계산이 되기 때문에 문제없음
    • 성능 저하를 유발하더라도 매우 정확하게 계산해야 할 경우 글로벌하게 계산할 수 있는 옵션 제공

 

GET <인덱스>/_search?search_type=dfs_query_then_fetch

 

 

주의할 점

  • query then fetch 과정에서 from, size를 사용해서 검색을 하는 경우 뒷 페이지를 검색할수록 각 샤드에서 가져오는 데이터의 양이 많아질 수밖에 없으며 각 샤드에서 가져올 문서들을 정렬하는데도 리소스가 많이 들어감
    • 샤드 개수 * (from + size)

 

검색 요청에서 샤드 특징 및 최적 샤드 갯수

 

1. Primary & Replica Shard

  • Primary 샤드와 해당하는 Replica 샤드는 같은 데이터를 가지고 있고 이를 통해 데이터의 안전성 및 가용성을 보장
  • 검색 요청 처리 시 Elasticsearch는 기본적으로 사용 가능한 모든 Primary와 Replica 샤드 중에서 하나를 선택하여 요청을 처리
    • Round Robin 방식으로 이루어지며 이를 통해 검색 요청의 부하를 분산

 

1.1 Adaptive Replica Selection(ARS)

  • Round Robin 방식의 허점을 보완하는 방식
  • 기존의 라운드 로빈 방식과 비교하여, ARS는 각 요청에 대해 더 나은 복제본을 선택함으로써 성능을 최적화
  • ARS는 단순한 라운드 로빈 방식 대신, 각 복제본의 현재 상태와 성능을 고려하여 더 적합한 복제본을 선택하며 주요 요소는 다음과 같음
    • 응답 시간(latency): 각 복제본의 평균 응답 시간을 모니터링하며 응답 시간이 빠른 복제본을 우선적으로 선택
    • 큐 길이(queue length): 각 복제본이 처리 중인 요청 수를 확인하며 요청이 덜 쌓인 복제본을 선택하여 과부하를 방지
    • 노드 상태: 복제본이 있는 노드의 상태(CPU 사용률, 메모리 상태)를 고려

 

2. 최적 샤드 갯수

  • 샤드 갯수 선정 시 여러 요소를 고려해야 함
    • 검색 목적
    • 문서 수
    • 검색량
    • aggregation 사용 여부
    • 인덱스 갯수
    • etc.

 

2.1 Primary 샤드 갯수 선정

  • 자원을 효율적으로 쓰기 위해서는 노는 노드가 없도록 샤드 개수를 분배하는 것이 필요
  • 하나의 샤드가 처리 가능한 max 사이즈 산정
    • 429 에러가 발생하는 것을 확인해 보면서 색인이 얼마나 가능한지 파악

 

  • 보통 검색 서비스 샤드는 20GB, 로깅 플랫폼 샤드는 50GB 권장
    • 로깅 플랫폼의 경우 인덱스 사이즈가 커지면 롤링을 통해 인덱스 새로 만드는 것이 답일 수도 있음

 

2.2 Replica 샤드 개수 선정

  • 우선 1개로 시작
  • 많으면 안정화와 검색 성능에 도움이 되지만 너무 많을 경우 색인 성능이 저하되고 디스크 사용량이 많아짐

 

score가 계산되는 과정과 순서

 

1. score 값에 영향을 주는 요소

  • TF-IDF 점수: 검색 쿼리에서의 각 단어가 문서에 얼마나 자주 등장하는지(TF), 그리고 해당 단어가 전체 문서 집합에서 얼마나 흔한지(IDF)를 고려하여 계산
  • BM25 점수: TF-IDF를 개선한 알고리즘으로 각 단어의 중요성뿐만 아니라 문서의 길이도 고려
  • 부스팅: 특정 필드에 대한 검색 결과를 더 중요하게 여기도록 부스팅 적용 가능
  • bool 쿼리: 쿼리 내 must, should, filter, must_not 등의 절을 사용하여 검색 조건을 구조화할 수 있고 각 절을 각기 다른 방식으로 점수 계산에 영향을 줌
  • script/function score: 사용자 정의 script나 function 사용하여 복잡한 랭킹 로직을 적용할 수 있으며 이를 통해 비즈니스 로직에 따라 문서의 랭킹 조정 가능
  • 필드의 종류에 따라 다른 방식으로 점수 계산
    • keyword 타입은 정확한 일치를 기준으로 점수 계산

 

2. score가 계산되는 순서

 

2.1 필터 로직

  • 필터는 문서가 조건을 만족하는지 판별하는 역할
  • 필터는 결과에 대한 점수를 계산하지 않으므로 쿼리보다 빠르게 실행하기 때문에 가능한 많은 필터를 사용하여 검색 범위를 좁히는 것을 권장
  • 하지만 쿼리와 필터의 복잡성, 데이터의 분포, 샤드의 상태에 따라 실제 실행 순서는 달라질 수도 있음

 

2.2 비용이 낮은 쿼리/필터가 먼저 실행

  • Elasticsearch는 cost() 메서드를 통해 각 쿼리와 필터의 비용을 추정하고 비용이 낮은 쿼리/필터부터 먼저 실행

 

2.3 post_filter

  • 이름에서 유추할 수 있다시피 검색 결과가 반환되기 전 마지막으로 적용되는 필터
  • 결과의 score에 영향을 주지 않으며 결과 집합을 추가로 필터링하는 데 사용

 

* 실제 실행 순서는 profile API를 통해 확인 가능

 

캐싱

  • Elasticsearch에서 캐싱을 통해 검색 성능을 향상할 수 있음

 

1. 페이지 캐시

  • OS가 파일 I/O의 성능 향상을 위해 사용하는 메모리 영역
  • 한 번 읽은 파일의 내용을 페이지 캐시에 저장 후 다시 동일한 파일의 접근이 발생할 때 디스크에서 읽지 않고 페이지 캐시에서 빠르게 읽어올 수 있음
  • Elasticsearch의 메모리 사용량을 시스템의 총메모리의 절반 이하로 유지하는 주요 이유 중 하나는 페이지 캐시를 효과적으로 사용하기 위해
    • Elasticsearch를 설정할 때, JVM 힙 메모리(heap memory)의 크기를 시스템의 총 메모리의 절반 이하로 설정하는 것을 권장
    • OS가 페이지 캐시로 사용할 수 있는 메모리를 충분히 확보하려면, Elasticsearch가 시스템 메모리의 절반 이하를 사용해야 함
    • Elasticsearch가 과도하게 메모리를 사용하면, 운영 체제가 메모리 부족 상태에 빠질 수 있으며 이 경우 성능 저하나 시스템 불안정 유발할 수 있음
    • JVM 힙 메모리가 너무 크면, GC가 더 자주 실행되거나 더 오래 걸릴 수 있음

 

  • preload: 인덱스 설정을 통해 es 인덱스 데이터를 페이지 캐시에 미리 로딩되도록 Elasticsearch를 구성 가능
    • Elasticsearch는 Lucene을 기반으로 하며, Lucene 인덱스 파일은 여러 세그먼트(segment)로 구성
    • 각 세그먼트는 독립적인 파일 집합으로, 새로운 문서가 추가되거나 기존 문서가 삭제될 때마다 생성
    • 프리로드는 이 세그먼트 파일을 미리 메모리에 로드하여 검색 성능을 최적화
    • Elasticsearch에서 인덱스 프리로드는 주로 index.store.preload 설정을 통해 관리되며 해당 설정은 특정 파일 확장자를 지정하여 해당 파일들을 프리로드하도록 지시


 

부연 설명

  • nvd 파일은 필드 norms 데이터를 저장되며 norms는 문서 점수 계산에 사용되는 값으로, 특정 필드의 길이나 빈도 등을 정규화(normalization)한 값을 포함
    • 이를 프리로드하면 문서 점수 계산 시 디스크 접근을 줄이고 성능을 향상할 수 있음

 

  • dvd 파일은 도큐먼트 값(doc values)을 저장하며 도큐먼트 값은 필드 정렬, 집계(aggregation), 스크립팅 등에 사용되며, 주로 검색 시점이 아닌 인덱싱 시점에 생성
    • 해당 데이터를 프리로드하면 이러한 작업들이 더 빠르게 수행될 수 있음

 

2. 샤드 레벨 캐시

  • aggregation 동작에서 캐싱 발생
    • aggregation 작업은 Elasticsearch에서 매우 자주 사용되는 기능으로, 데이터의 요약 정보를 제공하는 데 사용
    • aggregation은 일반적으로 대규모 데이터를 처리하므로, 효율적인 캐싱 메커니즘이 필요 
    • ex) 날짜별 로그 집계를 하는 중 한 번은 2024-06-23 ~ 2024-06-28 구간을 집계하고 다음번에는 2024-06-16 ~ 2024-06-25 구간을 집계할 경우 기간이 겹치는 2024-06-23 ~ 2024-06-25 구간은 겹치기 때문에 해당 기간의 집계 정보가 캐시에 있을 경우 활용 가능

 

3. 노드 쿼리 캐시

  • Elasticsearch 클러스터 내 각 노드에 존재하는 캐시로, 쿼리의 실행 결과를 저장하여 동일한 쿼리가 다시 실행될 때 캐시된 결과를 반환
  • 서로 다른 쿼리 간 재사용되는 데이터를 캐시
  • 쿼리 내 filter context(filter, must_not) 수행 시 동작
    • 쿼리는 매번 달라지지만 필터는 동일할 경우 활용하기 좋음

 

  • LRU(Least Recently Used) 방식으로 작동하여 가장 오랫동안 사용되지 않은 데이터를 캐시 교체 대상으로 지정
  • 각 문서를 나타내는 배열, 특정 필터에 대한 전용 bitset을 만들어 특정 문서가 해당되면 1, 아니면 0으로 표시
  • 기본적으로 캐시 개수는 10,000개를 가지며 힙 메모리의 10%까지만 캐시로 들고 있음
    • 세그먼트 단위로 세그먼트 당 10,000개 이상의 문서를 가지고 해당 샤드의 전체 문서의 3% 이상을 차지하고 있어야 함
    • 또한 한 번의 호출로 인해 캐시가 채워지지 않도록 캐싱에 적합한 최소 빈도에 대한 조건 존재
      • 기본적으로는 5번 이상 호출 시 캐싱
      • 비용이 상대적으로 비싼 쿼리는 두 번 이상 호출 시 캐싱
      • indices.queries.cache.size(static) 통해 명시적으로 지정 가능

 

  • API를 통해 클러스터별, 노드별, 그리고 인덱스별 캐시 확인 가능
    • 클러스터별: GET _cluster/stats?filter_path=indices.query_cache API
    • 노드별: GET /_nodes/stats/indices/query_cache?human API
    • 인덱스별: GET {인덱스}/_stats?filter_path=indices.*.total_query_cache API

 

참고

  • 패스트 캠퍼스 -  고성능 검색 엔진 구축으로 한 번에 끝내는 Elasticsearch
반응형

'Elastic Search' 카테고리의 다른 글

[Elasticsearch] 최적화를 위한 시스템 설정  (0) 2024.07.13
[Elasticsearch] 색인  (0) 2024.06.28
[Elasticsearch] ILM  (0) 2024.06.27
[ELK] Beats 정리  (0) 2024.06.25
[ELK] Logstash 정리  (0) 2024.06.25