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

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

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

MAATRIX

Weaviate ломается предсказуемо: клиент падает на gRPC, хотя REST отвечает; батч отрабатывает без единого исключения, а объектов в коллекции ноль; поиск возвращает пустоту при полной базе. Почти все ошибки Weaviate сводятся к пяти причинам — неопубликованный порт 50051, анонимный доступ, автосхема с чужими типами, забытый тенант и заполненный диск. Разбираем по симптомам.

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

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

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

Три проверки, которые сужают поиск за минуту

«Weaviate не работает» — это три разные поломки: процесс не поднялся, процесс жив и отвечает 4xx, или отвечает 200 не тем. Различаются за минуту.

curl -s http://127.0.0.1:8080/v1/meta | jq '{version, modules: (.modules | keys)}'
curl -s -o /dev/null -w 'live=%{http_code}\n'  http://127.0.0.1:8080/v1/.well-known/live
curl -s -o /dev/null -w 'ready=%{http_code}\n' http://127.0.0.1:8080/v1/.well-known/ready
ss -tulpn | grep -E ':(8080|50051)\b'

/v1/meta отдаёт версию и модули — видно, тот ли образ запущен и есть ли нужный векторизатор. /v1/.well-known/ready даёт 503, пока шарды поднимаются после рестарта, — это нормальный старт, не зависание.

Деталь, на которой теряют часы: зелёный ready ничего не говорит про gRPC — REST на 8080, gRPC на отдельном 50051, клиент v4 ходит в оба. Только 8080 в выводе ss — сразу в третью секцию. Дальше — уровень узла и шардов:

curl -s 'http://127.0.0.1:8080/v1/nodes?output=verbose' \
  -H "Authorization: Bearer $WEAVIATE_API_KEY" \
  | jq '.nodes[] | {name, status, version, shards: [.shards[] | {name, class, objectCount, vectorIndexingStatus}]}'
{
  "name": "node1",
  "status": "HEALTHY",
  "version": "1.34.0",
  "shards": [
    {"name": "3nKqUn0pXqTz", "class": "Document", "objectCount": 128400, "vectorIndexingStatus": "READY"}
  ]
}

objectCount нулевой — данные не доехали, в пятую секцию. vectorIndexingStatus: INDEXING — HNSW ещё строится, выдача неполна, это нормально после заливки. Объекты есть, индекс READY, результат пустой — шестая секция.

СимптомГде искать причину
/v1/meta не отвечаетсекция 2: порт, том, память
WeaviateGRPCUnavailableErrorсекция 3: транспорт клиента
401 anonymous access not enabledсекция 4: аутентификация
not a string, but json.Numberсекция 5: схема и вставка
200 и пустой результатсекция 6: векторизация, тенант
store is read-onlyсекция 6: диск

Контейнер не стартует или уходит в бесконечный рестарт

Портов Weaviate занимает больше, чем кажется: 8080 — REST и GraphQL, 50051 — gRPC, 7100 — gossip между узлами, 7101 — обмен данными кластера, 8300 и 8301 — Raft, в котором с версии 1.25 живёт схема. О конфликте сообщает Docker, а не Weaviate:

Error response from daemon: driver failed programming external connectivity on
endpoint weaviate: Bind for 0.0.0.0:8080 failed: port is already allocated

Легко нарушить руками: CLUSTER_DATA_BIND_PORT обязан равняться CLUSTER_GOSSIP_BIND_PORT + 1 — поставили 7100 и 7105, узел не соберётся, а в логе будет ругань на bind, а не понятное «неверные порты».

Второй барьер — хранилище: без тома контейнер держит данные в своём слое, и первый docker rm уносит коллекции (docker inspect -f '{{json .Mounts}}' weaviate | jq). При bind-монтировании от root — permission denied на /var/lib/weaviate; именованный том это снимает.

services:
  weaviate:
    image: cr.weaviate.io/semitechnologies/weaviate:1.34.0
    restart: unless-stopped
    command: ["--host", "0.0.0.0", "--port", "8080", "--scheme", "http"]
    ports:
      - "127.0.0.1:8080:8080"
      - "127.0.0.1:50051:50051"
    volumes:
      - weaviate_data:/var/lib/weaviate
      - weaviate_backups:/var/lib/weaviate-backups
    environment:
      PERSISTENCE_DATA_PATH: /var/lib/weaviate
      CLUSTER_HOSTNAME: node1
      AUTOSCHEMA_ENABLED: 'false'
      DEFAULT_VECTORIZER_MODULE: none
      ENABLE_MODULES: 'text2vec-ollama,generative-ollama,backup-filesystem'
      BACKUP_FILESYSTEM_PATH: /var/lib/weaviate-backups
      AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'false'
      AUTHENTICATION_APIKEY_ENABLED: 'true'
      AUTHENTICATION_APIKEY_ALLOWED_KEYS: 'ЗАМЕНИТЕ_МЕНЯ'
      AUTHENTICATION_APIKEY_USERS: 'admin@example.com'
      AUTHORIZATION_ADMINLIST_ENABLED: 'true'
      AUTHORIZATION_ADMINLIST_USERS: 'admin@example.com'
      QUERY_DEFAULTS_LIMIT: 25
      DISK_USE_WARNING_PERCENTAGE: 80
      DISK_USE_READONLY_PERCENTAGE: 90
      LIMIT_RESOURCES: 'true'
      GOMEMLIMIT: 6GiB
      DISABLE_TELEMETRY: 'true'
    extra_hosts:
      - "host.docker.internal:host-gateway"
volumes:
  weaviate_data:
  weaviate_backups:

Третий барьер — CLUSTER_HOSTNAME: менять его после создания узла нельзя (идентичность в состоянии Raft) — переименование контейнера вместо старта даёт жалобы Raft на bootstrap. Честно: откат ниже 1.25 не поддерживается, схема мигрирует в одну сторону.

Четвёртый случай — контейнер, который «сам перезапускается». Обычно его убило ядро:

docker inspect -f '{{.State.ExitCode}} OOMKilled={{.State.OOMKilled}}' weaviate
dmesg -T | grep -i 'killed process'

Код выхода 137 и Out of memory: Killed process — не баг, а нехватка RAM: Weaviate на Go держит граф HNSW в памяти целиком. LIMIT_RESOURCES: 'true' ограничивает ~80% доступной памяти, GOMEMLIMIT задаёт предел вручную — ставьте ниже лимита контейнера. Подробнее — что делать при нехватке RAM.

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

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

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

WeaviateGRPCUnavailableError: клиент не видит gRPC

Самая частая ошибка у тех, кто пришёл с питоновским клиентом:

weaviate.exceptions.WeaviateGRPCUnavailableError: Weaviate makes use of a high-speed
gRPC API as well as a REST API. Unfortunately, the gRPC health check against Weaviate
could not be completed.

This error could be due to one of several reasons:
  - The gRPC traffic at the specified port is blocked by a firewall.
  - gRPC is not enabled or incorrectly configured on the server or the client.

REST клиент v4 использует только для схемы, поиск и батч гонит по gRPC. Причин четыре:

  • Порт 50051 не опубликован — классика docker run -p 8080:8080 без второго -p. Внутри контейнера gRPC работает, снаружи его нет.
  • Фаервол. ufw allow 8080/tcp без парного правила даёт ровно эту картину: REST отвечает, клиент падает.
  • Nginx проксирует только REST. Через proxy_pass gRPC не ходит, нужен grpc_pass и HTTP/2.
  • Мал таймаут инициализации — на медленном канале health-check не успевает.

Оба протокола разводятся одним server-блоком: gRPC-методы живут в сервисе weaviate.v1.Weaviate.

server {
    listen 443 ssl;
    http2 on;
    server_name weaviate.example.com;

    location /weaviate.v1.Weaviate/ {
        grpc_pass grpc://127.0.0.1:50051;
        grpc_read_timeout 600s;
        grpc_send_timeout 600s;
    }
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        client_max_body_size 64m;
    }
}

