Term-level 쿼리와 Full-text 쿼리 비교
Term-level 쿼리와 Full-text 쿼리 비교
텍스트를 검색할 때 term-level 쿼리와 full-text 쿼리를 모두 사용할 수 있어요. term-level 쿼리는 주로 구조화된 데이터를, full-text 쿼리는 전체 텍스트 검색을 위해 사용해요. 이 글에서는 두 쿼리의 차이점을 살펴봐요.
출처: 문서
본문
텍스트를 검색할 때 term-level 쿼리와 full-text 쿼리를 모두 사용할 수 있어요. term-level 쿼리는 보통 구조화된 데이터를 검색하는 데 쓰이고, full-text 쿼리는 전체 텍스트 검색(full-text search)에 사용돼요. 둘의 가장 큰 차이는 term-level 쿼리가 지정한 용어와 정확히 일치하는 문서를 검색하는 반면, full-text 쿼리는 쿼리 문자열을 분석(analyze)한다는 점이에요. 다음 표는 term-level 쿼리와 full-text 쿼리의 차이를 요약해요. Term-level queries Full-text queries 설명(Description) | term-level 쿼리는 어떤 문서가 쿼리와 일치하는지 답해요. full-text 쿼리는 문서가 쿼리와 얼마나 잘 일치하는지 답해요. 분석기(Analyzer) | 검색 용어는 분석되지 않아요. 즉 term 쿼리는 검색 용어를 그대로 검색해요. | 검색 용어는 특정 문서 필드가 인덱싱될 때 사용했던 것과 동일한 analyzer로 분석돼요. 즉 검색 용어가 문서 필드와 동일한 분석 과정을 거쳐요. 관련성(Relevance) | term-level 쿼리는 관련성 점수를 기준으로 정렬하지 않고 일치하는 문서를 반환해요. 그래도 관련성 점수는 계산하지만, 반환된 모든 문서에 대해 동일한 점수예요. | full-text 쿼리는 각 일치 항목에 대해 관련성 점수를 계산하고 관련성 내림차순으로 결과를 정렬해요. 사용 사례(Use Case) | 숫자, 날짜, 태그 같은 정확한 값을 일치시키고 관련성으로 정렬할 필요가 없을 때 term-level 쿼리를 사용해요. | 텍스트 필드를 일치시키고 대소문자, 어간(stemming) 변형 같은 요소를 고려한 뒤 관련성으로 정렬하려면 full-text 쿼리를 사용해요.
OpenSearch는 관련성 점수를 계산할 때 BM25 순위 알고리즘을 사용해요. 자세한 내용은 Okapi BM25를 참고하세요.
full-text 쿼리를 써야 할까, term-level 쿼리를 써야 할까?
full-text 쿼리와 term-level 쿼리의 차이를 명확히 하기 위해, 특정 텍스트 구문을 검색하는 다음 두 예제를 살펴봐요. 셰익스피어 전집이 OpenSearch 클러스터에 인덱싱되어 있다고 가정해요.
예제: 구문 검색 (Phrase search)
이 예제에서는 text_entry 필드에서 셰익스피어 전집에서 “To be, or not to be”라는 구문을 검색해 봐요. 먼저 이 검색에 term-level 쿼리를 사용하세요:
GET shakespeare/_search
{
"query": {
"term": {
"text_entry": "To be, or not to be"
}
}
}
응답에는 일치 항목이 없으며, hits가 0으로 표시돼요:
{
"took" : 3,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
}
}
이것은 “To be, or not to be”라는 용어가 역색인(inverted index)에서 문자 그대로 검색되기 때문이에요. 역색인에는 텍스트 필드의 분석된 값만 저장돼요. term-level 쿼리는 분석된 텍스트 필드 검색에 적합하지 않아요. 의외의 결과를 내는 경우가 많기 때문이에요. 텍스트 데이터를 다룰 때는 keyword로 매핑된 필드에서만 term-level 쿼리를 사용하세요. 이제 같은 구문을 full-text 쿼리로 검색해 봐요:
GET shakespeare/_search
{
"query": {
"match": {
"text_entry": "To be, or not to be"
}
}
}
“To be, or not to be” 검색 쿼리는 문서의 text_entry 필드와 마찬가지로 분석되고 토큰 배열로 토큰화돼요. full-text 쿼리는 검색 쿼리와 모든 문서의 text_entry 필드 사이에서 토큰의 교집합을 구한 다음 관련성 점수로 결과를 정렬해요:
{
"took" : 19,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 10000,
"relation" : "gte"
},
"max_score" : 17.419369,
"hits" : [
{
"_index" : "shakespeare",
"_id" : "34229",
"_score" : 17.419369,
"_source" : {
"type" : "line",
"line_id" : 34230,
"play_name" : "Hamlet",
"speech_number" : 19,
"line_number" : "3.1.64",
"speaker" : "HAMLET",
"text_entry" : "To be, or not to be: that is the question:"
}
},
{
"_index" : "shakespeare",
"_id" : "109930",
"_score" : 14.883024,
"_source" : {
"type" : "line",
"line_id" : 109931,
"play_name" : "A Winters Tale",
"speech_number" : 23,
"line_number" : "4.4.153",
"speaker" : "PERDITA",
"text_entry" : "Not like a corse; or if, not to be buried,"
}
},
{
"_index" : "shakespeare",
"_id" : "103117",
"_score" : 14.782743,
"_source" : {
"type" : "line",
"line_id" : 103118,
"play_name" : "Twelfth Night",
"speech_number" : 53,
"line_number" : "1.3.95",
"speaker" : "SIR ANDREW",
"text_entry" : "will not be seen; or if she be, its four to one"
}
}
]
}
}
...
모든 full-text 쿼리 목록은 Full-text queries를 참고하세요.
예제: 정확한 용어 검색 (Exact term search)
speaker 필드에서 “HAMLET” 같은 정확한 용어를 검색하고 관련성 점수로 정렬할 필요가 없다면 term-level 쿼리가 더 효율적이에요:
GET shakespeare/_search
{
"query": {
"term": {
"speaker": "HAMLET"
}
}
}
응답에는 문서 일치 항목이 포함돼요:
{
"took" : 5,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1582,
"relation" : "eq"
},
"max_score" : 4.2540946,
"hits" : [
{
"_index" : "shakespeare",
"_id" : "32700",
"_score" : 4.2540946,
"_source" : {
"type" : "line",
"line_id" : 32701,
"play_name" : "Hamlet",
"speech_number" : 9,
"line_number" : "1.2.66",
"speaker" : "HAMLET",
"text_entry" : "[Aside] A little more than kin, and less than kind."
}
},
{
"_index" : "shakespeare",
"_id" : "32702",
"_score" : 4.2540946,
"_source" : {
"type" : "line",
"line_id" : 32703,
"play_name" : "Hamlet",
"speech_number" : 11,
"line_number" : "1.2.68",
"speaker" : "HAMLET",
"text_entry" : "Not so, my lord; I am too much i' the sun."
}
},
{
"_index" : "shakespeare",
"_id" : "32709",
"_score" : 4.2540946,
"_source" : {
"type" : "line",
"line_id" : 32710,
"play_name" : "Hamlet",
"speech_number" : 13,
"line_number" : "1.2.75",
"speaker" : "HAMLET",
"text_entry" : "Ay, madam, it is common."
}
}
]
}
}
...
term-level 쿼리는 정확한 일치를 제공해요. 따라서 “Hamlet”으로 검색하면 일치 항목이 나오지 않아요. “HAMLET”은 keyword 필드이므로 분석된 형태가 아니라 OpenSearch에 문자 그대로 저장되기 때문이에요. 검색 쿼리 “HAMLET”도 문자 그대로 검색돼요. 이 필드에서 일치 항목을 얻으려면 정확히 같은 문자를 입력해야 해요.
Should I use a full-text or a term-level queryExample: Phrase search