-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
240610 [Elasticsearch] 난 like 검색이 필요했을 뿐인데..
- Loading branch information
Showing
1 changed file
with
163 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |