MAATRIX / Блог / Meilisearch на сервере: частые ошибки и решения

Meilisearch на сервере: частые ошибки и решения

MAATRIX

Meilisearch выбирают за скорость и опечатко-устойчивый полнотекстовый поиск — он поднимается за пару минут и не требует настройки шардов, как Elasticsearch. Но именно эта простота подводит: без master key сервис открыт всему интернету, индекс после docker restart вдруг «пустеет», а под нагрузкой процесс неожиданно съедает всю память и падает по OOM. Ниже — конкретные причины и рабочие решения для типичных проблем Meilisearch на VPS.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Meilisearch не отвечает или падает сразу после запуска

Первое, что стоит проверить — как именно запущен процесс и что он пишет в лог. При запуске через systemd:

sudo systemctl status meilisearch
sudo journalctl -u meilisearch -n 100 --no-pager

При запуске в Docker:

docker logs meilisearch --tail 100

Частая причина падения сразу после старта — несовместимая версия файла данных. Meilisearch хранит индекс в собственном бинарном формате в директории data.ms, и он не гарантирует обратную совместимость между мажорными версиями. Если вы обновили образ (например, с getmeili/meilisearch:v1.6 на v1.10) поверх старых данных, движок может отказаться стартовать с ошибкой вида database version mismatch или тихо упасть без внятного текста.

Решение — либо явно экспортировать/импортировать дамп перед обновлением (см. ниже), либо держать версию образа зафиксированной в конфиге и обновлять осознанно, а не через :latest:

services:
  meilisearch:
    image: getmeili/meilisearch:v1.10
    restart: unless-stopped

Вторая частая причина — занят порт 7700 другим процессом или предыдущим зависшим контейнером:

sudo ss -tlnp | grep 7700

Если порт занят «призраком» от старого контейнера, docker ps -a покажет его в статусе Exited, но со старым мэппингом — удалите контейнер (docker rm) и поднимите заново.

Индекс «пропадает» после перезапуска контейнера

Это почти всегда путаница с volume. Meilisearch по умолчанию хранит данные в пути, заданном переменной MEILI_DB_PATH (по умолчанию ./data.ms относительно рабочей директории процесса, в официальном Docker-образе — /meili_data). Если в docker-compose.yml этот путь не примонтирован как volume, при пересоздании контейнера (не при простом restart, а именно при docker compose up -d с новым образом или docker rm + run) все данные исчезают — контейнер стартует с чистой директорией внутри своего слоя.

Рабочий docker-compose.yml с постоянным хранением:

services:
  meilisearch:
    image: getmeili/meilisearch:v1.10
    container_name: meilisearch
    restart: unless-stopped
    ports:
      - "127.0.0.1:7700:7700"
    environment:
      - MEILI_MASTER_KEY=${MEILI_MASTER_KEY}
      - MEILI_ENV=production
      - MEILI_NO_ANALYTICS=true
    volumes:
      - ./meili_data:/meili_data

Обратите внимание на ./meili_data:/meili_data — это bind mount на хост, а не анонимный volume. Анонимный volume (просто - /meili_data без источника) тоже переживает restart, но при docker compose down -v или ручной очистке неиспользуемых volume (docker volume prune) удаляется вместе с данными. Bind mount на конкретную папку хоста нагляднее и его проще включить в бэкап.

Проверить, что данные реально лежат на хосте, а не только внутри слоя контейнера:

ls -la ./meili_data
du -sh ./meili_data

Если папка пустая после нескольких дней работы и добавленных документов — значит volume не примонтирован и всё, что было проиндексировано, физически лежало во временном слое контейнера.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Meilisearch открыт без ключа — кто угодно может читать и писать в индекс

Это не «ошибка» в смысле краша, а тихая дыра в безопасности, которую находят по логам постфактум. Без переменной MEILI_MASTER_KEY Meilisearch запускается в открытом режиме — любой, кто достучится до порта 7700, может не только искать, но и удалять индексы через API.

Обязательные шаги:

  1. Сгенерировать длинный случайный ключ и передать его через переменную окружения, а не хардкодить в конфиге, который может попасть в git:
