[Elasticsearch] like 검색, term vs match

Suzie·2024년 6월 11일
4
post-thumbnail

Intro

팀에서 Elasticsearch로 구현된 부분에 이런 문의가 들어왔다,

/documents/common 을 검색했는데, /documents/suzie/common도 검색됐어요!

코드를 보니 Match Query가 and option으로 설정되어 있었다.

그럼.. 어떤 검색 쿼리를 써야 적당한 걸까?!
검색 방법을 정하기 위해서는 우선 Elasticsearch가 어떻게 저장되는지 알아야한다!

Inverted Index

Elasticsearch는 저장하는 행위를 Indexing 한다고 하는데, 이건 저장하면서 (Inverted) Index를 생성하기 때문이다!

  • 구조 : Text를 Tokenize한 Term에 대한 Document ID를 저장하는 구조
    • ex) "suzie" -> doc1, doc2, doc3 (suzie가 포함된 문서)
* 뭐가 달라??
기본적으로 RDBMS는 like 검색을 위해 데이터를 순차 검색 -> 속도 느림
ES는 데이터를 저장할 때 Inverted Index를 생성(색인) -> 빠른 검색 가능

아니 근데, 무슨 기준으로 text를 indexing 하는건데?!

Analyzer

텍스트를 Indexing하는 과정에서 Analyzer가 텍스트 분석을 하게된다.

ElasticSearch Analyzer Pipeline

image

Char Filters

입력된 원본의 텍스트를 분석에 필요한 형태로 변환(전처리)

  • HTML Strip : HTML -> 일반 텍스트
  • Mapping : 지정 단어 치환
    • ex) 특수문자(+) -> 문자(_plus_) 치환
  • Pattern Replace : 정규식(Regular Expression)으로 복잡한 경우 치환
    • camelCase -> 사이에 공백 삽입
  • ...

Tokenizer

입력 데이터를 설정된 기준에 따라 검색어 토큰으로 분리하는 역할

  • standard: 기호, 공백을 구분자로 분리
    • ex) -, [], @, /
  • whitespace: 스페이스, 탭, 그리고 줄바꿈 같은 공백만을 기준으로 텀을 분리
    • ex) 띄어쓰기, 탭, 줄바꿈
  • keyword: 입력값 전체를 하나의 싱글 토큰으로 저장
  • letter: 알파벳을 제외한 모든 공백, 숫자, 기호들
  • UAX URL Email: 이메일 주소와 웹 URL 경로는 분리하지 않고 그대로 하나의 텀으로 저장
  • Pattern: 분리할 패턴 지정
    • ex) 단일 기호('/'), 정규식(camelCase) 등으로 지정 가능
  • Path Hierachy: 경로 데이터를 계층별로 저장해서 하위 디렉토리에 속한 도큐먼트들을 수준별로 검색 가능
    • ex) /hi/hello/bye -> /hi, /hi/hello, /hi/hello/bye
  • ...

Token Filters

분리된 토큰들에 다시 필터를 적용해서 실제로 검색에 쓰이는 검색어들로 최종 변환하는 역할

  • keyword: 입력값 전체를 하나의 싱글 토큰으로 저장
  • Lowercase: lowercase로 변경해 저장(Uppercase)
  • Stop: "in", "the" 같은 무의미한 단어를 제외할 수 있음
  • Synonym: 유의어, 약어 등을 사전에 설정
  • Unique: 중복되는 검색어 삭제
  • nGram: 입력값을 미리 분리해서 저장
    • min_gram (default: 1), max_gram (default: 2)
    • ex) "suzie" (size 1~2) => s/u/z/i/e, su/uz/zi/ie
  • edgeNGram: nGram을 앞에서부터 검색할 때
    • 예: "suzie" (size 1~3) => s, su, suz
  • Shingle: nGram을 문장으로 단어 단위로 검색할 때
    • min_shingle_size (default: 2), max_shingle_size (default: 2)
    • ex) "I am Suzie" (size 2) => I am, am Suzie
  • ...

Stemmer

어간 추출, 형태소 분석

  • Snowball: 형태소 분석 알고리즘("s","ing" 등 제거)
  • Nori: 한글 형태소 분석기

Query

그럼 이제 드디어 무슨 쿼리를 사용하면 되는지!!! 알아보자

Term Query

ES는 저장할 때 들어온 Text를 Token으로 분리하고, 이를 Term에 대한 Document ID를 저장하는 구조라고 했었다.
그래서 Term Query는말 그대로 색인된 Term이 일치하는 것을 찾는 Query이다!!

ex) ES 저장 Text : '여러개의 물건들'
 -> 색인된 Term : '여러', '개', '물건', '물건들'

이 때 Term Query로 검색할 수 있는 Term은 '여러', '개', '물건', '물건들'만 되는 것이다!

  • 따라서, keyword field 검색에 주로 사용(text field에는 사용 지양)
  • analyzer는 안 거치지만 대상 필드에 normalizer가 설정되어 있으면 normalizer를 거친다
* ES에서 선언 가능한 문자열 타입
- text: Analyzer를 통해 Tokenize된 Term을 저장
- keyword: 입력된 문자열을 하나의 Token으로 저장(= text타입에 keyword analyzer 적용한 것과 같음)
    ㄴ 주로 정렬, 필터링에 사용

Normalizer

  • Analyzer와 비슷하지만, token을 분리하지 않는 점이 다르다!!(Tokenizer X)
  • CharFilter, TokenFilter 일부 가능
    • lowercase 가능, stemming 같은 전체 형태소 분석 불가능

