«Самсунк телефон» вернул ноль результатов. Я нашёл это до того, как клиент.
Тихая потеря продаж
Первый нетривиальный запрос после запуска нового поиска на каталоге в 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 distance — fuzziness: 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-инструмент — в отдельной статье.