행궁동 데이터 엔지니어

반응형

 

 

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 검색이 아닌 다른 방식을 찾아보기로 했습니다.

 

Wildcard 검색 대신 ngram으로 인덱싱 적용

 

목표:
First, Last Name으로 구성된 David Buss와 같은 이름이 있을 때

대소문자 구분없이 First, Last Name으로 검색가능하게 하기

 

위처럼 인덱싱하기 위해 name 저장 시 인덱싱 방법(analyzer)에 edge_ngram을 적용했고 2글자 이상부터 검색가능하도록 하기위해 min_gram은 2로 설정했습니다.

검색할때는 Space 기준으로 문자를 나눠서 검색하도록 했습니다. (search_analyzer: whitespace_analyzer)

 

결과적으로 아래 스크린샷(kibana: dev tools) 처럼 검색이 잘되는 것을 확인할 수 있었습니다.

Kibana:  웹 GUI에서 Elasticsearch의 데이터를 조작/조회하고 대시보드를 생성할 수 있는 툴

 

로컬에서 테스트를 해보고 싶은 분들은 아래 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)

 

1. 인덱스 설정(Settings) 설명

a. Analysis Settings

Analysis 설정은 텍스트 데이터를 어떻게 분해하고 처리할지를 정의합니다. 여기서는 두 가지 주요 구성 요소인 필터(Filters)와 분석기(Analyzers)가 설정되어 있습니다.

i. Filters

"filter": { "edge_ngram_filter": { "type": "edge_ngram", "min_gram": 2, "max_gram": 20 } }

edge_ngram_filter:

  • Type: edge_ngram
    • 텍스트의 시작 부분에서부터 일정한 길이의 부분 문자열을 생성합니다.
  • Parameters:
    • min_gram: 2
      • 최소 2자의 n-그램을 생성합니다.
    • max_gram: 20
      • 최대 20자의 n-그램을 생성합니다.

용도: 사용자가 입력한 검색어의 접두사(prefix)에 기반하여 부분 일치 검색을 가능하게 합니다. 예를 들어, "David"라는 단어는 "Da", "Dav", "Davi", "David"와 같은 n-그램으로 분해됩니다.

ii. Analyzers

"analyzer": {
  "edge_ngram_analyzer": {
    "type": "custom",
    "tokenizer": "standard",
    "filter": [
      "lowercase",
      "edge_ngram_filter"
    ]
  },
  "whitespace_analyzer": {
    "type": "custom",
    "tokenizer": "whitespace",
    "filter": [
      "lowercase"
    ]
  }
}
  • edge_ngram_analyzer:
    • Type: custom
    • Tokenizer: standard
      • 표준 토크나이저를 사용하여 텍스트를 단어 단위로 분리합니다.
    • Filters:
      • lowercase: 모든 토큰을 소문자로 변환합니다.
      • edge_ngram_filter: 위에서 정의한 edge n-gram 필터를 적용합니다.
    용도: 인덱싱 시 텍스트를 분석하여 부분 일치 검색을 지원합니다. 예를 들어, "David Buss"라는 이름은 "david"와 "buss"로 분리된 후, 각각의 단어에 edge n-gram 필터가 적용됩니다.
  • whitespace_analyzer:
    • Type: custom
    • Tokenizer: whitespace
      • 공백을 기준으로 텍스트를 단어 단위로 분리합니다.
    • Filters:
      • lowercase: 모든 토큰을 소문자로 변환합니다.
    용도: 검색 시 입력된 검색어를 분석하여 정확한 일치  부분 일치를 지원합니다. 이 분석기는 인덱싱 시 사용된 edge n-gram 분석기와는 별도로 동작합니다.

