Elastic Search

[Elasticsearch] 자동완성

꾸준함. 2024. 6. 12. 18:00

자동완성 기능

  • 서비스에서 사용자가 입력 중인 단어 낱말의 나머지 부분을 예측해서 제안하는 기능
  • 영어 동의어로는 autocomplete, typeahead, instant search
  • 과거에는 단순하게 찾고자 하는 검색어의 나머지 부분을 제안하는 수준 (LIKE 검색)
    • 최근에는 사용자의 니즈를 충족시키기 위해 부가정보를 넣어서 고도화하는 추세

 

자동완성 기능을 구현하기 위해 필요한 조건

 

1. 자동완성 후보 키워드를 위한 데이터 셋

  • 사용자의 최근 검색어 목록
  • 인기 키워드 후보군

 

2. 제한된 개수 및 길이

  • 키워드의 길이가 너무 길지 않아야 함
  • 제안하는 키워드의 개수가 반드시 많을 필요 없음

 

3. 요청에 대한 빠른 응답

  • 응답할 데이터 후보군을 캐싱하여 100ms 이내로 응답

 

4. 색인 시 고려사항

  • 자소분리 기능
  • 색인량이 상대적으로 적고 변경이 많지 않은 경우 유리

 

5. 기능적 요구사항

  • 오타를 정타로 추천할 수 있는 기능
  • 키워드의 중간에서 매칭되거나, 키워드가 아예 매칭이 되지 않는 경우도 대응이 가능해야 할 수도 있음

 

자동완성 품질 평가

  • 품질 평가는 어떤 지표에 중점을 두느냐에 따라 나뉨
    • 순위의 품질에 중점을 둔 지표
    • 사용자의 노력이 얼마나 절약되었는지에 중점을 둔 지표

 

1. 순위의 품질에 중점을 둔 지표

  • 사용자 마지막 입력 시 클릭한 아이템의 순위 기반

 

2. 사용자의 노력이 얼마나 절약되었는지에 중점을 둔 지표

  • MKS(Minimum Keystroke Length)
  • e-Saved(Expected Saved)
  • p-Saved(Perceived Saved)

 

2.1 MKS

  • 사용자가 대상 쿼리를 선택할 때까지 수행해야 할 작업 수
  • 이때 문자 입력과 대상 쿼리의 위치에 도달하기 위해 누르는 수고를 모두 포함
  • k번째 키 입력에서 idx번째 위치에 있는 쿼리를 선택한 경우 (k + idx)로 계산
    • ex) 사용자가 fa 입력했을 때 첫 번째로 노출된 facebook 클릭했다면 입력한 수(2) + 첫 번째 노출된 키워드 클릭(1) = 3

 

2.2 e-Saved

  • 쿼리 자동 완성 기능이 사용자의 시간을 얼마나 절약해 주는지 측정하는 척도
  • 사용자가 절약한 실제 문자 수를 측정하는 객관적인 지표
  • 사용자가 완성되지 않은 쿼리를 입력할 때, 자동 완성 제안이 제공되어 입력을 완료해며 해당 제안을 통해 절약된 문자 수를 계산
    • ex) 사용자가 "Elastics"까지 입력했을 때 "Elasticsearch"라는 제안이 나타난다면, "earch"라는 5개의 문자가 절약되며 이런 식으로 절약된 문자 수를 전체 문자 수와 비교하여 백분율로 표현할 수 있음
    • (1 - 8/13) * 100 = 약 38.46%

 

e-Saved

 

2.3 p-Saved

  • 키 입력 측면에서 절약된 작업량을 측정하기 위한 척도
  • 사용자 경험을 더 직접적으로 반영하는 지표로 사용자가 느끼는 효율성을 측정
  • p-Saved는 주관적인 사용자 피드백과 사용 패턴에 기반
  • 사용자가 쿼리를 제출할 때까지 입력을 건너뛸 수 있는 예상 문자 비율을 계산
  • 사용자가 긴 쿼리를 사용할 때 자동완성 검색의 이점이 드러나기 때문에 p-Saved 지표로 평가하는 것이 의미 있음

 

