전문 검색(Full-Text Search)
여기서는 전문 검색 쿼리를 작성하는 방법을 설명합니다. 전문 검색은 AND
, OR
같은 연산자를 조합하거나 doc_id:10
과 같이 특정한 조건에 부합하는 단어(혹은 토큰)의 일치 여부를 점수화하여 종합적으로 가장 연관된 문서를 찾는 방법입니다.
전문 검색은 다음 예시처럼 여러 연산자 중 하나인 $search
를 사용합니다. 이어서 $project
와 같이 다른 연산자와 함께 사용하는 것이 가능합니다.
{
"$search": {
"query": f"(content:({query}))^0.2 AND (embedding:[{query_embed}])^20",
},
"$project": [
"doc_id",
"content",
{"_meta": "$unnest"}
],
"$hint": "sk_fts"
}
전문 검색을 위해서는 색인이 준비되어 있어야 합니다. 색인의 구성에 따라 검색 결과도 달라집니다.
문법
여기서는 $search
의 인자인 query
의 작성 방법에 대해서 설명합니다. 벡터 검색을 제외하고 입력된 문서와 쿼리는 Analyzer에 의해 토큰으로 분해되고 문서와 쿼리간의 토큰의 일치 여부를 계산하게 됩니다.
기본적으로 아래와 같이 간단히 쿼리만 전달할 수 있습니다. 이 경우 일반적으로 Apple
과 Pineapple
으로 분리된 단어가 일치하는 문서들을 찾게 됩니다.
Apple Pineapple
만약 하나 이상이 단어가 일치하는 문서를 찾고자 한다면 다음과 같이 "
로 묶어 검색할 수 있습니다.
"white cat"
여기서는 $search
연산자에 설정된 기본값에 따라서 동작하게 됩니다. 기본 동작은 다음과 같습니다.
- 전문 검색으로 설정된 모든 필드가 대상이 됨
- 기본 연산은
or
(should)
Boolean
Boolean 연산자를 통해 Term 검색 조건을 제어할 수 있습니다.
다음 쿼리는 China
출현하면서 US
와 USD
가 모두 출현하지 않는 문서를 검색하고 있습니다.
(China AND -(US OR USD))
지원하는 연산자는 다음과 같습니다.
- AND: and
- OR: or
- NOT: not
- +: must
- -: must not
필터 검색
필터 연산은 #
연산자를 통해 사용할 수 있습니다. 필터 연산은 점수 계산에 관여되지 않고 조건만 만족하는지 판단합니다. 따라서 필터 연산으로만 쿼리가 입력된다면 점수는 기본값만 출력됩니다.
cats AND #(cat_type:balinese AND weight:>8.0)
필터 연산은 점수 기반의 연산인 must(+)
, should
, must not(-)
과 함께 사용하며 검색 집합의 크기(cardinality)를 줄여 검색 속도 개선에 활용할 수 있습니다.
그룹화
그룹화를 통해 쿼리 연산의 우선순위를 조절할 수 있습니다.
다음 예시처럼 공백이 포함된 natural language
를 title
필드에 한정하여 검색하게 하거나 AI AND "machine learning"
의 subquery에 대해 boosting 연산을 추가할 때 활용할 수 있습니다.
section:economy title:(natural language) content:(AI AND "machine learning")^2
필드 검색
필드 검색을 사용하여 특정한 필드만 검색할 수 있습니다.
다음 쿼리는category
필드에서 news
이면서 author
가 ted
인 문서를 찾고 있습니다.
category:news AND author:ted
다음 쿼리는 title
필드에서 China
또는 US
가 출현하고 전체 필드에서 Korea
가 출현한 문서를 찾고 있습니다.
title:(China OR US) AND Korea
만약 복수의 필드에 동일한 값을 지정하고 싶다면 다음과 같이 입력할 수 있습니다.
{!fields=[title, content]}:(China AND US)
이 쿼리는 title:(China AND US) content:(China AND US)
로 해석됩니다.
와일드카드(Wildcard) 검색
와일드카드를 사용하여 단어의 일부가 일치하는 문서를 검색할 수 있습니다.
lake* *like
퍼지(Fuzzy) 검색
퍼지 검색은 특정한 편집 거리 (opens in a new tab) 이하의 차이를 허용하는 검색 방식입니다.
예를 들어 appl
은 apple
편집 거리가 1이고 다음과 같이 쿼리하면 검색 과정에서 일치하는 단어로 다룰 수 있습니다. ~
다음에 출현하는 숫자는 편집 거리를 의미합니다.
appl~1
근접 검색
하나 이상의 단어로 구성된 쿼리의 단어가 완전히 일치하지 않더라도 단어 단위로 일정 편집 거리 (opens in a new tab) 이하로 근접했을 때 이를 허용하고자 한다면 근접 검색을 사용할 수 있습니다.
예를 들어 문서에 funny baby cat
이란 문장이 존재하고 다음과 같이 쿼리하면 이 문서는 일치하게 됩니다. ~
다음에 출현하는 숫자는 편집 거리를 의미합니다.
"funny cats"~2
추가적으로 단어의 출현 순서를 고려하지 않는 근접 검색은 ~~
연산자를 통해 검색할 수 있습니다.
"funny cats"~~2
벡터 검색
벡터 검색은 임베딩된 벡터를 사용하여 벡터간 유사도를 기반하여 검색하는 방법입니다. 벡터 검색을 위해서는 다음과 같이 encoder
를 통해 임베딩된 벡터를 생성하고 실수 타입의 배열 스트링으로 변환하여 쿼리할 수 있습니다.
query = "그 여자는 달걀 하나를 그릇에 깨어 넣었다."
query_embedding = encoder.encode(query)
query_embed = ", ".join([str(e) for e in query_embedding[0]])
search_query = {
"$search": {
"query": f"embedding:[{query_embed}]"
},
"$hint": "sk_fts",
}
위 예시에서 embedding
필드는 벡터 검색을 위한
색인으로
준비되어 있어야 합니다.
Boosting
전문 검색에서 출력하는 문서의 순위는 내부적으로 계산되는 점수에 의존합니다. 이 점수는 토큰의 일치 여부, 근접 등 여러 요인으로 결정되고 필요에 따라 특정한 단어의 일치 여부를 중요하게 다루고 싶을 수 있습니다. 이 경우 Boosting을 사용하여 계산 되는 점수의 가중치를 조절할 수 있습니다.
다음의 예시는 term 기반의 검색과 벡터 검색을 통합한 경우이며 term 기반의 검색에 대해서 (content:(그 여자는 달걀 하나를 그릇에 깨어 넣었다))^0.2
에서와 같이 0.2
의 가중치를 부여하고 벡터 기반의 검색에 대해 (embedding:[0.12, 0.92, ... , 0.10])^20
에 20
의 가중치를 부여하는 예시입니다.
(content:(달걀 그릇에 깨어 넣었다))^0.2 AND (embedding:[0.12, 0.92, ... , 0.10])^20
비교 / 범위
특정 필드가 Int64Analyzer나 Float64Analyzer와 같은 비교 가능한 타입의 색인이 지정되어 있다면 다음과 같이 비교 연산자를 사용하여 조건에 맞는 특정값을 검색할 수 있습니다.
다음 예시는 age가 10을 초과하는 값을 검색합니다.
age:>10
혹은 다음과 같이 범위 연산자를 사용하여 특정 범위의 값을 찾을 수 있습니다. 다음은 1보다 큰 값을 찾는 예시이며 여기서 *
는 모든 값을 의미합니다.
age:[1 TO *]
활용
문서 수
FTS로 검색하는 경우 내부 변수인 cursor.fts.total_hits
에는 전체 문서수를 가지고 있습니다. 이를 활용하여 다음과 같이 쿼리하며 total_hits
라는 칼럼이 추가되고 여기에는 전체 문서 수가 저장됩니다.
"$literal": {
"total_hits": {
"$prop": "cursor.fts.total_hits"
}
}