한 개발 팀이 OpenSearch와 SQLite FTS5를 결합하여, 지연 시간을 20ms 미만으로 유지하면서도 검색 결과가 없는(zero-result) 비디오 검색 비율을 11.4%에서 2.1%로 낮췄습니다. 이제 사용자가 “blackpink jenny solo stag”라고 입력해도 빈 목록 대신 정확한 “BLACKPINK Jennie SOLO stage” 결과가 표시됩니다.

변경이 필요했던 이유

한 비디오 호스팅 플랫폼의 검색 로그를 분석한 결과, 반복적인 문제가 발견되었습니다. 라틴 문자(Latin-script) 제목에서 오타가 단 하나만 발생해도 모든 검색 결과가 사라지는 현상이었습니다. 중국어, 일본어, 한국어(CJK) 텍스트의 부분 일치(substring matching) 기능으로 높게 평가받는 SQLite의 FTS5 확장은 퍼지 매칭(fuzzy matching)을 지원하지 않습니다. 이름이나 곡 제목에서 글자 하나만 잘못 입력해도 쿼리가 완전히 깨져버리는 것입니다.

기존 파이프라인은 SQLite를 유일한 인덱스로 사용했습니다. CJK 쿼리는 잘 처리했지만, 라틴 문자 오타에 대한 안전장치는 없었습니다. 따라서 팀은 검증된 FTS5 레이어를 유지하면서도 오타 허용(typo tolerance) 기능을 제공할 수 있는 보완적인 검색 엔진을 찾기 시작했습니다.

OpenSearch 도입 방법

OpenSearch는 최전방 검색 서비스로 작동하며, SQLite는 데이터의 원천(source of truth) 역할을 유지합니다. 두 시스템은 병렬로 실행됩니다. 먼저 OpenSearch가 사용자 쿼리를 수신하고, 충분히 빠르게 응답하면 그 결과를 보여줍니다. 만약 OpenSearch에서 타임아웃이 발생하거나 오류가 생기면, 요청은 SQLite FTS5 인덱스로 폴백(fallback)됩니다. 이러한 "페일 세이프(fail-safe)" 설계 덕분에 네트워크 장애가 발생하더라도 검색창이 빈 상태로 남는 일이 없습니다.

멀티 필드 매핑(Multi-field mapping)

OpenSearch에서는 각 비디오 제목을 세 가지 방식으로 인덱싱합니다:

  • title.std – ASCII folding 기능이 포함된 표준 분석기(standard analyzer)로 처리됩니다. 이는 악센트가 있는 문자를 정규화하고 대부분의 라틴 문자 오타를 처리합니다.
  • title.cjk – 바이그램(bigram, 두 글자 토큰)을 생성하는 CJK 분석기로 처리됩니다. 이를 통해 FTS5가 아시아권 문자에 제공하던 부분 일치 성능을 유지합니다.
  • title.keyword – 정확한 일치(exact-match) 검색 및 정렬을 위해 변경 없이 그대로 저장됩니다.

필드를 분리함으로써 토큰화(tokenisation) 전략을 혼합하지 않고도 각 스크립트에 적합한 분석을 적용할 수 있습니다.

부스트 티어(Boost tiers)

팀은 단일한 거대 쿼리 대신, 결과를 자동으로 순위화하는 계층형 쿼리를 구축했습니다:

  1. title.keyword의 **정확한 구문 일치(Exact phrase matches)**에 가장 높은 부스트를 부여하여, 완벽하게 일치하는 결과가 목록 상단을 차지하도록 합니다.
  2. title.cjk의 **CJK 바이그램 일치(CJK bigram matches)**에는 중간 정도의 부스트를 부여하여 아시아 언어 검색의 품질을 유지합니다.
  3. title.std의 **퍼지 라틴 일치(Fuzzy Latin matches)**에는 낮은 부스트를 부여하여, 정확한 검색 결과를 가리지 않으면서도 오타가 포함된 결과가 나타날 수 있도록 합니다.

이러한 계층적 접근 방식은 튜닝을 간편하게 만듭니다. 하나의 부스트 값만 조정하면 해당 클래스에 속하는 모든 일치 결과의 상대적 중요도를 변경할 수 있습니다.

스마트 퍼지(Smart fuzziness)

제한된 횟수의 문자 수정을 허용하는 퍼지(Fuzziness) 기능은 라틴 필드에만 적용됩니다. CJK의 경우 글자 하나만 바뀌어도 의미가 완전히 달라지는 경우가 많기 때문에 title.cjk에 대한 퍼지 기능은 비활성화했습니다. 라틴 텍스트의 경우, 단어 길이에 따라 허용되는 편집 거리(edit distance)를 조절하는 OpenSearch의 AUTO 퍼지 설정을 사용하여 허용 범위와 관련성 사이의 균형을 맞춥니다.

성능 및 폴백 로직

검색 루틴은 OpenSearch 호출을 try-catch 블록으로 감쌉니다:

  • OpenSearch가 400ms 이내에 응답하면 해당 결과를 표시합니다.
  • 호출 시 예외가 발생하거나 타임아웃을 초과하면, 시스템은 즉시 SQLite FTS5를 대상으로 쿼리를 다시 실행합니다.

이를 통해 네트워크 지연이나 서비스 중단이 사용자 경험을 저해하지 않도록 보장합니다. 검색 지연 시간은 20ms 미만으로 유지되었습니다.

측정 가능한 성과

  • 라틴 문자 쿼리의 검색 결과 없음(Zero-result) 비율이 **11.4%**에서 **2.1%**로 감소했습니다.
  • CJK 쿼리의 검색 품질은 변함없이 유지되어, 새로운 CJK 분석기가 기존 FTS5 인덱스의 강점을 그대로 보존했음을 확인했습니다.
  • 엔드 투 엔드(End-to-end) 지연 시간이 목표치인 20ms보다 훨씬 낮게 유지되어, 추가된 레이어가 UI 속도를 저하시키지 않았음을 증명했습니다.

교훈 및 트레이드오프(Trade-offs)

  • 폴딩(Folding)과 퍼지(Fuzziness)의 분리 – 폴딩(문자 정규화)과 퍼지(오타 처리)는 서로 다른 문제를 해결합니다. 이를 별도의 필드에 유지함으로써 의도치 않은 상호작용을 방지할 수 있습니다.
  • 검색 인덱스를 데이터의 원천(source of truth)으로 취급하지 말 것 – SQLite가 표준 저장소(canonical store)로 남고, OpenSearch는 파생되어 갱신 가능한 뷰(view) 역할을 합니다. 이는 인덱스 드리프트(index drift)를 방지하고 장애 발생 후 복구를 단순화합니다.
  • 부스트 티어는 튜닝을 단순화함 – 관련 있는 일치 결과들을 하나의 부스트 계수로 그룹화하면 조정해야 할 파라미터의 수를 줄일 수 있습니다.

향후 주목할 점

이번 실험은 SQLite FTS5의 검증된 CJK 기능을 유지하면서도, 가벼운 OpenSearch 레이어를 추가함으로써 다국어 동영상 제목에 대한 오타 허용도를 획기적으로 개선할 수 있음을 증명합니다. 검색 관련성이 시청 시간에 직접적인 영향을 미치는 플랫폼의 경우, 이러한 개선은 실질적인 사용자 경험의 이점으로 이어집니다.