자동완성을 위한 로깅

  • 일반적으로 검색 쿼리 로그는 사용자 ID, timestamp, 제출된 쿼리 및 관련 검색 결과를 포함하지만 자동완성에 필요한 검색창에 입력한 순차적인 키 입력인 접두어와 그에 따른 자동완성 제안이 포함되지 않은 경우가 많음
  • 실제 사용자의 행동을 잘 분석하고 이해하기 위해서는 전체 자동완성 프로세스에서 각 키 입력과 관련 시스템 응답에서 사용자와 자동완성 엔진 간의 상호작용을 기록하는 고도화된 자동완성 로그를 도입하여 분석할 필요가 있음
    • 제출된 각 쿼리에 대해 기존 검색 쿼리 로그에는 하나의 레코드만 존재하지만
    • 자동완성 로그에서는 제출된 각 쿼리가 사용자가 검색창에 입력한 첫 번째 키 입력부터 최종 제출된 쿼리까지로 정의되는 자동완성 세션과 연결됨
    • 각 자동완성 세션에 기록된 정보에는 사용자가 입력한 각 키 입력, 키 입력의 timestamp, 접두사(prefix)에 해당하는 상위 n개 추천 쿼리, 사용자 ID, 최종 클릭한 쿼리 포함

 

Elasticsearch가 제공하는 자동완성 API 없이 자동완성을 구현한다면

  • 충분히 가능하지만 단점들이 존재하므로 Elasticsearch가 제공하는 자동완성 API를 사용하는 것을 권장

 

1. Trie 자료구조

 

Elasticsearch 없이 자동완성을 구현한다면 Trie 자료구조를 사용할 수 있습니다.

  • 효과적으로 탐색이 가능하며 트리 기반으로 루트 노드부터 자식 노드들을 따라가면서 접두사 기반으로 매칭되는 결과를 찾을 수 있음
  • 단어의 길이가 n일 때 단어 삽입의 시간 복잡도는 O(n)
  • 검색할 접두사의 길이를 m이라고 할 때, 검색의 시간 복잡도는 O(m)이지만 접두사에 해당하는 Trie의 노드까지 이동한 이후에는 해당 노드에서부터 모든 단어를 탐색하므로 단어의 개수에 비례하지 않음

 

장점

  • 문자열을 하나씩 전부 비교하는 것보다 효율적이고 빠름

 

단점

  • prefix 기반이기 때문에 공간복잡도가 문제 될 수 있음
    • O(m)인 시간복잡도를 달성하기 위해 다음 문자를 가리키는 노드가 필요
    • ex) 알파벳에 대해 트라이를 형성할 경우 a~z인 총 26개의 포인터 배열을 지니고 있어야 함

 

https://ko.wikipedia.org/wiki/%ED%8A%B8%EB%9D%BC%EC%9D%B4_%28%EC%BB%B4%ED%93%A8%ED%8C%85%29

 

2. Map 자료구조

 

사용자의 입력을 key, 그에 대한 응답값을 value로 저장하는 Map 자료구조를 사용할 수 있습니다.

 

장점

  • Hash 충돌만 없다면 자동완성을 위한 검색 속도가 O(1)에 가까울 수 있음

 

단점

  • 정확한 일치 검색에는 효과적일 수 있지만 중간에서 일치하는 부분 일치 검색에는 적합하지 않음
  • 저장된 키의 개수에 따라 메모리 사용량이 증가
  • 동일한 prefix를 가진 다른 값들을 중복해서 저장해야 하는데 key-value 쌍인 Map 자료구조는 하나의 키에 대응하는 값만 저장할 수 있으므로 부분 일치 검색을 효율적으로 처리하기 어려움
    • ex) "trie"와 "tripod"이라는 두 단어가 있을 때, "tri"이라는 접두사로 시작하는 모든 단어를 검색하기 위해서는 "tri" 키에 대응하는 값을 찾아야 함

 

3. Redis

in-memory DB를 사용해서 자동완성을 구현하는 것도 충분히 가능합니다.

 

장점

  • 빠르게 응답을 받을 수 있음

 