Директива http2 on; — с Nginx 1.25.1, на старых сборках listen 443 ssl http2;. Без HTTP/2 клиент получит обрыв потока вместо ответа.

import weaviate
from weaviate.classes.init import Auth, AdditionalConfig, Timeout

with weaviate.connect_to_local(
    host="127.0.0.1", port=8080, grpc_port=50051,
    auth_credentials=Auth.api_key("ЗАМЕНИТЕ_МЕНЯ"),
    additional_config=AdditionalConfig(timeout=Timeout(init=30, query=60, insert=180)),
) as client:
    print(client.is_ready())

За Nginx берут connect_to_custom с http_port=443, http_secure=True, grpc_port=443, grpc_secure=True. skip_init_checks=True уберёт исключение при подключении, но не транспорт: батч всё равно упадёт на отправке. Проверка gRPC без питона:

grpcurl -plaintext 127.0.0.1:50051 list
# grpc.health.v1.Health
# weaviate.v1.Weaviate

Старый код: weaviate.Client(url=...) теперь даёт AttributeError: module 'weaviate' has no attribute 'Client' — вместо него connect_to_local и client.collections.get("Document").

401 и 403: анонимный доступ, API-ключи и RBAC

Скажем прямо: в стандартной сборке Weaviate аутентификации нет. Конфигуратор генерирует AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true' — любой, кто дотянулся до 8080, читает документы и может выполнить DELETE /v1/schema/Document. Открытый порт находят сканерами за часы.

Защита включается тремя группами переменных — и здесь три ловушки подряд.

  • Ключи и пользователи — списки попарно. AUTHENTICATION_APIKEY_ALLOWED_KEYS и AUTHENTICATION_APIKEY_USERS через запятую: либо длины совпадают, либо один пользователь на все ключи.
  • Включение ключей не выключает анонимку. Пока AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED не 'false', ключи работают, но и без ключа пускают.
  • Владелец ключа должен попасть в список прав. Если пользователя нет ни в AUTHORIZATION_ADMINLIST_USERS, ни в AUTHORIZATION_ADMINLIST_READONLY_USERS, каждый запрос вернёт 403.

Без ключа при выключенной анонимке ответ такой:

UnexpectedStatusCodeError: Meta endpoint! Unexpected status code: 401,
with response body: {'error': [{'message': 'anonymous access not enabled'}]}

Засада для переезжающих с Qdrant: Weaviate принимает ключ только заголовком Authorization: Bearer — привычные api-key и X-API-Key игнорирует, и приходит тот же 401, будто ключа нет вовсе.

curl -s http://127.0.0.1:8080/v1/schema -H "api-key: $WEAVIATE_API_KEY"        # 401
curl -s http://127.0.0.1:8080/v1/schema -H "Authorization: Bearer $WEAVIATE_API_KEY"

С версии 1.29 доступна ролевая модель (AUTHORIZATION_RBAC_ENABLED), но для одиночного сервера хватает admin-list: ключ на запись и read-only для приложения, которое только ищет.

Сетевой минимум: публикация на loopback, ufw allow 22/tcp, ufw deny 8080/tcp, ufw deny 50051/tcp, наружу — только Nginx с TLS. И не вешайте auth_basic на тот же location: заголовок Authorization тот же, что и у ключа клиента.

Схема и вставка: автосхема, типы и молчаливые батчи

Самая обидная категория — когда всё «прошло успешно», а данных нет.

Имена классов Weaviate поднимает в верхний регистр. Создали document — в схеме будет Document, и приложение, идущее к document, получит «коллекция не найдена». id и _additional — зарезервированные имена ('id' is a reserved property name), идентификатор передаётся полем uuid.

Автосхема угадывает типы по первому объекту. AUTOSCHEMA_ENABLED включена по умолчанию: первая партия несла "price": "1200" строкой, во второй прилетело число — партия падает.

invalid object: invalid text property 'price' on class 'Product': not a string, but json.Number

Обратная ситуация симметрична, а на пустом массиве тип не определить вовсе. Существующее свойство изменить нельзя — только пересоздать коллекцию; поэтому в продакшене автосхему выключают и описывают свойства явно (новые добавлять это не мешает).

