Qdrant на сервере: частые ошибки и решения
Qdrant ломается узнаваемо: либо контейнер не поднимается, либо API отвечает 400 с внятным текстом, либо коллекция жива, а поиск возвращает пустой список. Почти все ошибки Qdrant сводятся к десятку причин — права на каталог storage, размерность вектора, отсутствующий api-key, файловая система под bind-mount и попытка перепрыгнуть минорную версию. Разбираем по симптомам.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Сервис не стартует: порт, права на storage, лимит файлов
Первый барьер — занятый порт. Qdrant слушает три: 6333 — REST и веб-интерфейс, 6334 — gRPC, 6335 — внутренний gRPC между узлами кластера. О конфликте сообщает Docker своим текстом, а не текстом Qdrant:
Error response from daemon: driver failed programming external connectivity
on endpoint qdrant: Bind for 0.0.0.0:6333 failed: port is already allocated
Проверяется через ss -tulpn | grep -E ':(6333|6334|6335)\b'; виновник обычно — забытый контейнер из docker ps -a.
Второй барьер — права на хранилище. Процесс внутри образа работает не от root, и если смонтированный каталог хоста принадлежит root, контейнер падает с Permission denied (os error 13) на пути /qdrant/storage. Лечится либо sudo chown -R 1000:1000 ./qdrant_storage перед стартом, либо переходом на именованный том, где Docker сам разбирается с владельцем.
docker volume create qdrant-storage
docker run -d --name qdrant --restart unless-stopped \
-p 127.0.0.1:6333:6333 -p 127.0.0.1:6334:6334 \
--ulimit nofile=10000:10000 \
-v qdrant-storage:/qdrant/storage \
qdrant/qdrant:v1.19.0
Третий барьер всплывает, когда сегментов становится много: каждому нужны свои открытые файлы.
Error: Too many files open (OS error 24)
Это системный лимит дескрипторов. В Docker он поднимается флагом --ulimit nofile=10000:10000 (мягкий и жёсткий сразу), в systemd — директивой LimitNOFILE=10000 в секции [Service], вручную — ulimit -n 10000 до старта сервера.
Четвёртый случай — два экземпляра поверх одного каталога: старый контейнер не остановился, а новый подняли на том же томе.
Can't open Collections meta Wal: Os { code: 11, kind: WouldBlock,
message: "Resource temporarily unavailable" }
WAL коллекции заблокирован другим процессом. У каждого узла должен быть свой каталог или том: общий storage не «шарится», а ломается.
Пятое, самое обидное: переменные окружения пишутся через двойное подчёркивание, повторяя вложенность YAML — QDRANT__SERVICE__API_KEY, QDRANT__STORAGE__STORAGE_PATH. Вариант с одним подчёркиванием ошибки не вызывает, он просто игнорируется, и сервер молча стартует с дефолтами.
401 и 403: api-key, которого сервер не видит
Про безопасность стоит сказать прямо: в open-source-сборке аутентификации нет по умолчанию. Свежий контейнер слушает все интерфейсы и принимает любые запросы, включая DELETE /collections/docs. Риск не гипотетический: открытый наружу 6333 находится сканерами за часы.
Ключ включается одной переменной, после чего безключевые запросы получают:
HTTP/1.1 401 Unauthorized
{"status":{"error":"Must provide an API key or an Authorization bearer token"},"time":0.0}
Ключ передаётся заголовком api-key либо Authorization: Bearer. Здесь спотыкаются пришедшие из других сервисов: X-Api-Key, Authorization: ApiKey ... и api_key с подчёркиванием Qdrant не понимает — будет тот же 401.
curl -s http://127.0.0.1:6333/collections -H 'api-key: your_secret_key'
curl -s http://127.0.0.1:6333/collections -H 'Authorization: Bearer your_secret_key'
Если ключ принят, но операция запрещена, придёт 403 — типично при записи ключом из QDRANT__SERVICE__READ_ONLY_API_KEY. Оба ключа работают одновременно, и раздать сервисам-читателям второй — хорошая привычка.
Ещё три ловушки этого слоя:
- Питон-клиент и gRPC.
QdrantClient(url="http://127.0.0.1:6333", api_key=...)идёт в REST, аprefer_grpc=Trueуводит трафик на 6334. Фаервол про этот порт часто не знает, и «ключ перестал работать» означает «соединение не установилось». - Внутренний порт 6335 не защищён ключом никогда. В распределённом режиме узлы общаются между собой без
api-key— по замыслу. Наружу его выставлять нельзя ни при каких настройках. - Смена ключа обнуляет JWT. При
jwt_rbac: trueтокены подписываются значениемapi_key: поменяли ключ — все выданные токены невалидны. С версии 1.17 естьalt_api_key, второй ключ наравне с основным для ротации без простоя, но JWT придётся выпустить заново.
Минимальная гигиена: порты только на loopback, наружу — Nginx с TLS. И не вешайте на тот же location auth_basic: он занимает заголовок Authorization, которым клиент передаёт bearer-токен.
Развернуть за пару минут
Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть Qdrant400 на записи: размерность, имя вектора и ID точки
Абсолютный чемпион среди ошибок Qdrant выглядит так:
{"status":{"error":"Wrong input: Vector dimension error: expected dim: 768, got 1024"},"time":0.0}
Коллекция создана под одну модель эмбеддингов, а пишете вы векторы от другой. Размерность фиксируется при создании и не меняется — ни PATCH, ни параметром, никак. Что ждёт сервер, покажет curl -s http://127.0.0.1:6333/collections/docs | jq '.result.config.params.vectors'.
| Модель эмбеддингов | Размерность |
|---|---|
| all-MiniLM-L6-v2 | 384 |
| nomic-embed-text | 768 |
| multilingual-e5-large, bge-m3, mxbai-embed-large | 1024 |
| text-embedding-3-small | 1536 |
| text-embedding-3-large | 3072 |
Единственный путь при смене модели — создать коллекцию с правильным size и переиндексировать корпус: наполняете docs_v2, затем запросом к /collections/aliases переводите алиас docs на неё, и приложение переезда не замечает. Метрика (Cosine, Dot, Euclid) фиксируется при создании ровно так же.
Второй по частоте текст касается именованных векторов:
{"status":{"error":"Wrong input: Not existing vector name error: text"},"time":0.0}
Qdrant не создаёт имена векторов на лету. Если коллекция сделана с безымянной конфигурацией vectors: {size: 768, distance: Cosine}, а клиент шлёт {"text": [...]} — будет 400, и наоборот. Чаще всего на этом спотыкаются обёртки из n8n, LangChain и LlamaIndex, где имя зашито в коде.
Третье — идентификаторы. Qdrant принимает только 64-битные беззнаковые целые или UUID:
Format error in JSON body: value ... is not a valid point ID,
valid values are either an unsigned integer or a UUID
Хеши, пути к файлам и строки вида app-id--sha256 не пройдут. Решение — детерминированный UUID из естественного ключа: uuid.uuid5(uuid.NAMESPACE_URL, doc_path). Он стабилен между запусками, поэтому повторная индексация обновляет точку, а не плодит дубли.
Ещё две ошибки семейства. Ответ 404 {"status":{"error":"Not found: Collection \docs\ doesn't exist!"}} — опечатка в имени или не тот адрес; сверьтесь со списком curl -s http://127.0.0.1:6333/collections | jq -r '.result.collections[].name'. А Bad request: Index required but not found for "metadata.type" of one of the following types: [keyword] приходит при строгом режиме: фильтр по неиндексированному полю запрещён, нужен payload-индекс через PUT /collections/docs/index.
Данные пропали или поиск ничего не находит
Самая частая причина потери данных — их вообще никуда не сохраняли. Контейнер без -v держит хранилище внутри своего слоя, и docker rm уносит коллекции целиком. Проверка: docker inspect -f '{{json .Mounts}}' qdrant | jq.
Вторая причина серьёзнее. Qdrant требует POSIX-совместимую файловую систему и с версии 1.15 проверяет её при старте:
WARN qdrant: There is a potential issue with the filesystem for storage path
./storage. Details: HFS/HFS+ filesystem support is untested
ERROR qdrant: Filesystem check failed for storage path ./storage.
Details: FUSE filesystems may cause data corruption due to caching issues
Игнорировать это нельзя. Поверх FUSE, сетевых шар и Windows-каталогов, проброшенных через WSL, вы рискуете получить паники gridstore и обнулённые векторы после рестарта:
Panic occurred in file /qdrant/lib/gridstore/src/gridstore.rs at line 53:
called `Result::unwrap()` on an `Err` value: OutputTooSmall { expected: 4, actual: 0 }
Вывод простой: storage живёт на локальном диске с ext4 или xfs либо в именованном docker-томе. NFS, CIFS, sshfs и «диск из облачного файлового хранилища» под Qdrant не годятся.
Отдельный класс жалоб — «поиск возвращает пустоту, хотя точки есть». Проверяйте по порядку: points_count; снятый score_threshold; путь в фильтре (вложенные поля адресуются как metadata.type, и опечатка даёт не ошибку, а пустой результат); ту же модель эмбеддингов на запросе, что и на индексации. И про метрику: коллекция с Dot под ненормализованные векторы выдаёт не пустоту, а правдоподобный мусор — это хуже явной ошибки.
Про бэкапы честно: копирование каталога storage у работающего сервера бэкапом не является. Штатный механизм — снапшоты, которые надо забирать с машины:
curl -s -X POST http://127.0.0.1:6333/collections/docs/snapshots -H "api-key: $QDRANT_API_KEY"
curl -s http://127.0.0.1:6333/collections/docs/snapshots -H "api-key: $QDRANT_API_KEY" | jq -r '.result[].name'
Память, диск и обновление версии
Контейнер, который «сам перезапускается», чаще всего убит ядром:
docker inspect -f '{{.State.ExitCode}} OOMKilled={{.State.OOMKilled}}' qdrant
dmesg -T | grep -i 'killed process'
Код выхода 137 и Out of memory: Killed process — не баг Qdrant, а нехватка RAM. По умолчанию векторы держатся в памяти, и объём считается арифметикой: миллион точек по 768 измерений во float32 — это 1 000 000 × 768 × 4 байта, около 2,9 ГБ под сырые векторы, плюс граф HNSW и payload. Аппетит снижают on_disk: true, on_disk_payload: true и скалярное квантование до int8.
Диск переполняется тише, но последствия хуже. С версии 1.19 у Qdrant есть квоты, и при исчерпании места клиент получает HTTP 507 (в gRPC — ResourceExhausted):
Disk usage is at 95% of total capacity, exceeding the configured limit of 90%.
Help: Reduce disk usage (e.g. delete points or drop collections), or raise
`max_disk_usage_percent` in the global quota config.
Удаление точек и коллекций под квотой разрешено — иначе места было бы не освободить, — а удаление отдельных векторов и ключей payload отклоняется. Запись возобновится не сразу: нужно опуститься ниже лимита с запасом, по умолчанию на 5 процентных пунктов; планка поднимается через PUT /quotas. На версиях до 1.17 полный диск умел повреждать ID-трекер с ошибкой Corrupted ID tracker mapping storage.
Теперь главная ловушка обновлений 2026 года. Перепрыгивать минорные версии нельзя. В 1.17.0 (20 февраля 2026) удалена поддержка RocksDB в пользу gridstore, а конвертация данных выполняется на старте версий 1.16.x. Прямое обновление с 1.15.x на 1.17.x не работает: новый бинарник встречает старое хранилище и падает с ошибкой о неподдерживаемом формате. Маршрут — 1.15.x → 1.16.3 → 1.17.x → 1.18.x → 1.19.x, и перед каждым шагом:
- снять снапшоты всех коллекций и скачать их с сервера;
- дождаться
optimizer_status: ok, а неindexing, иначе миграция начнётся посреди незавершённой оптимизации; - обновить клиентские SDK: они тестируются только с тремя последними минорными версиями, а в 1.18 окончательно удалены устаревшие методы поиска.
Отдельно про статус grey: если после рестарта коллекция показывает «optimizations pending, awaiting update operation», оптимизации не сломались, а стоят на паузе — их будит любая операция обновления.
curl -s -X PATCH http://127.0.0.1:6333/collections/docs \
-H 'Content-Type: application/json' -H "api-key: $QDRANT_API_KEY" \
--data-raw '{"optimizers_config": {}}'
Статус red так не лечится: это невосстановимая ошибка сегмента, и путь один — восстановление из снапшота.
Какой сервер под Qdrant взять в MAATRIX
Qdrant из каталога приложений apps.maatrix.io ставится автоматически при заказе сервера — разворачивать руками ничего не нужно, работает на Ubuntu и Debian. Адрес панели и ключи доступа появляются в личном кабинете, в разделе «Доступ». Дальше остаётся создать коллекцию под свою модель эмбеддингов и сразу включить api_key.
Честный минимум: 2 vCPU, 4 ГБ RAM, 40 ГБ NVMe. Хватает на корпус порядка сотен тысяч фрагментов при размерности 768 и включённом квантовании. На 1–2 ГБ RAM Qdrant формально запускается, но первая же массовая загрузка будит оптимизатор, и процесс уезжает в OOM.
Комфортный вариант: 4 vCPU, 8–16 ГБ RAM, 80–160 ГБ NVMe. Векторы и граф HNSW помещаются в память без ухищрений, оптимизация не конкурирует с поиском за ядра, есть место под снапшоты. Если на том же сервере считаются эмбеддинги на CPU, планируйте память с запасом: модель уровня e5-large займёт свои полтора-два гигабайта. Диск при этом обязан быть локальным NVMe с ext4 или xfs — сетевое хранилище под Qdrant, как показано выше, заканчивается повреждёнными сегментами.
Локация — Великобритания (Лондон). Векторная база редко живёт одна: рядом стоит сервис, который считает эмбеддинги, и часто он ходит в зарубежное API. Из Лондона такие вызовы уходят без региональных отказов, а канал до европейских пользователей короткий — обычно порядка 40–60 мс из Москвы против примерно вдвое большего RTT до Нью-Йорка. Если вся аудитория внутри России, разумнее взять RU-локацию: 152-ФЗ и минимальный пинг перевесят остальное. Оплата — картой российского банка, по СБП, криптовалютой или токеном MAAT.
Развернуть за пару минут
Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть QdrantОбсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Частые вопросы
Можно ли поменять размерность вектора у существующей коллекции?
Нет. size и метрика фиксируются при создании, и ошибка Vector dimension error: expected dim: 768, got 1024 настройками не лечится. Создайте новую коллекцию, переиндексируйте корпус и переключите алиас через /collections/aliases.
Обновил образ, Qdrant не стартует. Что проверить?
Скорее всего, вы перепрыгнули минорную версию. С 1.15.x на 1.17.x напрямую нельзя: в 1.17 удалён RocksDB, а конвертация хранилища идёт на старте 1.16.x. Откатитесь на прежний образ, снимите снапшоты, дождитесь optimizer_status: ok и идите по одной версии.
Qdrant доступен всем из интернета — как закрыть быстрее всего?
Задайте QDRANT__SERVICE__API_KEY, перезапустите контейнер с публикацией портов на loopback (-p 127.0.0.1:6333:6333) и закройте фаерволом 6333, 6334 и особенно 6335 — внутренний порт кластера ключом не защищается по определению.
Нужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.