단점

  • 자동완성 API에 조건에 따른 다양한 필터 혹은 정렬 기능이 들어가게 되면 고려사항이 많아짐

 

Elasticsearch가 제공하는 자동완성 API

  • ngram
  • edge_ngram
  • search_as_you_type
  • prefix_query
  • completion_suggester

 

1. ngram

  • 단어의 일부만 가지고 검색할 수 있게 나눈 단어의 부위로 자동완성의 특성상 일부의 단어를 가지고 응답을 반환해야 하기 때문에 적합한 자료구조
  • 1글자 단위로 나누면 unigram, 2글자 단위로 나누면 bigram, 3글자 단위로 나누면 trigram
  • Elasticsearch에서는 ngram을 지원하기 위해 tokenizer 지원 (ngram)
  • ngram의 글자 단위가 많아지면 색인해야 하는 글자 수가 많아지기 때문에 주의 필요

 

1.1 ngram 옵션 설명

  • min_gram: 최소값이며 default 값은 1 즉 unigram
  • max_gram: 최대값이며 default 값은 2 즉 bigram
  • token_chars: 토큰에 포함되어야 하는 문자 타입을 정의하며 default로는 모든 타입 수용
    • letter: 문자
    • digit: 숫자
    • whitespace: 빈칸이나 개행문자
    • punctuation: 마침표(.), 쉼표(,) 등
    • symbol: $와 같은 특수 기호

 

1.2 ngram 인덱스 생성 예시

 

 

 

부연 설명

  • Elasticsearch에서 제공하는 ngram 토크나이저가 아닌 커스텀 토크나이저인 ngram_tokenizer 정의
    • "type": 토크나이저 유형으로 ngram 지정
    • "min_gram": 생성할 ngram의 최소 길이를 지정하며 여기서는 bigram
    • "max_gram": 생성할 ngram의 최대 길이를 지정하며 여기서는 trigram
    • "token_chars": 토큰을 생성할 때 고려할 문자 유형을 지정하며 여기서는 문자와 숫자만 포함

 

1.3 ngram 분석

 

 

부연 설명

  • token_chars를 letter와 digit으로 지정했으므로 공백 문자와 특수 기호인 ! 무시
  • min_gram을 bigram으로 설정하였으므로 2는 숫자임에도 unigram이므로 결과에 나타나지 않음

 

2. edge_ngram

  • ngram을 사용하더라도 대부분은 앞에서부터 검색을 하는 경우가 많음
  • 텀 앞쪽의 ngram 만 저장하기 위해서는 edge_ngram 토크나이저를 사용
  • 보통 자동완성에서도 앞에서부터 매칭된 결과를 보여주기 때문에 ngram보다 색인되는 토큰이 줄어듦

 

2.1 edge_ngram 옵션 설명

  • ngram 옵션과 동일하므로 생략

 

2.2 edge_ngram 인덱스 생성 예시

 

 

 

부연 설명

  • 1.2와 달라진 점은 tokenizer type을 ngram에서 edge_ngram으로 대체했다는 점

 

2.3 edge_ngram 분석

 

 

부연 설명

  • 맨 앞을 기준으로 ngram을 진행하므로 색인되는 토큰이 현저히 줄어든 것을 확인할 수 있음

 

3. search-as-you-type

  • 자동완성을 위한 데이터 타입 제공
  • 전체 색인된 텍스트와 부분적으로 일치하는 쿼리를 매칭시키기 위한 효율적인 서브필드 제공
  • prefix 일치와 중간일치 제공

 

3.1 search-as-you-type 옵션 설명

  • my_field: analyzer 지원하며, 설정 안 할 경우 기본 standard analyzer로 동작
  • my_field._2gram: 크기 2의 shingle token filter로 wrapping
  • my_field._3gram: 크기 3의 shingle token filter로 wrapping
  • my_field._index_prefix: my_field._3gram + edge ngram token filter로 wrapping

 

shingle token filter

  • ngram과 edge_ngram은 둘 다 하나의 단어로부터 토큰을 확장하는 토큰 필터
  • 문자가 아니라 단어 단위로 구성된 묶음을 shingle이라고 함

 

