Як я додав пошук із толерантністю до помилок за допомогою OpenSearch та CJK

Запити з нульовим результатом вбивали наш час перегляду.

Протягом року TopVideoHub використовував SQLite FTS5 для пошуку. Це працювало для точних збігів, але давало збої, коли користувачі припускалися помилок.

Люди шукали "demon slyer" замість "demon slayer". Вони додавали зайві пробіли в японських або корейських назвах. Оскільки FTS5 шукає точні токени, одна помилка означала нуль результатів. Користувачі не намагалися шукати знову — вони просто йшли.

Я переніс пошук за назвами в OpenSearch. Ось як я вирішив цю проблему для багатомовної аудиторії.

Проблема зі стандартною fuzziness

Більшість туторіалів радять використовувати "fuzziness" (розмитість), щоб виправити помилки. Це працює для англійської, але не спрацьовує для китайської, японської та корейської (CJK) мов.

  • Редагувальна відстань (edit distance) погано підходить для CJK. Помилка в CJK часто означає використання зовсім іншого символу.
  • Стандартна fuzziness створює семантичне сміття. Вона може пов'язати "fire" із "water", оскільки вони відрізняються лише на одну зміну.
  • Токенізація є складною. У CJK-мовах між словами не використовуються пробіли.

Рішення: багатопольовий підхід

Я перестав ставитися до всього тексту однаково. Я створив одне логічне поле назви, яке індексує дані трьома різними способами:

  • Latin та Romaji: Я використав стандартну токенізацію з увімкненою fuzziness. Я встановив довжину префікса (prefix length) рівною 1. Це гарантує, що "demon" відповідатиме "demn", але "lemon" не відповідатиме "demon".
  • CJK текст: Я використав CJK bigram analyzer та ICU normalizer. Я вимкнув fuzziness. Замість цього я встановив поріг мінімальної відповідності (minimum match threshold) на рівні 70%.
  • Autocomplete: Я використав поле edge-ngram для реалізації результатів пошуку під час введення (search-as-you-type).

Архітектура та безпека даних

Я залишив SQLite як єдине джерело істини. OpenSearch виступає як швидкий індекс, який можна перезібрати.

  • Я використовую PHP для масового оновлення даних в OpenSearch пачками (batches).
  • Я ніколи не запускаю індексацію під час запиту користувача.
  • Я запускаю Python-скрипт для перевірки "дрейфу" (drift). Це гарантує, що кількість записів в OpenSearch збігається з кількістю в SQLite.

Результати

Зміни були колосальними:

  • Кількість запитів із нульовим результатом впала з 14% до менш ніж 3%.
  • Пошукові сесії стали довшими, оскільки користувачі миттєво знаходили те, що шукали.
  • Затримка (latency) залишається низькою — близько 40 мс.

Якщо ви працюєте з багатомовною аудиторією, пам'ятайте: толерантність до помилок та пошук у CJK — це дві різні проблеми. Вам потрібні два різні рішення.

Джерело: https://dev.to/ahmet_gedik778845/how-i-added-typo-tolerant-video-title-search-with-opensearch-and-cjk-3e5d