Using inner hits in hybrid queries
출처: 문서
본문
하이브리드 쿼리에서 inner hits 사용하기
3.0에서 도입 {: .label .label-purple }
하이브리드 검색을 실행할 때 검색 요청에 inner_hits 절을 포함하면 매칭된 nested 객체나 자식 문서를 가져올 수 있어요. 이 정보를 활용하면 쿼리와 매칭된 문서의 특정 부분을 살펴볼 수 있어요.
inner_hits가 동작하는 방식을 더 자세히 알아보려면 Retrieve inner hits를 참고하세요.
하이브리드 쿼리가 실행되는 동안 문서는 다음과 같이 점수가 계산되고 조회돼요:
- 각 서브쿼리는 자신의 inner hits 관련성을 기준으로 부모 문서를 선택해요.
- 모든 서브쿼리에서 선택된 부모 문서들이 결합되고, 그 점수가 정규화되어 하이브리드 점수가 만들어져요.
- 각 부모 문서에 대해 관련된
inner_hits가 샤드에서 조회되어 최종 응답에 포함돼요.
하이브리드 쿼리는 최종 검색 결과를 결정할 때 일반 쿼리와 다른 방식으로 inner hits를 처리해요:
- 일반 쿼리에서는 부모 문서의 최종 순위가
inner_hits점수에 의해 직접 결정돼요. - 하이브리드 쿼리에서는 최종 순위가 하이브리드 점수(모든 서브쿼리 점수를 정규화해 결합한 값)에 의해 결정돼요. 다만 부모 문서를 샤드에서 가져올 때는 여전히
inner_hits의 관련성을 기준으로 해요.
응답의 inner_hits 섹션에는 정규화 이전의 원본(raw) 점수가 표시돼요. 부모 문서에는 최종 하이브리드 점수가 표시돼요.
{: .note}
예제
다음 예제는 하이브리드 쿼리에서 inner_hits를 사용하는 방법을 보여줘요.
1단계: 인덱스 생성
두 개의 nested 필드(user와 location)가 있는 인덱스를 생성해요:
PUT /my-nlp-index
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 0
},
"mappings": {
"properties": {
"user": {
"type": "nested",
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "integer"
}
}
},
"location": {
"type": "nested",
"properties": {
"city": {
"type": "text"
},
"state": {
"type": "text"
}
}
}
}
}
}
{% include copy-curl.html %}
2단계: 검색 파이프라인 생성
min_max 정규화 기법과 arithmetic_mean 조합 기법을 사용하는 normalization-processor로 검색 파이프라인을 구성해요:
PUT /_search/pipeline/nlp-search-pipeline
{
"description": "Post processor for hybrid search",
"phase_results_processors": [
{
"normalization-processor": {
"normalization": {
"technique": "min_max"
},
"combination": {
"technique": "arithmetic_mean",
"parameters": {}
}
}
}
]
}
{% include copy-curl.html %}
3단계: 인덱스에 문서 색인
이전 단계에서 생성한 인덱스에 문서를 색인하려면 다음 요청을 보내요:
POST /my-nlp-index/_bulk
{"index": {"_index": "my-nlp-index"}}
{"user":[{"name":"John Alder","age":35},{"name":"Sammy","age":34},{"name":"Mike","age":32},{"name":"Maples","age":30}],"location":[{"city":"Amsterdam","state":"Netherlands"},{"city":"Udaipur","state":"Rajasthan"},{"city":"Naples","state":"Italy"}]}
{"index": {"_index": "my-nlp-index"}}
{"user":[{"name":"John Wick","age":46},{"name":"John Snow","age":40},{"name":"Sansa Stark","age":22},{"name":"Arya Stark","age":20}],"location":[{"city":"Tromso","state":"Norway"},{"city":"Los Angeles","state":"California"},{"city":"London","state":"UK"}]}
{% include copy-curl.html %}
4단계: 하이브리드 검색으로 인덱스를 검색하고 inner hits 가져오기
다음 요청은 하이브리드 쿼리를 실행해 두 개의 nested 필드 user와 location에서 매칭되는 항목을 검색해요. 각 필드의 결과를 부모 문서로 이루어진 하나의 순위 목록으로 결합하는 동시에, inner_hits를 사용해 매칭된 nested 객체도 함께 가져와요:
GET /my-nlp-index/_search?search_pipeline=nlp-search-pipeline
{
"query": {
"hybrid": {
"queries": [
{
"nested": {
"path": "user",
"query": {
"match": {
"user.name": "John"
}
},
"score_mode": "sum",
"inner_hits": {}
}
},
{
"nested": {
"path": "location",
"query": {
"match": {
"location.city": "Udaipur"
}
},
"inner_hits": {}
}
}
]
}
}
}
{% include copy-curl.html %}
응답에는 매칭된 부모 문서와 함께 user와 location 두 nested 필드에 대한 관련 nested inner_hits가 포함돼요. 각 inner hit은 어떤 nested 객체가 매칭됐는지, 그리고 전체 하이브리드 점수에 얼마나 강하게 기여했는지를 보여줘요:
...
{
"hits": [
{
"_index": "my-nlp-index",
"_id": "1",
"_score": 1.0,
"inner_hits": {
"location": {
"hits": {
"max_score": 0.44583148,
"hits": [
{
"_nested": {
"field": "location",
"offset": 1
},
"_score": 0.44583148,
"_source": {
"city": "Udaipur",
"state": "Rajasthan"
}
}
]
}
},
"user": {
"hits": {
"max_score": 0.4394061,
"hits": [
{
"_nested": {
"field": "user",
"offset": 0
},
"_score": 0.4394061,
"_source": {
"name": "John Alder",
"age": 35
}
}
]
}
}
}
// Additional details omitted for brevity
},
{
"_index": "my-nlp-index",
"_id": "2",
"_score": 5.0E-4,
"inner_hits": {
"user": {
"hits": {
"max_score": 0.31506687,
"hits": [
{
"_nested": {
"field": "user",
"offset": 0
},
"_score": 0.31506687,
"_source": {
"name": "John Wick",
"age": 46
}
},
{
"_nested": {
"field": "user",
"offset": 1
},
"_score": 0.31506687,
"_source": {
"name": "John Snow",
"age": 40
}
}
]
}
}
// Additional details omitted for brevity
}
}
]
// Additional details omitted for brevity
}
...
explain 파라미터 사용하기
inner hits가 하이브리드 점수에 어떻게 기여하는지 이해하려면 설명 기능을 활성화할 수 있어요. 그러면 응답에 상세한 점수 정보가 포함돼요. 하이브리드 쿼리에서 explain을 사용하는 방법을 자세히 알아보려면 Hybrid search explain을 참고하세요.
explain은 리소스와 시간 양면에서 모두 비용이 많이 드는 작업이에요. 프로덕션 클러스터에서는 문제 해결 목적으로 필요할 때만 아껴 사용할 것을 권장해요.
{: .warning}
먼저 2단계에서 생성한 검색 파이프라인에 hybrid_score_explanation 프로세서를 추가해요:
PUT /_search/pipeline/nlp-search-pipeline
{
"description": "Post processor for hybrid search",
"phase_results_processors": [
{
"normalization-processor": {
"normalization": {
"technique": "min_max"
},
"combination": {
"technique": "arithmetic_mean"
}
}
}
],
"response_processors": [
{
"hybrid_score_explanation": {}
}
]
}
{% include copy-curl.html %}
자세한 내용은 Hybrid score explanation processor와 Hybrid search explain을 참고하세요.
그다음 4단계에서 실행했던 것과 동일한 쿼리를 실행하되, 검색 요청에 explain 파라미터를 포함해요:
GET /my-nlp-index/_search?search_pipeline=nlp-search-pipeline&explain=true
{
"query": {
"hybrid": {
"queries": [
{
"nested": {
"path": "user",
"query": {
"match": {
"user.name": "John"
}
},
"score_mode": "sum",
"inner_hits": {}
}
},
{
"nested": {
"path": "location",
"query": {
"match": {
"location.city": "Udaipur"
}
},
"inner_hits": {}
}
}
]
}
}
}
{% include copy-curl.html %}
응답에는 상세한 점수 정보가 담긴 _explanation 객체가 포함돼요. nested details 배열에는 사용된 score mode, 부모 문서 점수에 기여하는 자식 문서의 개수, 그리고 점수가 어떻게 정규화되고 결합됐는지에 대한 관련 정보가 제공돼요:
{
...
"_explanation": {
"value": 1.0,
"description": "arithmetic_mean combination of:",
"details": [
{
"value": 1.0,
"description": "min_max normalization of:",
"details": [
{
"value": 0.4458314776420593,
"description": "combined score of:",
"details": [
{
"value": 0.4394061,
"description": "Score based on 1 child docs in range from 0 to 6, using score mode Avg",
"details": [
{
"value": 0.4394061,
"description": "weight(user.name:john in 0) [PerFieldSimilarity], result of:"
// Additional details omitted for brevity
}
]
},
{
"value": 0.44583148,
"description": "Score based on 1 child docs in range from 0 to 6, using score mode Avg",
"details": [
{
"value": 0.44583148,
"description": "weight(location.city:udaipur in 5) [PerFieldSimilarity], result of:"
// Additional details omitted for brevity
}
]
}
]
}
]
}
]
}
}
...
inner hits로 정렬하기
정렬을 적용하려면 inner_hits 절에 sort 서브절을 추가해요. 예를 들어 user.age로 정렬하려면 inner_hits 절에 이 정렬 조건을 지정해요:
GET /my-nlp-index/_search?search_pipeline=nlp-search-pipeline
{
"query": {
"hybrid": {
"queries": [
{
"nested": {
"path": "user",
"query": {
"match": {
"user.name": "John"
}
},
"score_mode": "sum",
"inner_hits": {
"sort": [
{
"user.age": {
"order": "desc"
}
}
]
}
}
},
{
"nested": {
"path": "location",
"query": {
"match": {
"location.city": "Udaipur"
}
},
"inner_hits": {}
}
}
]
}
}
}
{% include copy-curl.html %}
응답에서 user inner hits는 관련성이 아닌 age 기준 내림차순으로 정렬돼요. 이 때문에 _score 필드가 null이에요(커스텀 정렬이 적용되면 점수가 계산되지 않아요):
...
"user": {
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "my-nlp-index",
"_id": "2",
"_nested": {
"field": "user",
"offset": 0
},
"_score": null,
"_source": {
"name": "John Wick",
"age": 46
},
"sort": [
46
]
},
{
"_index": "my-nlp-index",
"_id": "2",
"_nested": {
"field": "user",
"offset": 1
},
"_score": null,
"_source": {
"name": "John Snow",
"age": 40
},
"sort": [
40
]
}
]
}
}
...
inner hits로 페이지네이션하기
inner hit 결과를 페이지네이션하려면 inner_hits 절에 from 파라미터(시작 위치)와 size 파라미터(결과 개수)를 지정해요. 다음 예제 요청은 from을 2로(처음 두 개 건너뛰기), size를 2로(두 개의 결과 반환) 설정해 user 필드의 세 번째와 네 번째 nested 객체만 가져와요:
GET /my-nlp-index/_search?search_pipeline=nlp-search-pipeline
{
"query": {
"hybrid": {
"queries": [
{
"nested": {
"path": "user",
"query": {
"match_all": {}
},
"inner_hits": {
"from": 2,
"size": 2
}
}
},
{
"nested": {
"path": "location",
"query": {
"match": {
"location.city": "Udaipur"
}
},
"inner_hits": {}
}
}
]
}
}
}
{% include copy-curl.html %}
응답에는 오프셋 2부터 시작하는 user 필드의 inner hits가 담겨 있어요:
...
"user": {
"hits": {
"total": {
"value": 4,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "my-nlp-index",
"_id": "1",
"_nested": {
"field": "user",
"offset": 2
},
"_score": 1.0,
"_source": {
"name": "Mike",
"age": 32
}
},
{
"_index": "my-nlp-index",
"_id": "1",
"_nested": {
"field": "user",
"offset": 3
},
"_score": 1.0,
"_source": {
"name": "Maples",
"age": 30
}
}
]
}
}
...
inner_hits 필드에 커스텀 이름 정의하기
하나의 쿼리에서 여러 inner hits를 구분하려면 검색 응답에서 inner hits에 커스텀 이름을 정의할 수 있어요. 예를 들어 다음과 같이 location 필드의 inner hits에 coordinates라는 커스텀 이름을 지정할 수 있어요:
GET /my-nlp-index/_search?search_pipeline=nlp-search-pipeline
{
"query": {
"hybrid": {
"queries": [
{
"nested": {
"path": "user",
"query": {
"match_all": {}
},
"inner_hits": {
"name": "coordinates"
}
}
},
{
"nested": {
"path": "location",
"query": {
"match": {
"location.city": "Udaipur"
}
},
"inner_hits": {}
}
}
]
}
}
}
{% include copy-curl.html %}
응답에서 user 필드의 inner hits는 커스텀 이름 coordinates 아래에 표시돼요:
...
"inner_hits": {
"coordinates": {
"hits": {
"total": {
"value": 4,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "my-nlp-index",
"_id": "1",
"_nested": {
"field": "user",
"offset": 0
},
"_score": 1.0,
"_source": {
"name": "John Alder",
"age": 35
}
}
]
}
},
"location": {
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 0.44583148,
"hits": [
{
"_index": "my-nlp-index",
"_id": "1",
"_nested": {
"field": "location",
"offset": 1
},
"_score": 0.44583148,
"_source": {
"city": "Udaipur",
"state": "Rajasthan"
}
}
]
}
}
}
...