openssl rand -base64 48
  1. Убедиться, что MEILI_ENV=production — в этом режиме Meilisearch по умолчанию отключает встроенный веб-интерфейс поиска (Mini Dashboard) и требует ключ для всех операций, кроме health-чека.
  1. Не пробрасывать порт 7700 наружу вообще, если поиск нужен только вашему бэкенду. В примере выше 127.0.0.1:7700:7700 — порт слушает только localhost, наружу доступ идёт через nginx с TLS и, при необходимости, дополнительным слоем аутентификации.

Если Meilisearch всё же должен быть доступен снаружи (например, отдельный поисковый сервис для фронтенда), используйте search key с ограниченными правами вместо master key — его можно сгенерировать через API /keys и передавать на клиент, не раскрывая ключ с правами на запись.

Проверка, что API действительно требует ключ:

curl -i http://127.0.0.1:7700/indexes
# без заголовка Authorization должен вернуться 401

Индексация зависает или занимает часы на большом датасете

Meilisearch индексирует документы асинхронно — вызов POST /indexes/{index}/documents сразу возвращает taskUid, а сама индексация идёт в фоне. Если после отправки большого JSON-файла поиск долго не находит новые документы, проблема обычно не в «зависании», а в том, что задача ещё не выполнена. Статус проверяется так:

curl \
  -H "Authorization: Bearer $MEILI_MASTER_KEY" \
  http://127.0.0.1:7700/tasks/12345

Если статус processing держится подозрительно долго, есть несколько типичных причин:

  • Слишком крупные батчи. Отправка одного файла на несколько сотен тысяч документов за один запрос увеличивает пиковое потребление памяти и время построения индекса. Меньшие батчи по 5–10 тысяч документов индексируются предсказуемее и позволяют следить за прогрессом.
  • Настройки searchableAttributes и filterableAttributes заданы после загрузки данных. Любое изменение этих настроек запускает полную переиндексацию всех документов — на большом датасете это может занять заметное время. Настройки атрибутов лучше задавать один раз, до массовой загрузки данных.
  • Медленный диск. Meilisearch активно работает с диском при построении индекса (движок использует LMDB). На VPS с сетевым или сильно нагруженным диском построение индекса растягивается ощутимо дольше, чем на локальном NVMe — конкретные цифры сильно зависят от датасета и диска, поэтому ориентируйтесь на собственные замеры, а не на чужие бенчмарки.

Для проверки очереди задач целиком:

curl -H "Authorization: Bearer $MEILI_MASTER_KEY" \
  "http://127.0.0.1:7700/tasks?statuses=enqueued,processing"

Процесс съедает всю память и падает по OOM

Meilisearch агрессивно использует память для построения индекса и кэширования — это плата за скорость поиска. На VPS с 1–2 ГБ RAM крупный индекс (сотни тысяч документов с полнотекстовыми полями) может упереться в предел памяти, особенно если параллельно с индексацией идут поисковые запросы.

Признаки — процесс исчезает из docker ps без явной ошибки в логе приложения, а в системном логе есть характерная запись:

sudo dmesg -T | grep -i "out of memory"
sudo journalctl -k | grep -i oom

Что помогает:

  • Ограничить и одновременно гарантировать ресурсы контейнеру, чтобы OOM killer работал предсказуемо, а не убивал случайный процесс на хосте:
services:
  meilisearch:
    image: getmeili/meilisearch:v1.10
    deploy:
      resources:
        limits:
          memory: 2g
        reservations:
          memory: 1g
  • Настроить swap, если постоянной памяти впритык — это не решает проблему производительности под нагрузкой, но даёт запас на пиковые моменты индексации вместо мгновенного падения процесса. Подробно о выборе размера — в статье про своп-файл на VPS.
  • Ограничить индексируемые поля. Через searchableAttributes можно исключить длинные текстовые поля, которые не нужны для поиска (например, полное HTML-содержимое страницы вместо очищенного текста) — это снижает и объём индекса, и память на его построение.
  • Разнести Meilisearch и основное приложение по разным серверам, если оба претендуют на одну и ту же оперативную память — на небольшом VPS конкуренция за RAM между БД, бэкендом и поисковым движком обычно и есть корень OOM-падений.

