Назад в блог

«Самсунк телефон» вернул ноль результатов. Я нашёл это до того, как клиент.

Тихая потеря продаж

Первый нетривиальный запрос после запуска нового поиска на каталоге в 28 000 SKU: «самсунк телефон».

Не «samsung». Не «самсунг». Именно «самсунк» — палец съехал на одну клавишу вправо от «г» к «к». Поиск вернул ноль результатов. Пользователь не увидел ни одного товара. Он ушёл. Клиент об этом не узнал.

Я нашёл это через девять дней в zero-results логах — до того, как кто-то пожаловался. Решение: fuzzy search в Elasticsearch, настройка которого заняла один день.

Проблема с опечатками в поиске такая: о ней не жалуются. Разочарованный пользователь не пишет в поддержку «ваш поиск не понимает опечатки». Он просто закрывает вкладку. Тихий уход. В аналитике — просто «сессия без покупки». Никакого красного флага, никакого тикета.

Вот почему опечатки надо искать самому. Не ждать жалоб.

Что zero-results лог говорит об опечатках

В любом Elasticsearch-поиске логировать нулевые результаты — обязательная практика. Я писал об этом в контексте продуктового backlog. Сейчас — другой разрез: как из этих же логов читать паттерны опечаток.

После включения расширенного логирования у нас за первые две недели накопилось около 1 800 запросов с нулевым результатом. Я прошёлся по первым 200. Выделилось три класса:

  • Опечатки на одну букву: «наутбук» вместо «ноутбук», «самсунк» вместо «самсунг», «телевзор» вместо «телевизор».
  • Неправильная раскладка: запрос латиницей «ghbyntk» вместо «принтер», или наоборот.
  • Склейки и пропуски: «беспроводные наушник» (без «и»), «клавиатурабеспроводная».

Примерно 40% нулевых результатов в этой выборке приходилось именно на опечатки. Это не теоретические потери — это реальные сессии, которые мы не конвертировали. Для магазина с нормальной конверсией поиска 8–12% это означало вполне конкретные рубли в год.

Как fuzziness работает в Elasticsearch

Elasticsearch поддерживает нечёткий поиск через параметр fuzziness. Он измеряется в количестве допустимых правок (edit distance — вставка, удаление, замена или транспозиция одного символа).

Два варианта настройки:

Фиксированный edit distancefuzziness: 1 или fuzziness: 2. Работает предсказуемо, но одинаково для коротких и длинных слов. Для слова «ок» с fuzziness: 1 вы получите совпадения с «ов», «ог», «ом» — что часто даёт мусорные результаты.

AUTO (рекомендуемый для каталогов)fuzziness: AUTO применяет разный edit distance в зависимости от длины запроса:

  • до 2 символов — точное совпадение;
  • 3–5 символов — 1 правка;
  • от 6 символов — 2 правки.

Для e-commerce каталога AUTO работает разумно из коробки. Короткие запросы не размываются лишними совпадениями, длинные — получают нужную гибкость.

Дополнительно: параметр prefix_length ограничивает поиск с нечёткостью только для символов, начиная с позиции N. Мы поставили prefix_length: 2 — это означает, что первые два символа запроса должны совпадать точно. Снижает вероятность случайных совпадений и немного ускоряет индексацию.

Конфиг нашего query-блока в упрощённом виде:

{
  "multi_match": {
    "query": "самсунк телефон",
    "fields": ["name^3", "brand^2", "description"],
    "fuzziness": "AUTO",
    "prefix_length": 2
  }
}

С этими параметрами «самсунк» матчит «самсунг» через одну замену. Результаты появляются.

Русская раскладка: отдельная боль

В русскоязычном e-commerce есть проблема, которую почти не описывают в документации по Elasticsearch: переключение раскладки клавиатуры.

Пользователь набирает «ghbyntk» — это «принтер» на русской клавиатуре при случайно включённой английской. Или наоборот: пишет «планшет» русскими буквами, но на EN-раскладке у него получается полная абракадабра. Fuzziness тут не поможет — это не опечатка на одну букву, это полностью другой набор символов.

Мы решили это отдельно: на уровне препроцессинга запроса. Перед отправкой в Elasticsearch скрипт проверяет, похоже ли введённое на транслит-паттерн. Если да, пробует конвертировать. Получается разумное слово — отправляем оба варианта в OR-запросе.

Это не Elasticsearch-настройка. Это PHP-слой до запроса. Но без него fuzzy-matching закрывает только 60% проблем с опечатками, не 90.

Что изменилось после включения

Мы включили fuzziness: AUTO + prefix_length: 2 + препроцессинг раскладки поэтапно, чтобы отделить эффекты.

После fuzzy-matching:

  • Процент запросов с нулевым результатом снизился с 22% до 14%.
  • Конверсия поиска (клик по результату → добавление в корзину) выросла с 6.1% до 7.8%.

После препроцессинга раскладки:

  • Нулевых результатов стало 11%.
  • Конверсия поиска: 8.3%.

Итого за 4 недели: нулевые результаты сократились вдвое, конверсия поиска выросла на 36% относительно базовой точки. На магазине с несколькими тысячами заказов в месяц это хорошо считается в рублях.

Важнее: мы не ждали жалобы. Мы исправили до того, как клиент заметил.

Где fuzzy-matching ломается

Fuzzy — не серебряная пуля. Несколько мест, где мы обожглись.

Слишком агрессивный edit distance. С fuzziness: 2 без prefix_length запрос «сок» начинает матчить «бок», «ток», «мок» и другие трёхбуквенные слова. Результат — мусор. prefix_length: 2 частично спасает, но для коротких запросов лучше отключать fuzzy совсем (через fuzziness: 0 ниже определённой длины или MIN_LENGTH guard в query-builder).

Артикулы и SKU. Покупатель ищет «AB-12345» — конкретный артикул. Fuzzy тут разрушителен: вместо точного совпадения он выдаёт товары с похожими артикулами. Для полей sku и article мы выключили fuzziness полностью.

Производительность. Fuzzy-запросы дороже точных. На нашем индексе разница была примерно 40 мс против 12 мс на медиане. Для каталога в 28 000 SKU — приемлемо. Для миллионного индекса потребуется другая стратегия (suggest API, отдельный spelling-correction pass).

Итог

Fuzzy search в Elasticsearch — это не про «умный поиск». Это про деньги, которые уходят молча. Опечатки не генерируют тикеты. Они генерируют закрытые вкладки.

Решение несложное: fuzziness: AUTO, prefix_length: 2, отдельный препроцессинг для раскладки. Но прежде всего — регулярный аудит zero-results лога. Без него вы не знаете, сколько теряете.

Подробнее про Elasticsearch как UX-инструмент — в отдельной статье.