Поиск на LIKE-запросах разочаровывает: опечатки не находятся, релевантность случайна, фильтры приходится дописывать вручную. Meilisearch решает задачу отдельным сервисом на Rust: один бинарник, REST API и мгновенная выдача. Разберём установку, индексацию, фильтры, фронтенд, ключи и резервные копии.
Зачем отдельный поисковый движок
Реляционная база умеет искать точные совпадения и подстроки, но не умеет ранжировать результаты по близости слов. Meilisearch строит собственный индекс: разбивает текст на слова, хранит позиции, поддерживает префиксный поиск, опечатки и синонимы. Для сайта с десятками тысяч страниц этого достаточно, а ресурсы несопоставимы с Elasticsearch.
Движок не заменяет базу: он хранит документы в своём индексе, поэтому при изменении статьи её нужно обновить через API. CMS остаётся источником истины.
Установка и запуск
Официальный установщик скачивает бинарник, но для сервера лучше сразу разложить его по системным каталогам и завести отдельного пользователя. Мастер-ключ сгенерируйте заранее и сохраните в файл с правами 600: он даёт полный доступ к API.
curl -L https://install.meilisearch.com | sh
sudo install -m 755 meilisearch /usr/local/bin/meilisearch
sudo useradd --system --home /var/lib/meilisearch --shell /usr/sbin/nologin meilisearch
sudo install -d -o meilisearch -g meilisearch /var/lib/meilisearch/data
openssl rand -hex 32
Параметры задаются переменными окружения. Режим production требует мастер-ключ и запрещает анонимные запросы, адрес 127.0.0.1 оставляет сервис доступным только локально, а аналитику лучше выключить явно.
MEILI_ENV=production
MEILI_HTTP_ADDR=127.0.0.1:7700
MEILI_DB_PATH=/var/lib/meilisearch/data
MEILI_DUMP_DIR=/var/lib/meilisearch/dumps
MEILI_MASTER_KEY=вставьте-ключ-из-openssl
MEILI_NO_ANALYTICS=true
Файл положите в /etc/meilisearch.env и опишите unit для systemd с пользователем meilisearch. Проверка — curl на /health; если сервис не стартует, journalctl покажет занятый порт, права или короткий ключ.
Индексация документов
Индекс создаётся с уникальным полем id. Документы можно отправить массивом JSON или потоком NDJSON, большими партиями. Операции асинхронные: API сразу возвращает uid задачи, а результат нужно смотреть отдельно, иначе ошибка в документе останется незамеченной.
curl -X POST http://127.0.0.1:7700/indexes -H "Authorization: Bearer $MEILI_MASTER_KEY" -H "Content-Type: application/json" --data '{"uid":"pages","primaryKey":"id"}'
curl -X POST http://127.0.0.1:7700/indexes/pages/documents -H "Authorization: Bearer $MEILI_MASTER_KEY" -H "Content-Type: application/json" --data-binary @pages.json
curl -s http://127.0.0.1:7700/tasks -H "Authorization: Bearer $MEILI_MASTER_KEY"
curl -s http://127.0.0.1:7700/indexes/pages/stats -H "Authorization: Bearer $MEILI_MASTER_KEY"
В документ кладут только нужные поля: заголовок, описание, текст, категорию, язык и дату. Пароли и служебные заметки индексировать нельзя. При частых обновлениях скрипт забирает изменения из CMS пакетами, а удалённые страницы убирает через DELETE.
Релевантность, фильтры и сортировка
По умолчанию движок ищет по всем текстовым полям. Настройками можно задать приоритет полей, включить синонимы, стоп-слова и ограничить число результатов. Для фильтров и сортировки поля нужно явно объявить, иначе запрос с filter вернёт ошибку.
curl -X PATCH http://127.0.0.1:7700/indexes/pages/settings -H "Authorization: Bearer $MEILI_MASTER_KEY" -H "Content-Type: application/json" --data '{"searchableAttributes":["title","excerpt","body"],"filterableAttributes":["category","lang"],"sortableAttributes":["date"],"synonyms":{"лмде":["lmde","linux mint debian"]},"pagination":{"maxTotalHits":1000}}'
Порядок правил ранжирования влияет на выдачу. Если точное совпадение должно побеждать популярность, поднимите exactness; для свежести используйте сортировку по дате, не заменяя ею текстовую релевантность. Переиндексация после смены настроек не требуется.
Поиск на сайте
Для фронтенда есть официальный клиент. В браузер попадает только поисковый ключ, мастер-ключ остаётся на сервере. Запрос идёт напрямую к поисковому домену, поэтому настройте CORS и HTTPS.
npm install meilisearch
const client = new MeiliSearch({ host: "https://search.example.com", apiKey: "SEARCH_ONLY_KEY" });
const index = client.index("pages");
const result = await index.search(query, { limit: 10, filter: 'lang = "ru"', attributesToHighlight: ["title"] });
В интерфейсе показывайте заголовок, короткий фрагмент и подсветку совпадений. Не отправляйте запрос на каждое нажатие клавиши без задержки: пауза в 150–250 мс снижает нагрузку и убирает мигание выдачи.
Ключи и безопасность
Мастер-ключ не должен попадать в репозиторий и JavaScript. Для сайта создайте ключ с действием search, ограниченный одним индексом; при утечке его отзывают и выпускают новый.
curl -X POST http://127.0.0.1:7700/keys -H "Authorization: Bearer $MEILI_MASTER_KEY" -H "Content-Type: application/json" --data '{"description":"site search","actions":["search"],"indexes":["pages"],"expiresAt":null}'
curl -s http://127.0.0.1:7700/keys -H "Authorization: Bearer $MEILI_MASTER_KEY"
curl -X DELETE http://127.0.0.1:7700/keys/UID_КЛЮЧА -H "Authorization: Bearer $MEILI_MASTER_KEY"
Если поиск должен учитывать права пользователя, выдавайте не общий ключ, а tenant token с фильтром по доступным документам. Это обязательно для закрытых баз знаний и личных кабинетов.
Резервные копии и обновления
Есть два механизма: дампы и снапшоты. Дамп переносим между версиями и подходит для хранения, снапшот быстрее, но привязан к версии движка. Перед обновлением снимайте дамп, а не только копию каталога данных.
curl -X POST http://127.0.0.1:7700/dumps -H "Authorization: Bearer $MEILI_MASTER_KEY"
curl -X POST http://127.0.0.1:7700/snapshots -H "Authorization: Bearer $MEILI_MASTER_KEY"
ls -lh /var/lib/meilisearch/dumps
meilisearch --import-dump /var/lib/meilisearch/dumps/ДАТА.dump
Восстановление проверяйте отдельно: импортируйте дамп в чистый каталог и сравните число документов. Старый снапшот может не подойти новой версии, дамп решает эту проблему.
Развёртывание за nginx
Порт 7700 не публикуйте напрямую. Поставьте reverse proxy с TLS и ограничьте размер тела запроса. Мастер-ключ передаётся только с сервера, поэтому прокси не должен логировать Authorization.
server {
listen 443 ssl;
server_name search.example.com;
client_max_body_size 100m;
location / {
proxy_pass http://127.0.0.1:7700;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Диагностика
Проверка здоровья, статистики и задач отвечает на большинство вопросов. Если индексация встала, смотрите failed-задачи: обычно причина в неверном типе поля, слишком большом документе или нехватке памяти.
curl -s http://127.0.0.1:7700/health
curl -s http://127.0.0.1:7700/stats -H "Authorization: Bearer $MEILI_MASTER_KEY" | jq
curl -s "http://127.0.0.1:7700/tasks?status=failed" -H "Authorization: Bearer $MEILI_MASTER_KEY" | jq
journalctl -u meilisearch -n 100 --no-pager
Следите за местом: индекс может занимать больше исходных документов. Если поиск замедлился, проверьте число документов, filterableAttributes и лишние индексы; для больших каталогов ограничьте maxTotalHits.
Чек-лист
- Сервис слушает 127.0.0.1, снаружи доступен только через HTTPS-прокси.
- Мастер-ключ хранится в файле с правами 600 и не попадает в git.
- Для фронтенда выпущен отдельный ключ только с действием search.
- filterableAttributes и sortableAttributes заданы до первых запросов.
- Скрипт обновления индекса обрабатывает новые, изменённые и удалённые записи.
- Дамп снимается перед обновлением и проверяется восстановлением.
Итог
Meilisearch добавляет сайту полноценный поиск без кластера и подписки: установка занимает минуты, а настройка сводится к описанию полей и правил ранжирования. Главное — не открывать мастер-ключ наружу и поддерживать индекс в актуальном состоянии. Начните с одного индекса и десяти запросов, проверьте выдачу на реальных материалах, и только потом усложняйте синонимы и фильтры.