[Elasticsearch] 정보 검색과 검색 쿼리
저장된 데이터를 검색할 때는 엘라스틱서치 엔진이 제공하는 QueryDSL을 사용한다.
여기서 QueryDSL은 Domain Specific Language의 약자로 특정 기술에 종속되지 않고 도메인 모델과 쿼리 언어간 자연스러운 통합을 목표로 설계된 언어이다.
여러 데이터 소스에서 사용할 수 있는 추상화된 쿼리 방법론이고, JPA Elasticsearch 등 각 기술 스택에 맞춰서 구현된 QueryDSL 구현체가 있다고 생각하면 된다. (인터페이스)
Term-Level 쿼리
analyzed 되지 않은 필드에 대해 검색할 때 사용되는 쿼리.
keyword, integer, date, boolean 값으로 데이터를 찾을 때 사용하자.
term, terms. range, exists, prefix, wildcard, regexp, ids 등 여러 조건을 설정할 수 있으니 참고하자.
exists : 인덱싱된 필드의 유무를 확인해 누락된 데이터가 있는 문서를 찾을 때 유용하다 (RDB의 NOT NULL)
range : 기본적으로 UTC 시간을 사용하고 특정 시간대를 사용하려면 time_zone 조건으로 지정할 수 있다.
{
"query": {
"bool": {
"must": [
{
"term": {
"status": "available"
}
},
{
"range": {
"price": {
"gte": 10000,
"lte": 50000
}
}
}
]
}
}
}
Term-Level 쿼리가 기본적으로 analyzed 되지 않은 필드를 대상으로 쿼리하긴 하지만 역색인 구조는 유용하게 활용된다.
필드의 값이 그대로 역색인에 저장돼 특정 상품의 수량, 등록일자를 기준으로 문서를 탐색하는 경우 효율적으로 처리할 수 있지만, 정규표현식이나 prefix wildcard를 사용하면 역색인을 제대로 활용할 수 없고 역색인된 모든 단어를 탐색해야 해서 쿼리 성능이 저하된다.
검색 조건으로 자주 사용되는 접두사나 해당 패턴에 대한 별도의 필드를 매핑에 추가해서 사용하거나 n-gram 인덱싱을 사용해서 쿼리 성능을 향상시킬 수 있지만.. 저장되는 데이터도 많아지다 보니 공간복잡도가 늘어난다.
이 이슈는 RDB의 LIKE REGEXP 쿼리에서도 인덱스를 제대로 사용할 수 없어 발생하는 문제와 유사한데.. 사실 엘라스틱서치는 역색인 구조를 기반으로 Full-Text 검색을 효과적으로 수행하는데에 최적화된 시스템이라 Term-Level쿼리에서 prefix나 wildcard 쿼리를 사용하기에 설계상 적합하지 않다.
Full-Text 쿼리
analyze 된 텍스트 필드를 검색할 때 사용되는 쿼리.
토큰화된 결과를 부분적 일치와 유사성 점수를 기반으로 결과를 정렬한다.
띄어쓰기는 기본적으로 OR 연산자로 인식되고, operator 옵션으로 연산자를 지정할 수 있다.
match : 텍스트를 분석기로 처리해 검색하며 유사성 점수로 결과를 정렬한다.
match_phrase : 구문 검색을 수행해 단어가 인접한 경우에만 결과를 반환한다. slop 옵션으로
multi_match : 여러 필드를 대상으로 검색하고, 필드별 가중치를 설정할 수 있다.
{
"query": {
"multi_match": {
"query": "elasticsearch query",
"fields": ["title", "content"],
"type": "most_fields"
}
}
}
{
"query": {
"bool": {
"should": [
{ "match": { "title": "elasticsearch query" } },
{ "match": { "content": "elasticsearch query" } }
]
}
}
}
multi_match 쿼리는 내부적으로 다수의 match query로 분리된 후 실행된다.
score 계산은 검색 결과의 관련성을 평가해 문서를 정렬할 때 사용되는데, 기본적으로 BM25 알고리즘을 사용한다. (Best Matching)
단어가 얼마나 자주 등장하는지, 단어가 전체 문서에서 얼마나 조금 나타나는지, 문서의 길이가 어느정도인지, slop proximity 등 쿼리에서 사용된 옵션이 무엇인지 등을 고려해 점수를 계산한다.
(https://en.wikipedia.org/wiki/Okapi_BM25)
이런 수식으로 점수가 결정되는데..
물론 내부적으로 어떻게 동작하는지 이해하고 넘어가는것도 중요하지만.. 실무에서 엘라스틱서치를 사용하면서 BM25 알고리즘을 다른 알고리즘으로 교체해야 했던 적은 없었고, 점수 조정은 필드나 단어별 가중치 조절로 충분했었다.
우선은 너무 깊게 가지 말고.. BM25 알고리즘을 점수를 계산을 추상화해주는 블랙박스로 남겨두고 넘어가자.
Leaf 쿼리와 Compound 쿼리
Term-Level 쿼리와 Full-Text 쿼리는 모두 개별 필드와 상호작용해 특정 조건에 따라 문서를 검색하는 Leaf 쿼리이고, Compound 쿼리는 Leaf 쿼리를 결합해 복잡한 검색을 수행한다.
Compound 쿼리는 bool 쿼리 기반으로 동작한다.
must : AND로 동작해 문서의 점수에 영향을 준다.
should : OR로 동작해 조건 만족 시 점수가 증가한다.
filter : 조건을 만족하는 문서만 반환하고 결과를 캐싱할 수 있으며 점수에 영향을 주지 않는다.
minimum_should_match : should 조건에서 만족시켜야 할 최소 개수를 설정한다.
boost : 조건의 중요도를 설정한다.
{
"bool": {
"must": [
{ "match": { "title": "Elasticsearch" } },
{ "range": { "price": { "gte": 100, "lte": 500 } } }
],
"must_not": [
{ "term": { "status": "inactive" } }
],
"should": [
{ "term": { "category": "electronics" } },
{ "term": { "category": "appliances" } }
],
"minimum_should_match": 1
}
}
SELECT
*
FROM
TB_PRODUCTS
WHERE
COL_TITLE LIKE '%Elasticsearch%'
AND COL_PRICE BETWEEN 100 AND 500
AND COL_STATUS != 'inactive'
AND (COL_CATEGORY = 'electronics' OR COL_CATEGORY = 'appliances');
compound 쿼리와 그 쿼리에 해당하는 SQL이다.
사실 Full-Text 쿼리의 match를 사용하면 엘라스틱서치 엔진은 내부적으로 해당 쿼리를 bool 쿼리로 변환해 토큰 간의 관계를 정의한다.
자바에서 for 구문을 쓰면 JVM이 내부적으로 Iterator 기반으로 변환해서 처리하는 것 처럼.. match 쿼리가 bool 쿼리로 내부적으로 변환된 후 실행되는건 일종의 추상화라고 생각하면 된다.
사용자는 match 쿼리를 사용해 bool 쿼리를 직접 작성하는 것 보다 편하게 쿼리를 작성할 수 있고, 복잡한 내부 로직은 엘라스틱서치가 자동으로 처리한다.
매핑 타입이 nested로 설정된 경우 nested 쿼리를 사용해 데이터를 검색한다.
nested 쿼리는 기본적으로 부모 문서를 반환하니 필요 시 inner_hits 옵션을 사용해 조건을 만족하는 nested 문서만 반환하도록 설정하자.
nested 문서는 hidden lucene document로 저장되기에 쿼리 실행 시 성능 저하가 발생할 수 있으니.. 조심해서 사용하자.
사실 회사에서는 Full-Text 쿼리와 Term-Level 쿼리를 주로 사용하고, nested 쿼리는 거의 사용해 본 적이 없다.
애초에 nested 타입으로 매핑을 지정하는 경우도 많지 않았고.. 우선 개념만 잡고 넘어가자.
검색 시 fuzzy 옵션을 추가해 오타나 유사한 단어로 정확하게 일치하지 않는 텍스트도 검색할 수 있다.
엘라스틱서치는 입력된 쿼리와 유사한 단어를 찾기 위해 편집 거리를 계산하고, 지정된 fuzziness 옵션에 따라 유사한 단어를 필터링한다.
{
"query": {
"match": {
"field_name": {
"query": "aple",
"fuzziness": 1,
"prefix_length": 2
}
}
}
}
prefix_length 옵션으로 편집 거리 비교 전에 일치해야 하는 문자 수를 지정하고, fuzziness 옵션으로 편집 거리를 설정한다.
auto로 설정 시 키워드의 길이에 따라 적절한 편집 거리 값을 사용한다.
fuzziness 값이 높아질수록 비교하는 데이터와 계산량이 많아져 성능에 영향을 줄 수 있으니 조심하자.
'Database > Elasticsearch' 카테고리의 다른 글
[Elasticsearch] 매핑과 데이터 타입 (0) | 2024.11.27 |
---|---|
[Elasticsearch] 아키텍처와 동작 원리 (0) | 2024.11.07 |
댓글
이 글 공유하기
다른 글
-
[Elasticsearch] 매핑과 데이터 타입
[Elasticsearch] 매핑과 데이터 타입
2024.11.27 -
[Elasticsearch] 아키텍처와 동작 원리
[Elasticsearch] 아키텍처와 동작 원리
2024.11.07