Батчи в клиенте v4 не бросают исключений. Ловушка номер один по числу потерянных вечеров: цикл отработал, ошибок нет, objectCount нулевой. Ошибки смотрят руками:

docs = client.collections.get("Document")
with docs.batch.fixed_size(batch_size=200, concurrent_requests=2) as batch:
    for chunk in chunks:
        batch.add_object(
            properties={"text": chunk["text"], "source": chunk["source"]},
            uuid=weaviate.util.generate_uuid5(chunk["source"] + str(chunk["n"])),
            vector=chunk["vector"],
        )
        if batch.number_errors > 10:
            break

failed = docs.batch.failed_objects
print(f"не вставлено: {len(failed)}")
if failed:
    print(failed[0].message)

Рецепт от дублей: без uuid Weaviate сгенерирует его случайно, переиндексация создаст вторую копию вместо обновления. generate_uuid5 от естественного ключа детерминирован — тот же путь и номер дадут тот же id, перезаливка перезапишет объект.

Размер батча важен: gRPC-сообщение клиент ограничивает ~100 МиБ, десятки тысяч объектов с векторами по 1024 измерения одним куском не влезут — рабочий диапазон 100–500 объектов.

Мультитенантность. Коллекция с multi_tenancy_config требует тенанта в каждом запросе:

collection Document has multi-tenancy enabled, but request was without tenant

Лечится docs.with_tenant("acme") вместо голого docs. Второй вариант той же беды — тенант есть, но неактивен: данные на месте, обращение отклоняется.

Поиск ничего не находит, а запись упирается в read-only

Векторизатора нет. В compose выше стоит DEFAULT_VECTORIZER_MODULE: none — эмбеддинги считаются снаружи, и near_text работать не будет:

explorer: get class: vectorize search vector: no vectorizer found for class Document

Векторизатор должен быть в ENABLE_MODULES и назначен коллекции: невключённый даёт no module with name "text2vec-ollama" present, реальный список — curl -s http://127.0.0.1:8080/v1/meta | jq '.modules | keys'. С локальным text2vec-ollama частый случай — Ollama слушает только 127.0.0.1, без OLLAMA_HOST=0.0.0.0:11434 из контейнера будет connection refused; подробнее — как считать эмбеддинги на CPU без GPU.

Размерность не совпала. Сменили модель и заливаете в старую коллекцию:

new node has a vector with length 1024. Existing nodes have vectors with length 768

Размерность фиксируется первым вектором и не меняется — путь один: новая коллекция и переиндексация.

Фильтр по несуществующему свойству — хорошая новость, ошибка внятная:

no such prop with name 'catgeory' found in class 'Document' in the schema

Хуже, когда свойство есть, а подходящих значений нет — пустой список: снимайте по одному фильтр, порог distance/certainty, поднимайте limit (QUERY_DEFAULTS_LIMIT = 25 по умолчанию). За QUERY_MAXIMUM_RESULTS (10000) коллекцию выгружают курсором, а не растущим offset — иначе query maximum results exceeded.

Диск — фирменное поведение: на 80% предупреждение в логе, на 90% шарды уходят в read-only, клиент получает {"error":[{"message":"store is read-only"}]}. Освободить место мало — статус шарда возвращают руками.

curl -s http://127.0.0.1:8080/v1/schema/Document/shards \
  -H "Authorization: Bearer $WEAVIATE_API_KEY" | jq
curl -s -X PUT -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $WEAVIATE_API_KEY" \
  -d '{"status":"READY"}' \
  http://127.0.0.1:8080/v1/schema/Document/shards/3nKqUn0pXqTz

Бэкап — не «скопировать /var/lib/weaviate на живом сервере» (LSM как раз компактится): штатный путь — модуль backup-filesystem, включённый заранее.

curl -s -X POST http://127.0.0.1:8080/v1/backups/filesystem \
  -H 'Content-Type: application/json' -H "Authorization: Bearer $WEAVIATE_API_KEY" \
  -d '{"id":"backup-2026-08-28"}'
