Skip to content

Commit

Permalink
240610 [Elasticsearch] 난 like 검색이 필요했을 뿐인데..
Browse files Browse the repository at this point in the history
  • Loading branch information
suzieep committed Jun 10, 2024
1 parent 3b88632 commit c254435
Showing 1 changed file with 163 additions and 0 deletions.
163 changes: 163 additions & 0 deletions TIL/240610.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# 240610 [Elasticsearch] 난 like 검색이 필요했을 뿐인데..

### 서론

> /documents/common 을 검색했는데, /documents/suzie/common도 검색됐어요!
코드를 보니 Match Query가 and option으로 설정되어 있었다.


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

## Analyzer
ES Query 종류를 알아보고자 검색하다보면 아래 문장을 계속 마주치게 된다.
```
Match query 는 Term query와 달리 Analyzer를 거쳐 검색된다
```
어쩔 수 없다!! Analyzer에 대해 먼저 알아보자

### ElasticSearch Analyzer Pipeline
![image](https://github.com/suzieep/TIL/assets/61377122/4502420f-f1b0-4ac2-8101-8f862f4a4942)


#### 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
이제 Analyzer가 뭔지 알았으니 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
Match Query는 Term Query와 달리 Analyzer를 거쳐 검색된다고 했다.
Term과 검색 방식은 같지만, 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로 변경하는 것이 더 낫겠다고 판단했다.

#### 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

0 comments on commit c254435

Please sign in to comment.