Покупатель вводит «холодилник samsung RB37A5000SA». В первом слове пропущена буква, а артикул набран точно. Хороший поиск должен понять, что «холодилник» — это «холодильник», и при этом не пытаться «исправить» артикул до какой-нибудь соседней модели с похожими символами. Одинаково мягкое отношение к обоим фрагментам запроса здесь вредно: слово прощает ошибку, а код модели ошибку прощать почти не должен.
Из этой маленькой сцены вырастает вся задача подбора Typesense под собственный каталог. Сначала нужно решить, какие данные ведут себя как слова, какие как коды, а какие как условия отбора. Потом это решение определит состав индексируемых полей, а состав полей — расход памяти и выбор VPS. Порядок именно такой, а не «у нас сто тысяч товаров, значит, нужно столько-то гигабайт».
Три вида текста в одной карточке товара
Возьмём условную карточку холодильника. В ней есть название, бренд, категория, артикул, цена, цвет, ширина, наличие на складе, описание и несколько десятков технических характеристик. Для поиска это не один тип данных, а как минимум три.
- Обычные слова. Название, бренд, часть описания. Здесь опечатки ожидаемы: «холодилник», «самсунг» вместо «samsung», «двухкамерный» с пропущенной буквой. Пользователь рад, если система поняла его намерение.
- Номера моделей и артикулы. Это короткие последовательности букв и цифр, где одна замена символа означает уже другой товар. Допустимое поведение узкое: найти по точному коду, по началу кода, возможно, по коду без дефисов, но не подсовывать «почти такой же» номер.
- Фильтруемые характеристики. Цена, цвет, ширина, наличие, категория. Их не ищут словами, ими сужают выборку. Опечаток здесь нет вообще: человек нажимает галочку или двигает ползунок.
Если все три группы свалить в одно поле и искать по нему с единой настройкой, получится компромисс, который плохо работает везде. Либо опечатки будут прощаться и в артикулах, либо артикулы будут требовать идеальной точности, и тогда «холодилник» ничего не найдёт. Поэтому в схеме коллекции эти группы лучше разнести по разным полям и по-разному использовать при запросе.
Сценарии словесного поиска
Для слов разумно разобрать несколько типовых запросов и заранее записать, какого результата вы от них ждёте. Это не тесты производительности, а описание поведения.
- Одна опечатка в частом слове. «холодилник» — ожидаем, что в верхней части окажутся холодильники. Если есть фильтр по категории, результат должен остаться внутри неё.
- Опечатка в бренде. «самсунг» или «samsnug» — ожидаем товары нужного бренда. Здесь полезно проверить, не вытесняют ли их товары, где то же слово встречается в описании.
- Неполное слово при наборе. «холодил» — человек ещё печатает. Поведение для последнего слова запроса отличается от поведения для законченных слов, и это стоит проверить отдельно.
- Два слова, одно с ошибкой. «двухкамерный холодилник» — ошибка в одном слове не должна обнулять вклад другого.
- Слово, которого в каталоге нет. Если система слишком охотно «исправляет», она вернёт что-то далёкое. Пустая выдача иногда честнее, чем выдача про другое.
Точные настройки терпимости к опечаткам — сколько ошибок допускается, с какой длины слова и что делать при нехватке результатов — берите из официальной документации Typesense для той версии, которую вы ставите. Здесь они не приводятся намеренно: параметры и значения по умолчанию меняются между версиями, а проверять нужно именно вашу установку. Порядок работы простой: открываете раздел документации про параметры поиска, выписываете параметры, связанные с опечатками и префиксами, и для каждого пишете рядом с ним один из пяти сценариев выше, который он должен менять.
Сценарии артикульного поиска
Артикул ведёт себя иначе. Вот сценарии, которые стоит записать до первой индексации.
- Точный ввод. «RB37A5000SA» должен вернуть эту модель первой, и желательно единственной.
- Ввод в другом регистре. «rb37a5000sa» — ожидаем тот же результат.
- Ввод с разделителями или без. Если в каталоге код хранится с дефисом, а покупатель набирает без него, вы получите промах, которого не должно быть. Решается подготовкой данных: рядом с основным кодом можно хранить нормализованный вариант без разделителей. Это ваше проектное решение, а не требование системы.
- Начало кода. «RB37A» — человек помнит только серию. Ожидаем список моделей серии, а не случайные совпадения по другим полям.
- Код с ошибкой. «RB37A5000SB» вместо «RB37A5000SA». Здесь надо решить, что лучше: показать пустую выдачу или близкую модель. Для запчастей и совместимых аксессуаров близкая модель опасна, потому что покупатель может заказать не то. Для крупной бытовой техники иногда допустимо показать соседние варианты, но явно, а не под видом точного ответа.
Практический вывод: для поля с артикулом терпимость к опечаткам стоит настраивать строже, чем для названия, а при запросе использовать оба поля с разной ролью. Как именно задаются такие настройки на уровне поля и запроса, смотрите в документации вашей версии, не полагаясь на память или чужие примеры.
Что меняется в выдаче, когда ошибка и фильтры встречаются вместе
Опечатки редко живут отдельно. Чаще человек сначала открывает категорию «Холодильники», отмечает ширину до 60 см и наличие, а потом вводит запрос с ошибкой. Для него это один жест, для поиска — две разные операции: сопоставление слов и отбор по условиям.
Полезно мысленно разделить, что делает каждая часть. Фильтры сужают множество допустимых товаров. Нечёткий текстовый поиск ранжирует внутри этого множества. Поэтому опечатка при активных фильтрах обычно даёт более точную выдачу, чем без них: кандидатов меньше, и «похожие, но ненужные» слова встречаются реже. Обратная сторона — риск пустой выдачи. Если из-за фильтров остаётся два десятка товаров, а слово с опечаткой не совпало ни с одним из них, человек увидит ноль, хотя во всём каталоге нужные товары есть.
Покажем это на условном примере, цифры выдуманы только для иллюстрации. В каталоге 100 холодильников. Фильтр «ширина до 60 см» оставляет 12. Запрос «холодилник» с терпимостью к опечаткам проходит по этим двенадцати и возвращает все, так как слово в названиях везде одинаково. Теперь запрос «холодилник samsung RB37A5000SA» с тем же фильтром. Если модель шире 60 см, она отфильтруется, и покупатель увидит другие холодильники Samsung или вообще ничего. Это правильное поведение системы, но неправильное впечатление для человека: он ищет конкретную модель и не понимает, что её скрыл собственный фильтр.
Отсюда два проектных решения для интерфейса, не связанных с VPS, но влияющих на нагрузку.
- Показывайте активные фильтры рядом с результатом и давайте сбросить их одним действием.
- Если запрос похож на артикул, а выдача пуста при активных фильтрах, повторите запрос без них и покажите найденную модель с пометкой, что она не подходит под выбранные условия.
Второй вариант увеличивает число запросов к поиску, поэтому его стоит учесть при расчёте нагрузки, а не добавлять потом «на глаз».
Как состав полей превращается в память
Теперь перейдём к ресурсам. Документация Typesense описывает принцип: поисковый индекс держится в памяти, а копия сырых данных лежит на диске, причём расход памяти зависит от индексируемых полей и распределения слов в них, а неиндексируемые поля могут храниться на диске (Typesense: System requirements). Там же сказано, что опубликованные оценки RAM служат ориентиром и не заменяют замер на собственном каталоге.
Из этого следуют вещи, которые легко упустить, если считать память по числу товаров.
Поле в схеме — это решение о памяти. Каждое индексируемое поле увеличивает индекс. Описание на несколько абзацев занимает в индексе заметно больше места, чем короткий артикул, потому что в нём больше слов. Поэтому вопрос «нужно ли искать по описанию» превращается в вопрос «готовы ли мы платить за это оперативной памятью».
Распределение слов важнее количества документов. Каталог из пятидесяти тысяч товаров с короткими однотипными названиями и каталог из пятидесяти тысяч товаров с длинными разнородными описаниями — разные по нагрузке задачи. Поэтому «сколько товаров» без «какой схемы» остаётся только половиной вводных.
Хранить и индексировать — не одно и то же. Поле, которое нужно только для показа на карточке, например ссылка на изображение или длинный текст для страницы товара, можно не индексировать. Тогда по нему нельзя искать и фильтровать, зато в индексе оно не занимает память. Какие именно параметры поля это определяют, смотрите в разделе документации про схему коллекции.
Три схемы одного каталога
Сравним на условном каталоге три варианта схемы. Это не измерения, а способ мышления: что именно вы сравниваете, когда выбираете между ними.
| Вариант | Индексируемые поля | Что получает покупатель | Что ожидать по памяти |
|---|---|---|---|
| Минимальный | название, бренд, артикул, категория, цена, наличие | Поиск по названию и коду, базовые фильтры | Наименьший индекс из трёх |
| Расширенный | то же плюс цвет, ширина, ещё 5–10 характеристик для фильтров | Подробные фасеты, уточнение выдачи | Больше, особенно если у характеристик много разных значений |
| Полный | всё из расширенного плюс описание и все технические свойства как текст | Поиск по любым словам карточки | Наибольший; нужен замер, оценкой по числу товаров не обойтись |
Порядок сравнения важнее чисел. Начинайте с минимального варианта, замеряйте память на выбранном срезе каталога, затем добавляйте поля группами и замеряйте снова. Тогда вы увидите, какая именно группа полей съедает больше всего, и сможете решить, стоит ли она того.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.
Как замерить: срез каталога и порядок экспериментов
Полный каталог для первых замеров не нужен. Нужен срез, который сохраняет пропорции. Возьмите случайную выборку, например десять процентов товаров, но так, чтобы в ней были представлены все категории и все типы карточек: с короткими и длинными описаниями, с большим и малым числом характеристик. Если взять первые тысячу строк из выгрузки, вы рискуете попасть в одну категорию и получить неверное представление.
Дальше для каждой схемы повторяйте один и тот же цикл.
- Создайте коллекцию с нужной схемой на отдельной тестовой машине или в отдельной коллекции.
- Загрузите срез и дождитесь завершения индексации.
- Запишите потребление памяти процессом Typesense и размер данных на диске, используя обычные средства операционной системы.
- Прогоните набор запросов, о котором ниже, и запишите задержку.
- Увеличьте срез вдвое и повторите, чтобы понять, как память растёт: линейно или быстрее.
Последний шаг особенно ценен. Две точки замера, например срез и удвоенный срез, дают грубую прикидку роста. Она не заменит замер полного каталога, но защищает от главной ошибки: умножить память среза на коэффициент, не проверив, что зависимость действительно близка к линейной.
Об установке самого сервера, которую нужно выполнить до этих экспериментов, подробнее рассказано в материале как установить и настроить Typesense на VPS.
Лёгкие и тяжёлые запросы на одном срезе
Теперь про задержку. Нельзя измерять её на одном «усреднённом» запросе, потому что у поиска по каталогу есть быстрые и дорогие операции. Составьте небольшой набор, который отражает реальное поведение покупателей, и замеряйте каждый класс отдельно.
- Точный артикул без фильтров. Самый простой случай, эталон нижней границы.
- Короткое слово с опечаткой без фильтров. Нечёткое сопоставление по всему индексу; интересно, как оно ведёт себя на частых и редких словах.
- Слово с опечаткой и двумя-тремя фильтрами. Типичный сценарий карточки категории.
- Пустой запрос с фасетами. Открытие страницы категории: нужно посчитать значения характеристик по большому множеству товаров.
- Длинный запрос из нескольких слов. Так пишут люди, которые перечисляют все свойства товара.
- Много фильтров одновременно. Крайний случай: человек отметил десяток галочек в разных группах.
Запись результатов лучше вести не одним числом, а как распределение: типичное время и худшие значения. Покупателя раздражает не среднее время, а редкий медленный ответ именно на тот запрос, который он набрал. Прогоняйте каждый класс множество раз, меняя содержимое запроса, а не повторяя одну и ту же строку, иначе вы измеряете кэш, а не поиск.
Отдельно прогоните набор во время переиндексации. Пересборка коллекции с другой схемой — это момент, когда индекс существует в двух экземплярах, старом и новом. Практическое следствие для памяти очевидно: на время такой операции вам нужен запас сверх обычного размера индекса. Насколько именно, покажет ваш собственный замер, а не общая формула.
Качество выдачи нельзя мерить числом найденных товаров
Соблазн оценивать поиск по количеству результатов понятен: больше совпадений — вроде бы лучше. Но запрос «холодилник» с опечаткой, вернувший пять тысяч товаров из всех категорий, хуже запроса, вернувшего восемьдесят холодильников. А артикульный запрос, который вернул одну точную модель, лучше запроса, вернувшего сорок похожих.
Вместо подсчёта совпадений составьте небольшой набор контрольных пар: запрос и товар, который покупатель должен увидеть. Например, «холодилник samsung RB37A5000SA» и нужная модель. Для каждой пары записывайте позицию этого товара в выдаче и отдельно отмечайте, нет ли в верхней части товаров, которые вводят в заблуждение, например соседней модели с близким артикулом. Такие два показателя, позиция нужного и отсутствие «ложных близнецов», говорят о качестве больше, чем общее число результатов.
Контрольный набор не обязан быть большим. Несколько десятков пар, собранных из реальных обращений в поддержку и из журнала запросов, который вы ведёте в своём магазине, покрывают основные виды ошибок. Каждый раз, когда вы меняете схему или настройки терпимости к опечаткам, прогоняйте набор заново и сравнивайте результат с предыдущим.
От замеров к VPS
Когда у вас есть память индекса для выбранной схемы, прикидка роста и картина задержки, можно выбирать машину. Логика такая: память процесса плюс запас на переиндексацию плюс место для операционной системы и сопутствующих сервисов, плюс диск под копию данных и рост. Опубликованные оценки RAM из документации здесь служат лишь отправной точкой, а решает ваш замер.
У MAATRIX в публичном предложении VPS на NVMe с процессорами AMD EPYC и root-доступом. Линейка тарифов по ресурсам такая: Ba — 1 vCPU, 2 ГБ RAM, 40 ГБ NVMe; Uab — 2 vCPU, 4 ГБ, 80 ГБ; Heka — 4 vCPU, 8 ГБ, 160 ГБ; Maat — 8 vCPU, 16 ГБ, 240 ГБ; Djed — 16 vCPU, 32 ГБ, 320 ГБ (страница аренды VPS). Это описание ресурсов, а не обещание ёмкости: сколько товаров поместится на конкретном тарифе, определяет ваша схема.
Как читать эту линейку применительно к трём схемам выше:
- Минимальная схема на небольшом каталоге может уместиться на младших тарифах, но только если замер это подтвердил, и с запасом на пересборку.
- Расширенная схема с большим числом фасетов чаще требует ступени, где памяти заметно больше, чем индекс занимает в покое.
- Полная схема с индексируемыми описаниями — тот случай, где без замера на срезе выбирать тариф нельзя вообще.
Число vCPU влияет на другое: на то, сколько запросов система обрабатывает одновременно и как быстро идёт индексация. Тяжёлые запросы из набора выше, особенно фасеты по большому множеству, чувствительны к процессору. Поэтому при выборе смотрите не только на RAM: прогоните набор с несколькими параллельными клиентами и посмотрите, как ведёт себя задержка. Если она растёт заметно раньше, чем вы ожидаете по нагрузке, стоит либо упростить схему, либо перейти на тариф с большим числом ядер.
Локации, перечисленные в публичном предложении, — UK, US, RU и FR. Для поиска в интернет-магазине важнее всего близость сервера к приложению, которое к нему обращается, чем к покупателю: запросы идут от бэкенда магазина. Точное наличие образов и операционных систем в нужной локации проверяйте в форме заказа.
Рост каталога и переиндексация как часть плана
Каталог не стоит на месте. Добавляются товары, появляются новые характеристики, и вместе с ними новые поля. Каждый такой шаг меняет и память, и качество выдачи. Полезная привычка — заранее решить, при каких условиях вы пересматриваете схему.
- Когда новое поле нужно для фильтра, а не для поиска словами, не делайте его текстовым поиском без необходимости.
- Когда число уникальных значений характеристики растёт, перепроверьте фасетные запросы на задержку.
- Когда появляются товары с принципиально другой структурой артикулов, повторите артикульные сценарии: нормализация, которая работала раньше, может перестать подходить.
Повторяйте замер на срезе всякий раз, когда меняется состав полей или заметно вырос каталог. Это дешевле, чем обнаружить нехватку памяти во время пересборки в рабочее время магазина.
Что видит покупатель
Вернёмся к человеку из начала. Он набирает «холодилник samsung RB37A5000SA», пропустив букву в первом слове. Слово словесного поля прощает ошибку и относит запрос к категории холодильников. Артикул ищется по полю, настроенному строго, и находит ровно одну модель. Фильтры, если они были включены, не скрывают её молча, а интерфейс сообщает, что модель не подходит под выбранную ширину, и предлагает снять условие. Покупатель кладёт нужный холодильник в корзину и ни разу не задумывается, что в слове была ошибка, а вместо его модели ему никто не показал похожую соседку.