curl -s http://127.0.0.1:8080/v1/backups/filesystem/backup-2026-08-28 \
  -H "Authorization: Bearer $WEAVIATE_API_KEY" | jq '.status'

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

Какой сервер под Weaviate взять в MAATRIX

Сразу честно: Weaviate в каталоге apps.maatrix.io нет — сервер приезжает чистым (Ubuntu 24.04 или Debian 12), разворачиваете по compose из второй секции. В каталоге есть готовые сборки соседей по стеку — Qdrant, Ollama, LiteLLM, AnythingLLM, n8n — ставятся автоматически, доступы в кабинете: практичная схема — Ollama оттуда под эмбеддинги, Weaviate рядом руками.

Память считается арифметикой: число объектов × размерность × 4 байта (float32) — миллион фрагментов размерности 768 это ~2,9 ГБ, размерности 1024 — ~3,8 ГБ. Сверху граф HNSW, документация Weaviate советует закладывать вдвое больше самих векторов. Не помещаетесь — включайте сжатие: скалярное ужимает индекс в разы, бинарное — на порядок, ценой точности, которую добирают переранжированием.

Честный минимум: 2 vCPU, 4 ГБ RAM, 40 ГБ NVMe. Хватает на 200–300 тысяч фрагментов при размерности 768 со сжатием и GOMEMLIMIT: 3GiB. На 1–2 ГБ Weaviate формально стартует, но первая массовая заливка запускает HNSW и компакцию одновременно — и уезжает в OOM.

Комфортный вариант: 4 vCPU, 8–16 ГБ RAM, 80–160 ГБ NVMe. Векторы и граф помещаются без ухищрений, индексация не конкурирует с поиском за ядра, есть место под бэкапы. Диск берите с двойным запасом: read-only включается на 90%. Если рядом Ollama — память под неё сверху: по нашим замерам на AMD EPYC 9554 (16 vCPU, Ollama 0.33.1) qwen2.5:7b в Q4_K_M занимает 5,1 ГБ, llama3.1:8b — 5,6 ГБ, qwen2.5:3b — 2,2 ГБ — на восьми гигабайтах вместе с Weaviate уже впритык.

Локация — Великобритания (Лондон). Рядом с векторной базой обычно сервис эмбеддингов, который ходит во внешние API, — из Лондона без региональных отказов, а до Европы канал короче: 40–60 мс из Москвы против вдвое большего RTT до Нью-Йорка. Данные и пользователи в России — берите RU: 152-ФЗ и пинг перевесят. Оплата — картами российских банков, СБП, криптой или токеном MAAT.

Если Weaviate избыточен — разбор соседа: Qdrant на сервере: частые ошибки. Сборка конвейера целиком: как поднять RAG по своим документам, VPS в Великобритании для нейросетей.

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

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

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

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

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

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

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

Клиент падает с WeaviateGRPCUnavailableError, хотя curl на 8080 отвечает. Почему?

Разные каналы: REST на 8080, gRPC на 50051, клиент v4 использует оба. Проверьте ss -tulpn | grep 50051, опубликуйте порт в Docker и фаерволе, а за Nginx настройте grpc_pass grpc://127.0.0.1:50051; с http2 on;proxy_pass gRPC не проксирует.

Батч отработал без исключений, а объектов в коллекции нет. Куда они делись?

Клиент v4 не бросает исключение на ошибках батча: смотрите batch.number_errors при загрузке, collection.batch.failed_objects после. Причина обычно в первой ошибке — автосхема ждала строку, пришло число (not a string, but json.Number), либо коллекция мультитенантная, запрос ушёл без тенанта.

Запись отвалилась с store is read-only, место на диске уже освободил. Что дальше?

Weaviate переводит шарды в read-only выше DISK_USE_READONLY_PERCENTAGE (90% по умолчанию) и сам обратно не переключает. Смотрите шарды через GET /v1/schema/Document/shards, возвращайте статус PUT с телом {"status":"READY"}.

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

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