Concurrent segment search
출처: 문서
본문
동시 세그먼트 검색
쿼리 단계에서 세그먼트를 병렬로 검색하려면 동시 세그먼트 검색(concurrent segment search)을 사용하세요. 동시 세그먼트 검색이 검색 지연 시간을 개선하는 사례는 다음과 같아요:
- 오래 실행되는 요청을 보낼 때, 예를 들어 애그리게이션이나 큰 범위가 포함된 요청
- 성능을 개선하기 위해 세그먼트를 단일 세그먼트로 force-merge하는 것의 대안으로 사용할 때
배경
OpenSearch에서 각 검색 요청은 scatter-gather 프로토콜을 따라요. 코디네이팅 노드는 검색 요청을 받고, 이 요청을 처리하는 데 필요한 샤드를 평가한 다음, 각 샤드에 샤드 수준 검색 요청을 보내요. 요청을 받은 각 샤드는 Lucene을 사용해 요청을 로컬에서 실행하고 결과를 반환해요. 코디네이팅 노드는 모든 샤드에서 받은 응답을 병합해서 검색 응답을 클라이언트에 다시 보내요. 필요에 따라, 클라이언트가 응답의 일부로 특정 문서 필드나 문서 전체를 요청한 경우에는 코디네이팅 노드가 최종 결과를 클라이언트에 반환하기 전에 fetch 단계를 수행할 수도 있어요.
세그먼트 동시 검색
동시 세그먼트 검색이 없으면, Lucene은 쿼리 단계에서 각 샤드의 모든 세그먼트에 걸쳐 요청을 순차적으로 실행해요. 그런 다음 쿼리 단계에서 검색 요청에 대한 상위 히트(top hits)를 수집해요. 동시 세그먼트 검색을 사용하면 각 샤드 수준 요청이 쿼리 단계에서 세그먼트를 병렬로 검색해요. 각 샤드에 대해 세그먼트는 여러 개의 _slice_로 나뉘어요. 각 slice는 별도의 스레드에서 병렬로 실행될 수 있는 작업 단위이므로, slice 수가 샤드 수준 요청의 최대 병렬 처리 수준을 결정해요. 모든 slice가 작업을 마치면 Lucene은 slice들에 대해 reduce 연산을 수행해서 이들을 병합하고 해당 샤드 수준 요청의 최종 결과를 만들어요. slice는 새로운 index_searcher 스레드 풀을 사용해 실행되며, 이는 샤드 수준 요청을 처리하는 search 스레드 풀과는 다른 풀이에요.
인덱스 또는 클러스터 수준에서 동시 세그먼트 검색 활성화하기
OpenSearch 3.0 버전부터 동시 세그먼트 검색은 기본적으로 클러스터 수준에서 활성화돼요. 기본 동시 세그먼트 검색 모드는 auto예요. 업그레이드 후에는 애그리게이션 워크로드에서 CPU 사용률이 증가할 수 있어요. 최적의 성능을 유지하려면 클러스터의 리소스 사용량을 모니터링하고 필요에 따라 인프라 용량을 조정하는 것을 권장해요.
{: .important}
클러스터에서 동시 세그먼트 검색을 구성하려면 search.concurrent_segment_search.mode 설정을 사용하세요. 기존의 search.concurrent_segment_search.enabled 설정은 새 설정으로 대체되어 향후 버전 릴리스에서 지원 중단될 예정이에요.
동시 세그먼트 검색은 두 가지 수준에서 활성화할 수 있어요:
- 클러스터 수준
- 인덱스 수준
인덱스 수준 설정이 클러스터 수준 설정보다 우선해요. 따라서 클러스터 설정이 활성화되어 있더라도 인덱스 설정이 비활성화되어 있으면 해당 인덱스에서는 동시 세그먼트 검색이 비활성화돼요. 이 때문에 인덱스 수준 설정은 설정에 구성된 기본값과 무관하게, 명시적으로 설정되지 않는 한 평가되지 않아요. Index Settings API를 호출할 때 ?include_defaults 쿼리 파라미터를 생략하면 인덱스 수준 설정의 현재 값을 조회할 수 있어요.
{: .note}
클러스터 수준과 인덱스 수준의 search.concurrent_segment_search.mode 설정은 다음 값들을 받을 수 있어요:
-
auto(기본값): 이 모드에서는 OpenSearch가 플러그인 방식의 _concurrent search decider_를 사용해, 쿼리 평가 결과와 요청에 애그리게이션이 있는지 여부를 바탕으로 검색 요청에 동시 경로를 사용할지 순차 경로를 사용할지 결정해요. 기본적으로 어떤 플러그인도 decider를 구성하지 않았다면, 요청에 애그리게이션이 있는지 여부를 기준으로 동시 검색 사용 여부가 결정돼요. 플러그인 방식 decider의 동작 방식에 대한 자세한 내용은 Pluggable concurrent search deciders를 참조하세요. -
all: 모든 검색 요청에 대해 동시 세그먼트 검색을 활성화해요. 이는search.concurrent_segment_search.enabled를true로 설정하는 것과 같아요. -
none: 모든 검색 요청에 대해 동시 세그먼트 검색을 비활성화해서 사실상 이 기능을 꺼요. 이는search.concurrent_segment_search.enabled를false로 설정하는 것과 같아요.
클러스터의 모든 인덱스에 걸친 모든 검색 요청에 대해 동시 세그먼트 검색을 활성화하려면 다음 요청을 보내세요:
PUT _cluster/settings
{
"persistent":{
"search.concurrent_segment_search.mode": "all"
}
}
{% include copy-curl.html %}
특정 인덱스의 모든 검색 요청에 대해 동시 세그먼트 검색을 활성화하려면 엔드포인트에 인덱스 이름을 지정하세요:
PUT {index-name}/_settings
{
"index.search.concurrent_segment_search.mode": "all"
}
{% include copy-curl.html %}
다음과 같이 기존의 search.concurrent_segment_search.enabled 설정을 계속 사용해서 클러스터의 모든 인덱스에 대해 동시 세그먼트 검색을 활성화할 수도 있어요:
PUT _cluster/settings
{
"persistent":{
"search.concurrent_segment_search.enabled": true
}
}
{% include copy-curl.html %}
특정 인덱스에 대해 동시 세그먼트 검색을 활성화하려면 엔드포인트에 인덱스 이름을 지정하세요:
PUT {index-name}/_settings
{
"index.search.concurrent_segment_search.enabled": true
}
{% include copy-curl.html %}
클러스터에서 동시 세그먼트 검색 활성화 여부를 평가할 때는 search.concurrent_segment_search.mode 설정이 search.concurrent_segment_search.enabled 설정보다 우선해요.
search.concurrent_segment_search.mode 설정이 명시적으로 설정되어 있지 않으면, 동시 세그먼트 검색 활성화 여부를 결정하기 위해 search.concurrent_segment_search.enabled 설정이 평가돼요.
이전 버전에서 기존의 search.concurrent_segment_search.enabled 설정을 지정하고 있던 클러스터를 업그레이드하는 경우에도 이 설정은 계속 적용돼요. 하지만 search.concurrent_segment_search.mode가 한 번 설정되면 이전 설정을 재정의하며, 지정된 모드에 따라 동시 검색을 활성화하거나 비활성화해요.
search.concurrent_segment_search.mode를 구성한 후에는 클러스터에서 search.concurrent_segment_search.enabled를 null로 설정하는 것을 권장해요:
PUT _cluster/settings
{
"persistent":{
"search.concurrent_segment_search.enabled": null
}
}
{% include copy-curl.html %}
특정 인덱스에 대해 기존 설정을 비활성화하려면 엔드포인트에 인덱스 이름을 지정하세요:
PUT {index-name}/_settings
{
"index.search.concurrent_segment_search.enabled": null
}
{% include copy-curl.html %}
슬라이싱 메커니즘
세그먼트를 slice에 할당하는 메커니즘은 두 가지 중에서 선택할 수 있어요: 기본값인 max slice count 메커니즘 또는 Lucene 메커니즘.
max slice count 메커니즘
max slice count 메커니즘은 동적으로 구성 가능한 최대 slice 수를 사용하고, 라운드 로빈 방식으로 세그먼트를 여러 slice에 나눠 주는 슬라이싱 메커니즘이에요. 이는 이미 최상위 샤드 요청이 너무 많아서, slice 간 경쟁을 줄이기 위해 요청별 slice 수를 제한하고 싶을 때 유용해요.
OpenSearch 3.0 버전부터 동시 세그먼트 검색은 기본적으로 max slice count 메커니즘을 사용해요. max slice count는 클러스터 시작 시점에 Math.max(1, Math.min(Runtime.getRuntime().availableProcessors() / 2, 4)) 공식을 사용해 계산돼요. 클러스터 수준 또는 인덱스 수준에서 max_slice_count 파라미터를 명시적으로 설정하면 이 값을 재정의할 수 있어요. max_slice_count 업데이트에 대한 자세한 내용은 슬라이싱 메커니즘 설정을 참조하세요. 기본 계산값으로 되돌리려면 max_slice_count를 null로 설정하세요.
Lucene 메커니즘
Lucene 메커니즘은 max slice count 메커니즘의 대안이에요. 기본적으로 Lucene은 샤드의 각 slice에 최대 250K 문서 또는 5개 세그먼트(먼저 충족되는 쪽)를 할당해요. 예를 들어 세그먼트 11개를 가진 샤드를 생각해 보세요. 처음 5개 세그먼트는 각각 250K 문서를 가지고, 그다음 6개 세그먼트는 각각 20K 문서를 가져요. 처음 5개 세그먼트는 각각 slice에 허용되는 최대 문서 수를 담고 있기 때문에 하나씩 각각 1개의 slice에 할당돼요. 그다음 5개 세그먼트는 slice에 허용되는 최대 세그먼트 수 때문에 또 다른 하나의 slice에 모두 함께 할당돼요. 11번째 slice는 별도의 slice에 할당돼요.
슬라이싱 메커니즘 설정
search.concurrent.max_slice_count 설정을 업데이트하면 클러스터 수준 또는 인덱스 수준에서 슬라이싱 메커니즘을 설정할 수 있어요.
클러스터 수준과 인덱스 수준의 search.concurrent.max_slice_count 설정은 다음과 같은 유효한 값을 가질 수 있어요:
- 양의 정수: max target slice count 메커니즘을 사용해요. 일반적으로 2에서 8 사이의 값이면 충분해요.
0: Lucene 메커니즘을 사용해요.
클러스터의 모든 인덱스에 대해 slice 수를 구성하려면 다음 동적 클러스터 설정을 사용하세요:
PUT _cluster/settings
{
"persistent":{
"search.concurrent.max_slice_count": 2
}
}
{% include copy-curl.html %}
특정 인덱스에 대해 slice 수를 구성하려면 엔드포인트에 인덱스 이름을 지정하세요:
PUT {index-name}/_settings
{
"index.search.concurrent.max_slice_count": 2
}
{% include copy-curl.html %}
일반 가이드라인
동시 세그먼트 검색은 CPU나 JVM 힙 같은 더 많은 리소스를 소비하는 대가로 검색 요청의 성능을 개선하는 데 도움이 돼요. 클러스터가 동시 세그먼트 검색에 맞게 올바르게 사이징되었는지 파악하려면 워크로드를 테스트해 보는 것이 중요해요. 다음 동시 세그먼트 검색 가이드라인을 따르는 것을 권장해요:
- slice 수를 2로 시작해서 워크로드의 성능을 측정해 보세요. 리소스 사용률이 권장 값을 초과하면 클러스터 확장을 고려하세요. 테스트 결과, 워크로드가 이미 CPU 리소스의 50% 이상을 소비하고 있다면 동시 세그먼트 검색을 위해 클러스터를 확장해야 한다는 것을 확인했어요.
- slice 수가 2인 상태에서 클러스터에 아직 가용 리소스가 남아 있다면, 클러스터의 검색 지연 시간과 리소스 사용률을 모니터링하면서 slice 수를 4나 6 같은 더 큰 값으로 늘릴 수 있어요.
- 많은 클라이언트가 검색 요청을 병렬로 보낼 때는 낮은 slice 수가 대체로 더 효과적이에요. 클라이언트 수가 많을수록 초당 쿼리 수가 늘어나고, 이는 더 높은 리소스 사용으로 이어지기 때문에 CPU 사용률에 그대로 반영돼요.
OpenSearch 3.0으로 업그레이드할 때는 auto 모드에서 동시 검색이 기본적으로 활성화되기 때문에, 애그리게이션이 포함된 워크로드에서 CPU 사용률이 더 높아질 수 있다는 점에 유의하세요. OpenSearch 2.x 클러스터의 CPU 사용률이 애그리게이션 워크로드를 실행할 때 25%를 초과한다면, 업그레이드 전에 다음 옵션을 고려하세요:
- 증가하는 CPU 수요를 감당할 수 있도록 클러스터 리소스 확장 계획을 세우세요.
- 사용 사례에서 확장이 불가능하다면 동시 검색을 비활성화할 준비를 하세요.
제한 사항
다음 애그리게이션은 동시 검색 모델을 지원하지 않아요. 검색 요청에 이 애그리게이션 중 하나가 포함되어 있으면, 클러스터 수준이나 인덱스 수준에서 동시 세그먼트 검색이 활성화되어 있더라도 요청은 비동시 경로로 실행돼요.
- join 필드에 대한 부모 애그리게이션. 자세한 내용은 이 GitHub 이슈를 참조하세요.
sampler및diversified_sampler애그리게이션. 자세한 내용은 이 GitHub 이슈를 참조하세요.
기타 고려 사항
다음 섹션에서는 동시 세그먼트 검색에 대한 추가 고려 사항을 설명해요.
terminate_after 검색 파라미터
terminate_after 검색 파라미터는 지정한 개수의 일치하는 문서가 수집되면 검색 요청을 종료하는 데 사용해요. 요청에 terminate_after 파라미터를 포함하면 동시 세그먼트 검색이 비활성화되고, 요청은 비동시 방식으로 실행돼요.
일반적으로 쿼리는 비교적 작은 terminate_after 값과 함께 사용되며, 검색이 축소된 데이터셋에서 수행되기 때문에 빠르게 완료돼요. 따라서 이 경우 동시 검색이 성능을 추가로 개선하지 못할 수 있어요. 게다가 terminate_after를 track_total_hits나 size 같은 다른 검색 요청 파라미터와 함께 사용하면 복잡성이 더해지고 예상되는 쿼리 동작이 달라져요. terminate_after가 포함된 검색 요청은 비동시 경로로 전환되기 때문에, 동시 요청과 비동시 요청 간에 일관된 결과가 보장돼요.
정렬
세그먼트의 데이터 배치에 따라, 정렬 최적화 기능은 최솟값과 최댓값, 그리고 이전에 수집한 값을 기준으로 세그먼트 전체를 제외할 수 있어요. 상위 값이 처음 몇 개 세그먼트에 존재하고 다른 모든 세그먼트가 제외된다면, 동시 세그먼트 검색으로 정렬할 때 쿼리 지연 시간이 늘어날 수 있어요. 반대로 마지막 몇 개 세그먼트에 상위 값이 포함되어 있다면, 동시 세그먼트 검색을 사용할 때 지연 시간이 개선될 수 있어요.
Terms 집계
비동시 검색은 문서 개수 오류를 계산하여 doc_count_error_upper_bound 응답 파라미터로 반환해요. 동시 세그먼트 검색에서는 shard_size 파라미터가 세그먼트 슬라이스 수준에 적용돼요. 이 때문에 동시 검색은 추가적인 문서 개수 오류를 유발할 수 있어요.
shard_size가 doc_count_error_upper_bound와 수집된 버킷 모두에 어떤 영향을 미치는지 자세히 알아보려면 이 GitHub 이슈를 참고하세요.
개발자 정보
다음 섹션에서는 개발자를 위한 추가 정보를 제공해요.
AggregatorFactory 변경 사항
구현 세부 사항 때문에 모든 애그리게이터 타입이 동시 세그먼트 검색을 지원할 수 있는 것은 아니에요. 이를 위해 AggregatorFactory 클래스에 특정 집계 타입이 동시 세그먼트 검색을 지원하는지 여부를 나타내는 supportsConcurrentSegmentSearch() 메서드를 도입했어요. 이 메서드는 기본적으로 false를 반환해요. 동시 세그먼트 검색을 지원해야 하는 애그리게이터는 자체 팩토리 구현에서 이 메서드를 반드시 오버라이드해야 해요.
커스텀 플러그인 기반 Aggregator 구현이 동시 검색 경로에서 정상적으로 동작하도록 하려면, 플러그인 개발자는 동시 검색을 활성화한 상태에서 자신의 구현을 검증한 뒤, 플러그인을 업데이트하여 supportsConcurrentSegmentSearch() 메서드를 오버라이드해 true를 반환하도록 만들면 돼요.
플러그인 방식의 동시 검색 decider: ConcurrentSearchRequestDecider
2.17에 도입됨 {: .label .label-purple }
플러그인 개발자는 ConcurrentSearchRequestDecider를 확장하고 그 팩토리를 SearchPlugin#getConcurrentSearchRequestFactories()를 통해 등록하여, auto 모드의 동시 검색 의사 결정을 커스터마이즈할 수 있어요. 이 decider들은 요청이 제한 사항 및 기타 고려 사항 섹션에 나열된 어떤 카테고리에도 속하지 않는 경우에만 평가돼요. decider 구현에 대해 자세히 알아보려면 해당 GitHub 이슈를 참고하세요.
검색 요청은 QueryBuilderVisitor를 사용해 파싱되며, 이 visitor는 검색 요청의 QueryBuilder 트리에 있는 모든 노드에 대해 구성된 모든 decider의 ConcurrentSearchRequestDecider#evaluateForQuery() 메서드를 호출해요. 최종 동시 검색 결정은 각 decider가 ConcurrentSearchRequestDecider#getConcurrentSearchDecision() 메서드에서 반환한 결정을 결합하여 얻어요.