3.2 search-as-you-type 색인


 

부연 설명

"서울시 강남구 청담동" 기준으로 설명

  • my_field는 standard analyzer로 인해 ["서울시", "강남구", "청담동"]으로 색인
  • my_field._2gram은 ["서울시 강남구", "강남구 청담동"]으로 색인
  • my_field._3gram은 ["서울시 강남구 청담동"]으로 색인
  • my_field._index_prefix은 ["서", "서울", "서울시", "서울시 강", "서울시 강남", "서울시 강남구", "서울시 강남구 청", "서울시 강남구 청담", "서울시 강남구 청담동"]으로 색인

 

3.3 search-as-you-type 검색

  • multi_match와 bool_prefix를 같이 사용하는 것이 효과적
    • 실제로 bool_prefix 없이 서로 검색하면 hits가 빈 배열로 나옴
    • multi_match의 경우 strict 하지 않기 때문에 "서울시 강"으로 검색해도 둘 다 나옴
    • 만약 엄격한 검색 결과를 원한다면 match_phrase_prefix 사용하는 것을 권장

 

3.3.1 multi_match + bool_prefix

 

 

 

3.3.2 match_phrase_prefix

 

 

4. prefix query

  • 특정 필드에 요청한 prefix를 포함한 문서를 응답해 주는 쿼리

 

4.1 prefix query 옵션 설명

색인 시 index_prefixes 하위에 다음 파라미터 설정 가능

  • min_chars: 0보다 큰 값으로 default 값은 2
  • max_chars: 20보다 작은 값으로 default 값은 5

 

4.2 prefix query 색인


 

부연 설명

  • 쿼리의 응답 성능을 높이기 위해 색인 시 파라미터를 추가하면 효과적
  • 앞서 설명한 min_chars, max_chars 참고

 

4.3 prefix query 고려사항

  • strict 한 조건이 맞아야 하기 때문에 유연성이 떨어질 수 있음
  • fuzzy 검색을 지원하지 않기 때문에 오타 처리 불가
  • 텍스트가 많은 경우 리소스 소모량이 높을 수 있음

 

5. Suggester

  • 자동완성은 근본적으로 사용자가 입력하는 동안에 응답이 바로 이루어져야 하기 때문에 빠르게 응답이 되어야 함
    • 일반적인 검색 API의 경우 많은 term들과 해당 term이 얼마나 존재하는지를 체크해야 하기 때문에 성능상 제약 존재

 

  • suggester API는 색인이 된 대상을 FST(유한상태 변환기)라는 데이터 구조로 만들어 in-memory에 올려 일반 검색 API보다 응답이 빠르며 in-memory에 올리기 때문에 변경이 적은 것이 좋음
    • 단, suggester api는 중간 키워드로는 검색이 안되기 때문에 검색을 원하는 대상을 배열로 지정하는 것이 필요
    • ex) ["서울시 강남구 청담동", "강남구", "강남구 청담동", "청담동"]

 

https://www.elastic.co/kr/blog/you-complete-me

 

6. Completion Suggester

  • 응답 속도가 빨라야 하는 경우 사용하기 좋은 자동완성 API
  • 색인 데이터 구조를 빠르게 조회하기 위해 in-memory에 올려놓음
  • 키워드에 대한 가중치(weight)를 부여할 수 있음

 

6.1 Completion Suggester 옵션 설명

  • analyzer: 색인 시 분석기
  • search_analyzer: 검색 시 분석기
  • preserve_separators: 띄어쓰기를 유지할지 여부를 나타내며 default 값은 true
  • max_input_length: 색인하는 데이터가 증가되는 것을 막기 위한 최대 input의 길이를 제한하며 default 값은 50

 

6.2 Completion Suggester 색인


 

부연 설명

  • 보통 구체적인 지역명을 검색하므로 [동, 구, 도] 순으로 가중치 부여

 

6.3 Completion Suggester 검색


 

