Opensearch에 저장된 고객 데이터를 이름으로 검색할 때
대소문자 구분없이 First, Last Name으로 검색이 가능해야하는 요구사항이 생겼습니다.
(요구사항 예시)
David Buss와 같은 이름이 있을 때
da, dav, davi, david, David 그리고 bu, bus, buss, Buss 로 검색했을 때 David Buss라는 고객이 나와야 합니다.
가장 간단하게는 기존 데이터를 리인덱싱하지 않고 wildcard를 이용해 *bus*와 같이 조회 하는 것도 방법이었습니다.
wildcard로 시간이 얼마나 걸리는지 확인해보니 고객이 많은 고객사의 경우 500ms ~ 1000ms (0.5~1초)까지 걸리는 것을 확인했습니다. 기존 prefix 매치 형태는 20ms면 조회가 가능했었는데 말이죠..
고객 검색에 1초가 소요되어도 사용하지 못할 응답속도는 아니니 wildcard로 해결해도 될 것 같았지만 ES에 고객 검색용 Index외에도 상품, 주소 등의 Index도 저장되어 있기에 wildcard 검색이 아닌 다른 방식을 찾아보기로 했습니다.
목표:
First, Last Name으로 구성된 David Buss와 같은 이름이 있을 때
대소문자 구분없이 First, Last Name으로 검색가능하게 하기
위처럼 인덱싱하기 위해 name 저장 시 인덱싱 방법(analyzer)에 edge_ngram을 적용했고 2글자 이상부터 검색가능하도록 하기위해 min_gram은 2로 설정했습니다.
검색할때는 Space 기준으로 문자를 나눠서 검색하도록 했습니다. (search_analyzer: whitespace_analyzer)
결과적으로 아래 스크린샷(kibana: dev tools) 처럼 검색이 잘되는 것을 확인할 수 있었습니다.
로컬에서 테스트를 해보고 싶은 분들은 아래 github 링크를 참고해주세요.
- https://github.com/menthamin/code-snippets/tree/main/elk/search
(테스트 방법)
1. docker-compose.yml으로 es와 kibana를 띄우고
2. text 디텍토리의 create-index.md를 kibana dev tools에서 실행해 인덱스를 생성한 후
3. add-documents.py로 샘플 데이터를 넣고 조회 테스트를 해볼 수 있습니다.
PUT /people-new
{
"settings": {
"analysis": {
"filter": {
"edge_ngram_filter": {
"type": "edge_ngram",
"min_gram": 2,
"max_gram": 20
}
},
"analyzer": {
"edge_ngram_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase",
"edge_ngram_filter"
]
},
"whitespace_analyzer": {
"type": "custom",
"tokenizer": "whitespace",
"filter": [
"lowercase"
]
}
}
}
},
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "edge_ngram_analyzer",
"search_analyzer": "whitespace_analyzer",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
인덱스 설정에 대해 설명은 GPT에서 받은 응답을 아래에 남겨봤습니다.
2줄 요약
- edge_ngram_filter로 인덱싱했다 (analyzer)
- 띄어쓰기(whitespace)로 검색어를 쪼개서 검색하도록 했다. (search_analyzer)
Analysis 설정은 텍스트 데이터를 어떻게 분해하고 처리할지를 정의합니다. 여기서는 두 가지 주요 구성 요소인 필터(Filters)와 분석기(Analyzers)가 설정되어 있습니다.
"filter": { "edge_ngram_filter": { "type": "edge_ngram", "min_gram": 2, "max_gram": 20 } }
edge_ngram_filter:
용도: 사용자가 입력한 검색어의 접두사(prefix)에 기반하여 부분 일치 검색을 가능하게 합니다. 예를 들어, "David"라는 단어는 "Da", "Dav", "Davi", "David"와 같은 n-그램으로 분해됩니다.
"analyzer": {
"edge_ngram_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": [
"lowercase",
"edge_ngram_filter"
]
},
"whitespace_analyzer": {
"type": "custom",
"tokenizer": "whitespace",
"filter": [
"lowercase"
]
}
}
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "edge_ngram_analyzer",
"search_analyzer": "whitespace_analyzer",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
이와 같은 다중 필드 설정을 통해, 동일한 데이터에 대해 여러 검색 전략을 적용할 수 있습니다.
POST /people-new/_doc
{
"name": "David Buss"
}
GET /people-new/_search
{
"query": {
"match": {
"name": "Dav"
}
}
}
GET /people-new/_search
{
"query": {
"term": {
"name.keyword": "david buss"
}
}
}