b. Why Use Separate Analyzers for Indexing and Searching?

  • Indexing Analyzer (edge_ngram_analyzer):
    • 텍스트를 다양한 길이의 n-그램으로 분해하여 인덱싱합니다.
    • 이는 사용자가 입력하는 검색어의 일부와 일치하는 문서를 빠르게 찾을 수 있게 합니다.
  • Search Analyzer (whitespace_analyzer):
    • 검색어를 단순히 소문자로 변환하고 공백을 기준으로 분리합니다.
    • 복잡한 n-gram 분석 없이도 효율적인 검색을 가능하게 합니다.

2. 매핑(Mappings) 설명

"mappings": {
  "properties": {
    "name": {
      "type": "text",
      "analyzer": "edge_ngram_analyzer",
      "search_analyzer": "whitespace_analyzer",
      "fields": {
        "keyword": {
          "type": "keyword",
          "ignore_above": 256
        }
      }
    }
  }
}

a. name 필드 설정

  • Type: text
    • 텍스트 분석 및 전체 텍스트 검색을 지원합니다.
  • Analyzer: edge_ngram_analyzer
    • 인덱싱 시 텍스트를 edge n-gram을 사용하여 분해합니다.
    • 예: "David Buss"  "david", "buss" → 각 단어에 edge n-gram 적용 → "da", "dav", "davi", "david", "bu", "bus", "buss"
  • Search Analyzer: whitespace_analyzer
    • 검색 시 입력된 검색어를 공백을 기준으로 분리하고 소문자로 변환합니다.
    • 예: "David Buss"  "david", "buss"
  • Fields:
    • keyword:
      • Type: keyword
        • 분석되지 않은 원시 데이터를 저장합니다.
        • 정확한 일치 검색을 지원합니다.
      • ignore_above: 256
        • 256자를 초과하는 텍스트는 이 필드에 인덱싱되지 않습니다.
    용도:
    • name.keyword 필드는 정확한 일치 검색을 위해 사용됩니다.
    • 예: 사용자가 전체 이름을 정확히 입력했을 때, 해당 문서를 빠르게 찾을 수 있습니다.

b. Multi-Field (다중 필드) 활용

  • name 필드는 text 타입으로 분석되어 부분 일치 검색에 최적화되어 있습니다.
  • name.keyword 서브 필드는 keyword 타입으로 설정되어 정확한 일치 검색에 사용됩니다.

이와 같은 다중 필드 설정을 통해, 동일한 데이터에 대해 여러 검색 전략을 적용할 수 있습니다.


3. 이 설정이 검색에 미치는 영향

a. 인덱싱 과정

   POST /people-new/_doc
   {
     "name": "David Buss"
   }
  1. 문서 삽입(위 POST 코드)
  2. edge_ngram_analyzer 적용:
    • name 필드: "David Buss"  "david", "buss" → 각 단어에 edge n-gram 적용 → "da", "dav", "davi", "david", "bu", "bus", "buss"
    • name.keyword 필드: "david buss" (소문자로 변환됨)
  3. 인덱싱된 토큰:
    • name:
      • "da", "dav", "davi", "david", "bu", "bus", "buss"
    • name.keyword:
      • "david buss"

b. 검색 과정

i. match 쿼리 사용 시

GET /people-new/_search
{
  "query": {
    "match": {
      "name": "Dav"
    }
  }
}
  • 검색어 분석:
    • "Dav"는 whitespace_analyzer에 의해 소문자로 변환되고 단어로 분리 → "dav"
  • 매칭 과정:
    • 인덱스에 저장된 n-그램 중 "dav"와 일치하는 문서를 찾습니다.
    • "David Buss"의 "david" 토큰 중 "dav"와 일치하여 해당 문서를 반환합니다.

ii. term 쿼리 사용 시

GET /people-new/_search
{
  "query": {
    "term": {
      "name.keyword": "david buss"
    }
  }
}
 
  • 검색어 분석:
    • term 쿼리는 분석되지 않은 정확한 값을 요구합니다.
    • "david buss"와 정확히 일치하는 문서를 찾습니다.
반응형

이 글을 공유합시다

facebook twitter kakaoTalk kakaostory naver band