부연 설명

  • _source: 소스에서 보고 싶은 필드만 필터링
  • size: 반환 개수
  • _source와 size 옵션을 적절히 설정하여 최대한 빠르게 응답받는 것이 중요
    • 적을수록 네트워크 트래픽이 적어지고 응답 속도가 빨라짐

 

6.4 Completion Suggester 오타 처리

  • fuzzy 검색을 사용하여 오타에 대해서 고려한 결과 반환
  • fuzziness 옵션을 통해 Edit Distance 설정

 

 

6.5 Completion Suggester Context 반영

  • 자동완성 내 특정 기준에 따라 boosting 하거나 필터링하는 조건을 넣는 방법
    • ex) 영화 목록을 자동완성에 넣을 때, 특정 장르의 영화를 부스팅
    • ex) 음식점을 자동완성에 넣을 때 현재 gps 근처 음식점만 나오도록 함 (geo location 사용)

 

 

자동완성 시 클라이언트 고려사항

  • 자동완성 기능은 일반 검색 대비 트래픽이 많기 때문에 클라이언트 단에서 추가적으로 고려할 사항들이 있음
    • ex) "아이폰"이라는 검색어의 경우 일반적인 검색으로는 한 번의 검색이지만 자동완성 기능으로는 자소단위를 포함할 경우 최대 "ㅇ,ㅏ,ㅇ,ㅣ,ㅍ,ㅗ,ㄴ"으로 총 7번의 API 요청이 이루어질 수 있음

 

1. 쓰로틀링(throttling)

  • 클라이언트의 요청을 제한하는 메커니즘
  • API 요청의 빈도를 제한하여 서버에 과부하를 방지하는 기능
  • 쓰로틀링은 일정한 시간 간격으로 API 요청을 제한하거나 일정한 요청량을 초과하는 경우에 대한 처리 수행 가능

 

2. 디바운싱(debouncing)

  • 사용자의 입력을 처리하기 전 일정 시간 동안 대기하는 메커니즘
  • 사용자가 연속해서 입력을 하는 경우 중복된 요청 방지 가능
  • ex) 사용자가 검색어를 입력할 때마다 API 요청을 보내는 대신, 일정 시간 동안 사용자의 입력이 없을 때에만 API 요청을 보내는 방식
    • onkeyup 이벤트 발생 이후 일정 시간 동안 onkeypress 이벤트가 발생하지 않을 경우 요청을 보내는 방식

 

3. 로컬 스토리지 사용

  • 자동완성에서 일반적으로 많이 쓰이는 사용자의 최근 검색어의 경우 클라이언트 단에서 로컬 스토리지를 사용할 수 있음
  • 로컬 스토리지를 사용하면 서버에 매번 요청을 보내지 않아도 되기 때문에 서버 부하를 줄일 수 있고 사용자 경험 향상도 도모할 수 있음
  • 또한, 로컬 스토리지는 일반적으로 빠른 Read/Write 속도를 제공하기 때문에 검색어를 빠르게 로드하고 갱신할 수 있음

 

3.1 로컬 스토리지 사용 시 주의할 점

  • 사용자 개인정보는 마스킹해서 저장
  • 사용자의 검색 기록이 길어질 경우 용량을 초과할 수 있으므로 필요한 경우 검색 기록을 관리하고 정리하는 로직 추가 필요
  • 로컬 스토리지는 사용자의 "로컬" 브라우저에 저장되기 때문에 다른 기기나 브라우저에는 동기화되지 않음
    • 여러 기기에서 동기화되길 원한다면 서버에 최근 검색어를 저장하고 동기화하는 메커니즘 추가 필요

 

참고

  • 패스트 캠퍼스 -  고성능 검색 엔진 구축으로 한 번에 끝내는 Elasticsearch
반응형

'Elastic Search' 카테고리의 다른 글

[Elasticsearch] Fuzzy 쿼리  (0) 2024.06.19
[Elasticsearch] 스크립트 쿼리  (0) 2024.06.13
[Elasticsearch] 집계  (3) 2024.06.09
[Elasticsearch] 검색  (0) 2024.06.07
[Elasticsearch] 분석기(analyzer)  (3) 2024.06.07