Wildcard Query

  • 주로 keyword field에 사용
  • wildcard 문자: *, ? 로 부분일치 검색 가능
    • ex) *suzie*, suz?e
  • 전체 스캔이므로 주의해서 사용!!

Match Query

ES Query 종류를 알아보고자 검색하다보면 아래 문장을 계속 마주치게 된다.

Match query 는 Term query와 달리 Analyzer를 거쳐 검색된다
아! 그러니까 Match 쿼리는 검색어도 Analyze해서 검색하는거구나?!

  • operator: or, and (default: or)
    • or: token 하나라도 매칭되면 return
    • and: 모든 token이 있어야 return
  • zero_terms_query: none, all (default: none)
    • 쿼리 문자열이 비어 있거나 모든 토큰이 제거된 경우, 어떻게 처리할지 설정
    • none: 검색 결과가 없음
    • all: 모든 문서가 반환

Match Phrase Query

Match Query(and) + 순서가 일치해야 함
ㄴ 따라서 일치하는 문장을 찾을 때 주로 사용

  • slop: 단어 사이의 거리(기본값 0)
    • ex) slop: 1 -> 단어 사이의 거리가 1인 것도 검색

Match Phrase Prefix Query

Match Phrase Query + 마지막 단어 접두사로 취급해 부분 일치 검색 허용
도대체 이게 무슨말이냐고...?! -> 마지막 단어 자동 완성 느낌이라고 생각하면 될 것 같다!

  • ex) "I am Su" -> "I am Suzie" 검색 가능

Multi Match Query

하나의 쿼리로 여러 필드를 검색할 수 있게!(필드 별 가중치 줄 수 있음)

  • best_fields: (기본값) 검색어와 가장 잘 매칭되는 단일 필드를 기준으로 결과를 반환
  • most_fields: 검색어가 여러 필드에서 매칭될 때, 각 필드의 매칭 점수를 합산하여 결과를 반환
  • cross_fields: 검색어를 여러 필드를 조합하여 하나의 필드처럼 검색(합치는 느낌!)
  • phrase: 검색어를 구문으로 간주하여, 구문 일치 검색을 수행
  • phrase_prefix: 구문 일치 검색을 하되, 마지막 단어를 접두사로 취급하여 검색

나의 결정은?

/documents/common 을 검색했는데, /documents/suzie/common도 검색됐어요!

기존 코드를 수정하는 것이기 때문에 검색 query만 바뀌길 원했다.
Match Query(And)-> Match Phrase Prefix Query로 변경하면 path 의 순서가 꼬이는 것을 방지하고 사용자가 like 과 비슷하게 기대하던 검색을 할 수 있을 것이라고 생각했다.

그런데 es에서 확인하고 프로젝트에 적용하니, 이러한 에러를 만났다 ㅜㅜ

[match_phrase_prefix] query does not support [zero_terms_query]

stackoverflow 에서 찾은 이유는 es client 버전과 es 버전이 맞지 않는 이유라고 했다.(match_phrase_prefix가 zero_terms_query를 지원하는 건 es 7.10 이후부터 가능하다고 한다.)

그래서 대안으로 Multi Match Query에 필드를 하나만 넣게 설정하고, phrase_prefix 옵션으로 설정해서 사용했다.

찾아보면서 Path Hierachy등 맞춤 옵션을 많이 찾았지만, 기존 사용하던 부분은 다른 검색에 전체적으로 사용되었던 모듈이기 때문에, 공통적으로 match_phrase_prefix로 변경하는 것이 더 낫겠다고 판단했다.

Outro

그럼 like 검색을 Elasticsearch에서는 어떻게 하면 될까?
like 검색!!을 하고 싶다면 field를 keyword로 저장하고 wildcard로 검색하는 방법이 있을 것이다.
하지만 이 방법은 Elasticsearch의 Indexing 장점을 활용하지 못하기 때문에 데이터가 많을 경우 지양하는 게 좋을 것이댜.

문장, 혹은 특수문자로 연결된 텍스트라면 match phrase prefix가 대안이 될 수 있다.

  • token이 모두 들어있는지 확인하고
  • 그 token이 순차적으로(사이에 다른 토큰 없이) 나열되어있는지 확인하고
  • 검색어의 마지막 단어는 접두어로 부분 일치 검색이 가능하기 때문에
    사람이 검색하기에 그나마 자연스러운 검색을 지원할 수 있다.(앞에서 부터 검색한다는 가정하에 like 검색과 유사하다고 생각함)

하지만 상황에 따라 적절하지 않은 케이스도 많을 것이기 때문에, 세밀한 검색은 field 별로 analyzer 설정을 해야할 것이다:)

Reference

https://esbook.kimjmin.net/06-text-analysis
https://findstar.pe.kr/2018/01/19/understanding-query-on-elasticsearch/
https://discuss.elastic.co/t/difference-between-analyzer-and-normalizer/205897/2
https://stackoverflow.com/questions/71839329/how-to-remove-zero-terms-query-in-match-phrase-prefix-in-elasticsearch-from-quer

2개의 댓글

comment-user-thumbnail
2024년 6월 11일

오호.. 자세한 정리 감사합니다

답글 달기
comment-user-thumbnail
2024년 6월 12일

검색할 때 얼렁뚱땅 넘어갔던 부분인데..많이 공감되네요😂

답글 달기