Узнайте, как добавить Meilisearch в бэкенд для быстрого, терпимого к опечаткам поиска: установка, индексирование, ранжирование, фильтры, безопасность и основы масштабирования.

Серверный поиск означает, что запрос обрабатывается на вашем сервере (или в выделённом сервисе поиска), а не в браузере. Ваше приложение отправляет запрос на поиск, сервер выполняет его по индексу и возвращает ранжированные результаты.
Это важно, когда ваш набор данных слишком велик, чтобы отправлять его клиенту, когда нужна согласованная релевантность на разных платформах или когда контроль доступа обязателен (например, внутренние инструменты, где пользователи должны видеть только разрешённые записи). Это также стандартный выбор, если вы хотите собирать аналитику, логировать события и иметь предсказуемую производительность.
Люди не думают о движке поиска — они оценивают опыт. Хороший «мгновенный» поиск обычно означает:
Если чего‑то не хватает, пользователи будут пробовать другие запросы, больше скроллить или вообще бросить поиск.
Эта статья — практическое руководство по созданию такого опыта с Meilisearch. Мы пройдём через безопасную установку, структуру и синхронизацию индексируемых данных, настройку релевантности и правил ранжирования, добавление фильтров/сортировки/фасетов и рассмотрим безопасность и масштабирование, чтобы поиск оставался быстрым по мере роста приложения.
Meilisearch хорошо подходит для:
Цель: результаты, которые кажутся мгновенными, точными и заслуживающими доверия — без превращения поиска в большой инженерный проект.
Meilisearch — это поисковый движок, который вы запускаете рядом с приложением. Вы отправляете ему документы (товары, статьи, пользователей или заявки в поддержку), и он строит индекс, оптимизированный для быстрого поиска. Ваш бэкенд (или фронтенд) затем запрашивает Meilisearch через простой HTTP API и получает ранжированные результаты за миллисекунды.
Meilisearch фокусируется на функциях, которые ожидают от современного поиска:
Он сделан так, чтобы быть отзывчивым и «прощать» ошибки, даже когда запрос короткий, слегка неверный или неоднозначный.
Meilisearch не заменяет вашу основную базу данных. База данных остаётся источником правды для записей, транзакций и ограничений. Meilisearch хранит копию тех полей, которые вы выбрали сделать доступными для поиска, фильтрации или отображения.
Хорошая ментальная модель: база данных — для хранения и обновления данных, Meilisearch — чтобы быстро их находить.
Meilisearch может быть очень быстрым, но это зависит от нескольких практических факторов:
Для небольших и средних наборов данных часто хватает одной машины. По мере роста индекса вам нужно будет продуманно выбирать, что индексировать и как поддерживать синхронизацию — темы для следующих разделов.
Прежде чем что‑то устанавливать, решите, что именно вы собираетесь искать. Meilisearch будет казаться «мгновенным» только если ваши индексы и документы соответствуют тому, как люди просматривают ваше приложение.
Сначала перечислите сущности, по которым будут искать: обычно products, articles, users, help docs, locations и т.д. Во многих приложениях самый простой подход — один индекс на тип сущности (например, products, articles). Это делает правила ранжирования и фильтры предсказуемыми.
Если ваш UX подразумевает поиск по разным типам в одном поле («искать всё»), вы всё ещё можете держать отдельные индексы и объединять результаты на бэкенде или создать отдельный «глобальный» индекс позже. Не сваливайте всё в один индекс, если поля и фильтры не согласованы.
Каждый документ нуждается в стабильном идентификаторе (primary key). Выберите такое поле, которое:
id, sku, slug)По форме документа предпочитайте плоские поля, когда это возможно. Плоская структура проще для фильтрации и сортировки. Вложенные объекты допустимы, когда они представляют тесно связанное неизменяемое сочетание (например, объект author), но избегайте глубокой иерархии, копирующей всю реляционную схему — документы для поиска должны быть оптимизированы для чтения, а не повторять структуру БД.
Практичный способ проектирования документов — присвоить каждому полю роль:
Это предотвращает распространённую ошибку — индексировать поле «на всякий случай» и потом удивляться, почему результаты шумные или фильтры медленные.
«Язык» в ваших данных может означать разные вещи:
lang: "en")Решите заранее, будете ли вы использовать отдельные индексы для каждого языка (просто и предсказуемо) или один индекс с полями языка (меньше индексов, больше логики). Подход зависит от того, ищут ли пользователи в одном языке за раз и как вы храните переводы.
Запустить Meilisearch просто, но «безопасно по умолчанию» требует нескольких решений: где его деплоить, как хранить данные и как обращаться с master key.
Хранилище: Meilisearch записывает индекс на диск. Разместите директорию данных на надёжном постоянном носителе (не на эфемерном контейнерном хранилище). Планируйте ёмкость под рост: индексы могут быстро увеличиваться при больших текстовых полях и множестве атрибутов.
Память: выделите достаточно RAM, чтобы поддерживать отзывчивость при нагрузке. При свопинге производительность падает.
Бэкапы: бэкапьте директорию данных Meilisearch (или снимки на уровне хранилища). Обязательно протестируйте восстановление; бэкап, который нельзя восстановить, — просто файл.
Мониторинг: отслеживайте CPU, RAM, диск и I/O. Логи и ошибки тоже должны попадать в систему мониторинга. Минимум — алерт при падении процесса или нехватке места на диске.
Всегда запускайте Meilisearch с master key вне локальной разработки. Храните его в секретном менеджере или в зашифрованном хранилище переменных окружения (не в Git, не в открытом .env).
Пример (Docker):
docker run -d --name meilisearch \
-p 7700:7700 \
-v meili_data:/meili_data \
-e MEILI_MASTER_KEY="$(openssl rand -hex 32)" \
getmeili/meilisearch:latest
Также подумайте о сетевых правилах: привязывать к приватному интерфейсу или ограничить входящие подключения так, чтобы только ваш бэкенд мог обращаться к Meilisearch.
curl -s http://localhost:7700/version
Индексирование в Meilisearch асинхронно: вы отправляете документы, Meilisearch ставит задачу в очередь, и только после успешного выполнения задачи эти документы становятся доступными для поиска. Обращайтесь с индексированием как с системой задач, а не как с одиночным запросом.
id).curl -X POST 'http://localhost:7700/indexes/products/documents?primaryKey=id' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_WRITE_KEY' \
--data-binary @products.json
taskUid. Ожидайте, пока его статус не станет succeeded (или failed).curl -X GET 'http://localhost:7700/tasks/123' \
-H 'Authorization: Bearer YOUR_WRITE_KEY'
curl -X GET 'http://localhost:7700/indexes/products/stats' \
-H 'Authorization: Bearer YOUR_WRITE_KEY'
Если количество не совпадает, не делайте догадок — сначала проверьте детали ошибки задачи.
Группировка нужна, чтобы задачи были предсказуемы и восстанавливались после ошибок.
addDocuments работает как upsert: документы с тем же первичным ключом обновляются, новые — вставляются. Используйте это для обычных обновлений.
Делайте полную переиндексацию, когда:
Для удаления явно вызывайте deleteDocument(s), иначе старые записи могут остаться.
Индексирование должно быть повторяемым. Ключ — стабильные id.
taskUid вместе с id пакета/задачи и повторяйте попытки, ориентируясь на статус задачи.Перед продуктивными данными индексируйте небольшой набор (200–500 элементов), соответствующий реальным полям. Пример: набор products с id, name, description, category, brand, price, inStock, createdAt. Это достаточно, чтобы проверить поток задач, количество и поведение обновления/удаления — без ожиданий при большом импорте.
«Релевантность» — это просто: что показывается первым и почему. Meilisearch делает это настраиваемым без необходимости строить собственную систему скоринга.
Две настройки определяют, как Meilisearch использует ваше содержимое:
searchableAttributes: поля, в которых Meilisearch ищет при вводе запроса (например: title, summary, tags). Порядок важен: ранние поля считаются более значимыми.displayedAttributes: поля, возвращаемые в ответе. Это важно для приватности и размера полезной нагрузки — если поле не отображается, оно не отправляется назад.Практическая отправная точка — сделать поисковыми несколько полей с высоким сигналом (title, ключевой текст) и показывать только те поля, которые нужны UI.
Meilisearch сортирует подходящие документы, используя ranking rules — конвейер «тай‑брейкеров». Концептуально он предпочитает:
Вам не нужно знать все внутренности, чтобы эффективно настраивать: достаточно решить, какие поля важны и когда применять кастомную сортировку.
Цель: «Совпадения в title должны побеждать.» Поместите title первым:
{
"searchableAttributes": ["title", "subtitle", "description", "tags"]
}
Цель: «Новые материалы должны быть выше.» Добавьте сортируемое поле и сортируйте при запросе (или задайте кастомное ранжирование):
{
"sortableAttributes": ["publishedAt"],
"rankingRules": ["sort", "typo", "words", "proximity", "attribute", "exactness"]
}
Затем запросите:
{ "q": "release notes", "sort": ["publishedAt:desc"] }
Цель: «Продвигать популярные товары.» Сделайте popularity сортируемым и сортируйте по нему при необходимости.
Выберите 5–10 реальных запросов. Сохраните верхние результаты до изменений, затем сравните после.
Пример:
"apple" → Apple Watch band, Pineapple slicer, Apple iPhone case"apple" → Apple iPhone case, Apple Watch band, Pineapple slicerЕсли «после» лучше соответствует пользовательскому намерению — оставьте настройки. Если возникают боковые эффекты, меняйте по одной вещи за раз (сначала порядок атрибутов, затем правила сортировки), чтобы понять причину улучшения.
Хороший поиск — это не только «вводи слова, получай совпадения». Люди также хотят сузить результаты («только в наличии») и отсортировать их («сначала дешёвые»). В Meilisearch это делается через фильтры, сортировку и фасеты.
Фильтр — правило, которое применяется к набору результатов. Фасет — то, что вы показываете в UI, чтобы помочь пользователям конструировать эти правила (обычно чекбоксы или счётчики).
Примеры:
Пользователь может искать «running», затем отфильтровать category = Shoes и status = in_stock. Фасеты показывают счётчики, например “Shoes (128)”, чтобы пользователь понял доступность.
Meilisearch требует явного разрешения полей для фильтрации и сортировки.
category, status, brand, price, created_at, tenant_id.price, rating, created_at, popularity.Держите этот список компактным. Делать всё filterable/sortable увеличит размер индекса и замедлит обновления.
Даже при 50 000 совпадений пользователь видит только первую страницу. Используйте маленькие страницы (обычно 20–50 результатов), задавайте sensible limit и реализуйте пагинацию через offset (или новые механизмы пагинации). Также ограничьте максимальную глубину страниц в приложении, чтобы предотвратить дорогие запросы на «странице 400».
Чистый способ добавить серверный поиск — делать Meilisearch специальным сервисом за вашим API. Ваше приложение получает запрос поиска, вызывает Meilisearch, затем возвращает клиенту отфильтрованный ответ.
Обычно команды используют такой поток:
GET /api/search?q=wireless+headphones&limit=20).Этот шаблон делает Meilisearch заменяемым и предотвращает зависимость фронтенда от внутренней структуры индекса.
Если вы строите новое приложение и хотите быстро реализовать этот шаблон, платформы‑вещи вроде Koder.ai могут помочь заскелетить полный поток — React UI, Go‑бэкенд и PostgreSQL — и интегрировать Meilisearch за одним /api/search endpoint, чтобы клиент оставался простым, а права — на стороне сервера.
Meilisearch поддерживает клиентские запросы, но обычно безопаснее делать запросы через бэкенд, потому что:
Клиентские запросы подходят для публичных данных с ограниченными ключами, но если есть правила видимости для пользователей — делайте запросы через сервер.
Трафик поиска часто повторяется («iphone case», «return policy»). Кешируйте на уровне API:
Относитесь к поиску как к публичному endpoint:
limit и максимальную длину запроса.Meilisearch часто ставят «за» вашим приложением, потому что он быстро возвращает бизнес‑данные. Обращайтесь с ним как с базой: закрывайте доступ и выдавайте только то, что должен видеть вызывающий.
Meilisearch имеет master key, который может всё: создавать/удалять индексы, обновлять настройки и читать/писать документы. Держите его только на сервере.
Для приложений генерируйте ключи с ограниченными правами и индексами. Обычная схема:
Принцип наименьших привилегий означает, что скомпрометированный ключ не сможет удалить данные или читать чужие индексы.
Если вы обслуживаете нескольких клиентов (тенантов), есть два основных подхода:
1) Один индекс на тенанта.
Просто моделируется и уменьшает риск доступа между тенантами. Минусы: больше индексов управлять и нужно поддерживать консистентность настроек.
2) Общий индекс + фильтр tenantId.
Храните поле tenantId в каждом документе и требуйте фильтр tenantId = "t_123" для всех поисков. Это может масштабироваться, но вы должны гарантировать, что каждый запрос всегда применяет фильтр (идеально через скоуп‑ключ, чтобы клиент не мог удалить фильтр).
Даже если поиск корректен, результаты могут случайно выдавать приватные поля (email, внутренние заметки, себестоимость). Настройте, что можно возвращать:
Сделайте «worst‑case» тест: выполните поиск по общему слову и проверьте, что приватные поля не появляются.
Если сомневаетесь, нужен ли ключ на стороне клиента — ответ чаще всего «нет»; держите поиск на сервере.
Meilisearch быстр, когда вы учитываете два рабочих потока: индексирование (запись) и запросы поиска (чтение). Большая часть «непонятной» медлительности — это конкуренция за CPU, RAM или диск между этими нагрузками.
Нагрузка индексирования может всплывать при больших пакетных импортов, частых обновлениях или при индексации множества полей. Индексирование — фоновая работа, но оно всё равно потребляет CPU и диск. Если очередь задач растёт, поиски начнут медлеть, даже если трафик запросов не изменился.
Нагрузка запросов растёт с трафиком, а также с функционалом: больше фильтров, фасетов, больших наборов результатов и более агрессивной толерантностью к опечаткам увеличивают работу на запрос.
Disk I/O — тихий виновник. Медленные диски (или «шумные соседи» на шаред‑томе) могут превратить «мгновенно» в «в конце концов». NVMe/SSD — типичный минимум для продакшна.
Начните с простого: выделите Meilisearch достаточно RAM, чтобы держать индексы в памяти, и достаточно CPU для пиков QPS. Затем разделите обязанности:
Отслеживайте небольшой набор метрик:
Бэкапы должны быть рутинными. Используйте функцию snapshot Meilisearch по расписанию, храните снапшоты вне машины и периодически тестируйте восстановление. Для апгрейдов читайте релиз‑ноты, прогоняйте апгрейд в stage‑среде и планируйте время на переиндексацию, если версия меняет способ индексирования.
Если вы уже используете снапшоты окружения и rollback в платформе (например, через рабочие процессы снапшотов/отката в Koder.ai), приведите rollout поиска к той же дисциплине: снимайте снапшот перед изменениями, проверяйте health‑чек и держите быстрый путь отката.
Даже при аккуратной интеграции проблемы с поиском обычно укладываются в несколько повторяющихся категорий. Хорошая новость: Meilisearch даёт достаточно видимости (задачи, логи, детерминированные настройки), чтобы быстро дебажить, если действовать системно.
filterableAttributes, или документы хранят его в неожиданной форме (строка vs массив vs вложенный объект).sortableAttributes/rankingRules меняют порядок.Начните с проверки, успешно ли Meilisearch применил последнее изменение.
filter, затем sort, затем facets.Если не можете объяснить результат, временно упростите конфигурацию: отключите синонимы, уменьшите правки ранжирования и протестируйте на маленьком наборе данных. Сложные проблемы релевантности легче заметить на 50 документах, чем на 5 миллионах.
your_index_v2 параллельно, примените настройки и проиграйте выборку продовых запросов.filterableAttributes и sortableAttributes соответствуют UI.Related guides: /blog (search reliability, indexing patterns, and production rollout tips).
Серверный поиск означает, что запрос выполняется на вашем бэкенде (или в выделенном сервисе поиска), а не в браузере. Это правильный выбор, когда:
Пользователи мгновенно замечают четыре вещи:
Если чего-то не хватает, пользователи будут перепечатывать запросы, пересматривать список или бросать поиск.
Относите Meilisearch как поисковый индекс, а не как источник истины. База данных отвечает за записи, транзакции и ограничения; Meilisearch хранит копию выбранных полей, оптимизированную для быстрого поиска.
Полезная ментальная модель:
Обычный подход — один индекс на тип сущности (например, products, articles). Это обеспечивает:
Если нужен «поиск по всему», можно запрашивать несколько индексов и объединять результаты на бэкенде или создать отдельный глобальный индекс позже.
Выберите первичный ключ, который:
id, sku, slug)Стабильные id делают индексирование идемпотентным: при повторной отправке вы не создадите дубликаты, т.к. обновления работают как upsert.
Классифицируйте каждое поле по назначению, чтобы не индексировать лишнее:
Явное разграничение ролей уменьшает шум в результатах и предотвращает раздутые индексы.
Индексирование асинхронно: загрузка документов создаёт задачу, и документы становятся доступны в поиске только после успешного выполнения этой задачи.
Надёжный поток:
succeeded или failedЕсли кажется, что данные устарели — сначала проверьте статус задачи.
Лучше использовать множество меньших пакетов, чем один огромный. Практические ориентиры:
Малые пакеты легче повторно отправлять, отлаживать и они реже таймаутятся.
Два наиболее эффективных рычага:
publishedAt, price, popularity и т.д.Практика: взять 5–10 реальных запросов, сохранить топ-результаты «до», изменить одну настройку и сравнить «после».
Чаще всего проблемы с фильтрами/сортировкой связаны с конфигурацией:
Проверьте также форму и тип поля в документах (строка/массив/объект). Если фильтр не работает — посмотрите на последний статус задач и убедитесь, что документы содержат ожидаемые значения.