Бэкап и восстановление индекса

Индекс Meilisearch — это не то, что стоит терять: пересоздание с нуля означает повторную индексацию всех данных, а на большом датасете это часы простоя поиска. Встроенный механизм — snapshot:

# включить периодические снапшоты при старте
docker run -d \
  -e MEILI_MASTER_KEY=$MEILI_MASTER_KEY \
  -e MEILI_SCHEDULE_SNAPSHOT=3600 \
  -v ./meili_data:/meili_data \
  -v ./meili_snapshots:/snapshots \
  getmeili/meilisearch:v1.10

MEILI_SCHEDULE_SNAPSHOT задаёт интервал автоматических снапшотов в секундах. Восстановление — запуск с флагом --import-snapshot:

docker run -d \
  -e MEILI_MASTER_KEY=$MEILI_MASTER_KEY \
  -v ./meili_snapshots:/snapshots \
  -v ./meili_data:/meili_data \
  getmeili/meilisearch:v1.10 \
  meilisearch --import-snapshot /snapshots/20260828-120000.snapshot

Альтернатива для переноса между несовместимыми версиями (когда простое копирование data.ms не работает из-за смены внутреннего формата) — экспорт документов через API в JSON и последующий импорт в новый индекс. Это медленнее снапшота, но гарантированно переживает смену мажорной версии. Сами файлы снапшотов и volume с данными стоит забирать за пределы контейнера тем же способом, что и остальные сервисы на сервере — общий подход к автоматизации бэкапов описан в статье про бэкап Docker volume.

Поиск работает, но nginx перед Meilisearch отдаёт ошибки

Если Meilisearch стоит за nginx как обратный прокси (что рекомендуется вместо прямого проброса порта наружу), типичные проблемы — это уже проблемы самого nginx, а не поискового движка: неверный proxy_pass, отсутствие увеличенных лимитов для длинных запросов при массовой индексации, таймауты на крупных батчах. Разбор конкретных ошибок nginx как обратного прокси — в отдельной статье про nginx как reverse proxy. Ключевое для Meilisearch — увеличить client_max_body_size, если вы отправляете большие JSON-батчи документов через прокси:

location /meili/ {
    proxy_pass http://127.0.0.1:7700/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    client_max_body_size 100M;
    proxy_read_timeout 300s;
}

Без увеличенного client_max_body_size nginx вернёт 413 Request Entity Too Large ещё до того, как запрос дойдёт до Meilisearch — это легко спутать с ошибкой самого поискового движка.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Meilisearch подходит для замены Elasticsearch на небольшом проекте?

Для полнотекстового поиска с опечатко-устойчивостью на датасетах до нескольких миллионов документов — да, и требует заметно меньше памяти и настройки. Для сложной аналитики, агрегаций и логов Elasticsearch (или ClickHouse) остаётся более подходящим инструментом.

Сколько RAM нужно для Meilisearch в продакшене?

Зависит от объёма и структуры данных — единой цифры нет. Для небольших проектов (десятки-сотни тысяч документов) обычно достаточно 1–2 ГБ, но точный объём стоит проверять на своих данных с запасом на пиковую индексацию, а не полагаться на чужие оценки.

Можно ли обновить Meilisearch без даунтайма поиска?

Прямого zero-downtime апгрейда через простую замену образа нет из-за возможной несовместимости формата данных между мажорными версиями. Безопасный путь — поднять новую версию рядом, восстановить данные через экспорт/импорт или совместимый снапшот, проверить и только потом переключить трафик.

Нужен ли Meilisearch отдельный сервер или можно на одном VPS с остальным приложением?

Для небольших нагрузок можно держать рядом с основным приложением, но стоит следить за суммарным потреблением памяти — индексация и поиск конкурируют за RAM с базой данных и бэкендом на том же сервере.

Как понять, что дело именно в Meilisearch, а не в сети или nginx перед ним?

Обратитесь к API напрямую с сервера через curl http://127.0.0.1:7700/health — если это отвечает нормально, а через публичный домен нет, проблема в прокси-слое, а не в самом движке.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →