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

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

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

MAATRIX

Chroma заводится на ноутбуке в три строки — и ровно поэтому на сервере ломается там, где не ждёшь: клиент получает 410 вместо ответа, коллекция после рестарта контейнера пуста, а поиск честно возвращает 200 и полную чушь. Почти все ошибки Chroma сводятся к десятку причин: версия API, версия sqlite3, каталог хранилища, размерность вектора и метрика расстояния. Разбираем по симптомам — с точными текстами ошибок, командами проверки и рабочими конфигами.

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

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

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

Сначала три команды: жив ли сервер и та ли у него версия API

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

curl -s http://127.0.0.1:8000/api/v2/heartbeat
# {"nanosecond heartbeat":1772451930183000000}

curl -s -o /dev/null -w 'v1=%{http_code}\n' http://127.0.0.1:8000/api/v1/heartbeat
curl -s http://127.0.0.1:8000/api/v2/version
ss -tulpn | grep -w 8000

v2 отвечает 200 — перед вами Chroma 1.x с ядром на Rust, старые клиенты к нему не подойдут. v2 даёт 404, а v1 — 200 — сервер ветки 0.4–0.6. v1 отдаёт 410 с телом {"error":"Unimplemented","message":"The v1 API is deprecated. Please use /v2 apis"} — сервер новый, клиент старый. Connection refused — до HTTP дело не дошло, смотрите journalctl -u chroma -n 100 --no-pager или docker logs --tail 100 chroma.

Heartbeat отвечает и без токена — зелёный ответ не говорит ни о правах, ни о том, что коллекции на месте. Их проверяют отдельно, и в v2 маршрут стал длиннее — с явными тенантом и базой:

curl -s -H "X-Chroma-Token: $CHROMA_TOKEN" \
 "http://127.0.0.1:8000/api/v2/tenants/default_tenant/databases/default_database/collections" \
 | jq '.[] | {name, id}'

Пустой массив при живом сервере — почти всегда не «база сломалась», а «сервер смотрит не в тот каталог»; разбор ниже.

Chroma не устанавливается и не стартует: ошибки sqlite3, venv и порта

Первая стена на свежем сервере — не Chroma, а пакетный менеджер: на Ubuntu 24.04 системный Python защищён PEP 668, и pip install chromadb обрывается:

error: externally-managed-environment
× This environment is externally managed

Лечится venv, не флагом --break-system-packages — Chroma тянет десятки зависимостей.

sudo apt install -y python3-venv
sudo mkdir -p /opt/chroma /var/lib/chroma
sudo python3 -m venv /opt/chroma/venv
sudo /opt/chroma/venv/bin/pip install -U pip chromadb
/opt/chroma/venv/bin/chroma run --path /var/lib/chroma --host 127.0.0.1 --port 8000

Вторая стена — известная ошибка sqlite3, из-за которой Chroma не импортируется вовсе:

RuntimeError: Your system has an unsupported version of sqlite3.
Chroma requires sqlite3 >= 3.35.0.

Проверяется строкой python3 -c "import sqlite3; print(sqlite3.sqlite_version)". Ubuntu 22.04 отдаёт 3.37.2, Ubuntu 24.04 — 3.45.1, там проблем нет. А Debian 11 приносит 3.34.1 — на волосок ниже порога, потому ошибка и прилетает на старых VPS. Обходной путь: pip install pysqlite3-binary и подмена модуля до импорта Chroma.

__import__("pysqlite3")
import sys
sys.modules["sqlite3"] = sys.modules.pop("pysqlite3")
import chromadb

Честно про костыль: он работает, но привязан к колесу pysqlite3-binary, собранному не под каждую архитектуру и версию Python — проще взять Debian 12 или Ubuntu 22.04/24.04. В ветке 1.x ядро ушло на Rust со своей сборкой SQLite, и проблема исчезает сама.

Третья стена — порт. Chroma слушает 8000, самый занятый порт на сервере с Python: тот же дефолт у uvicorn и python -m http.server. Systemd падает с [Errno 98] Address already in use, Docker — с Bind for 0.0.0.0:8000 failed: port is already allocated. Смотрите ss -tulpn | grep -w 8000 и переносите Chroma на 8001.

Четвёртая — эмбеддинги по умолчанию: без своей функции Chroma считает векторы моделью all-MiniLM-L6-v2 через onnxruntime, качая при первом обращении архив (~80 МБ) в ~/.cache/chroma/onnx_models/. Без исходящего доступа первая вставка виснет по таймауту, без пакета onnxruntime — в логе:

ValueError: The onnxruntime python package is not installed. Please install it with 'pip install onnxruntime'

Кеш лежит в домашнем каталоге пользователя сервиса: юнит с User=chroma без HOME полезет писать в /, получит отказ и уронит первый запрос. Задайте Environment=HOME=/var/lib/chroma.

[Unit]
Description=Chroma vector database
After=network-online.target

[Service]
User=chroma
Group=chroma
Environment=HOME=/var/lib/chroma
Environment=ANONYMIZED_TELEMETRY=FALSE
EnvironmentFile=/etc/chroma/chroma.env
ExecStart=/opt/chroma/venv/bin/chroma run --path /var/lib/chroma --host 127.0.0.1 --port 8000
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

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

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

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

Клиент не подключается: 410 на v1, tenant not found и лишний chromadb

Самая массовая ошибка 2026 года — рассинхрон версий: сервер обновили до 1.x, а в приложении остался старый chromadb (или обвязка LangChain, которая тянет его за собой) — все вызовы уходят на /api/v1/... и получают 410. Проверка: pip show chromadb | grep -i version у приложения против curl -s http://127.0.0.1:8000/api/v2/version у сервера. Правило: клиент не старше сервера, через мажор — только вместе.

Второй по частоте симптом выглядит загадочно, а причина обычно банальна:

ValueError: Could not connect to tenant default_tenant. Are you sure it exists?

В девяти случаях из десяти дело не в тенантах: клиент достучался до HTTP-сервиса, но не до Chroma нужной версии — перепутан порт (в 8000 сидит чужой uvicorn), ответила заглушка Nginx или тот же несовместимый API. Проверьте curl-ом, что по адресу отвечает heartbeat, и только потом ищите баг в коде.

Третья ловушка — устаревший способ создания клиента: конструкции из статей 2023 года через Settings(chroma_api_impl=..., chroma_server_host=...) давно не работают, актуальный вид один:

import chromadb
from chromadb.config import Settings

client = chromadb.HttpClient(
    host="127.0.0.1", port=8000, ssl=False,
    settings=Settings(
        chroma_client_auth_provider="chromadb.auth.token_authn.TokenAuthClientProvider",
        chroma_client_auth_credentials="ВАШ_ТОКЕН",
    ),
)
print(client.heartbeat())

За Nginx с TLS указывайте ssl=True и порт 443, не 8000 — типовая ошибка: порт остался от локальной отладки, отсюда таймаут.

Совет: в приложении ставьте не chromadb, а chromadb-client — тонкий HTTP-клиент без onnxruntime и tokenizers, образ худеет на сотни мегабайт. Минус честный: вместе с весом уезжают встроенные функции эмбеддингов — векторы придётся считать самим и передавать в add() готовыми.

Открытая база и 401: аутентификация, которую переименовали в 0.5

Ключевой факт: по умолчанию у Chroma нет аутентификации вообще. Команда chroma run --host 0.0.0.0 поднимает базу, доступную любому, кто знает адрес: читать, писать и удалять коллекции сможет кто угодно, без единого предупреждения при старте.

Токен включается переменными окружения на сервере:

# /etc/chroma/chroma.env
CHROMA_SERVER_AUTHN_PROVIDER=chromadb.auth.token_authn.TokenAuthenticationServerProvider
CHROMA_SERVER_AUTHN_CREDENTIALS=ваш-длинный-токен
CHROMA_AUTH_TOKEN_TRANSPORT_HEADER=X-Chroma-Token
ANONYMIZED_TELEMETRY=FALSE

Здесь спрятана самая неприятная грабля темы: в версии 0.5 переменные переименовали — было CHROMA_SERVER_AUTH_PROVIDER и CHROMA_SERVER_AUTH_CREDENTIALS, стало AUTHN, класс провайдера переехал из chromadb.auth.token в chromadb.auth.token_authn. Опасность в том, что старые имена новый сервер просто игнорирует — не падает, не ругается, а стартует полностью открытым. Проверяйте не конфиг, а поведение:

curl -s -o /dev/null -w 'no-token=%{http_code}\n' \
  http://127.0.0.1:8000/api/v2/tenants/default_tenant/databases/default_database/collections
# ожидаем 401; получили 200 — аутентификации нет

Без токена правильный ответ — 401 с телом {"error":"Unauthorized"}. Получили 200 — вернитесь к именам переменных.

Дальше периметр: порт на loopback (-p 127.0.0.1:8000:8000 в Docker, --host 127.0.0.1 в systemd), наружу — только через Nginx с сертификатом, порт закрыт фаерволом: ufw allow 22/tcp, ufw allow 443/tcp, ufw deny 8000/tcp. В Nginx — две правки, без которых всё ломается на первой загрузке:

location / {
    proxy_pass http://127.0.0.1:8000;
    proxy_set_header Host $host;
    client_max_body_size 100m;   # иначе 413 на пакетной вставке
    proxy_read_timeout 300s;     # индексация большого батча дольше 60 секунд
}

Дефолтный client_max_body_size 1m режет вставку крупнее пары сотен документов: клиент получит от Nginx HTML-страницу 413 Request Entity Too Large, а библиотека — непонятную ошибку разбора ответа. И не включайте auth_basic на этом location — он делит заголовок Authorization с Bearer-токеном клиента.

Данные пропали после рестарта: пути, PersistentClient и database is locked

Самый болезненный класс: первопричина почти всегда в том, что писали не туда, куда думали.

Клиент в памяти. chromadb.Client() — эфемерный, всё живёт в оперативке и умирает с процессом; для файла нужен chromadb.PersistentClient(path="/var/lib/chroma"). Метода persist() в современных версиях нет — старый код падает с AttributeError: 'Client' object has no attribute 'persist'.

Не тот каталог в Docker. Образы 1.x запускают сервер с --path /data, а в 0.4–0.5 по умолчанию был /chroma/chroma. Смонтировали том по старой инструкции — Chroma пишет в /data внутри слоя контейнера, и всё работает до первого docker rm. Проверка:

docker exec chroma sh -c 'ls -la /data /chroma/chroma 2>&1'
docker inspect -f '{{json .Mounts}}' chroma | jq

Рядом с chroma.sqlite3 должны быть подкаталоги с UUID — бинарные сегменты HNSW, и копировать один chroma.sqlite3 бесполезно: индекса в нём нет, бэкапьте остановленный сервис и весь каталог целиком. Отдельно ловят права: контейнер стартовал под root, сервис перевели на пользователя — sqlite3.OperationalError: attempt to write a readonly database, лечится sudo chown -R chroma:chroma /var/lib/chroma.

Два процесса на один каталог. Классика: gunicorn с четырьмя воркерами, каждый со своим PersistentClient на тот же путь. Результат — sqlite3.OperationalError: database is locked под нагрузкой и расходящиеся HNSW-сегменты в памяти разных процессов; WAL тут не спасает, конфликтует не только SQLite, но и файлы индекса. Правило жёсткое: один каталог — один процесс. Нужен доступ из нескольких воркеров — поднимайте chroma run отдельным сервисом и ходите через HttpClient.

Файл не уменьшается после удаления. Удалили сотни тысяч документов, а chroma.sqlite3 того же размера — нормальное поведение SQLite: страницы освобождены, файл не усечён. Лечится на остановленном сервисе:

sudo systemctl stop chroma
sudo -u chroma /opt/chroma/venv/bin/chroma utils vacuum --path /var/lib/chroma
sudo systemctl start chroma

Честное ограничение: удаление помечает вектор в HNSW удалённым, но не убирает из графа — память освободится только после перестроения. Сценарий «перезаписываем корпус на месте» для Chroma плохой; лучше строить новую коллекцию и переключаться на неё.

Отвечает, но не тем: размерность, метрика, embedding function и лимит батча

Худший класс — тот, где ошибки нет: сервер отвечает 200, результаты приходят, а качество ответов RAG на уровне подбрасывания монеты.

Размерность. Хотя бы кричит вслух:

chromadb.errors.InvalidDimensionException: Embedding dimension 384 does not match collection dimensionality 1536

Размерность фиксируется первой вставкой и не меняется: сменили модель (all-MiniLM-L6-v2 даёт 384, text-embedding-3-small — 1536) — заводите новую коллекцию и переиндексируйте корпус, настройками не чинится.

Функция эмбеддингов не та. Опаснее случай, когда размерности совпали: в старых версиях функция эмбеддингов не хранилась с коллекцией, и get_collection("docs") без embedding_function на чтении молча подставляет дефолтную MiniLM. Ошибки нет, ответы есть, релевантность на нуле — передавайте функцию явно и на записи, и на чтении.

Метрика расстояния. По умолчанию Chroma использует l2 — квадрат евклидова расстояния, не косинус. «Похожесть», посчитанная как 1 - distance, уходит в минус, и человек решает, что база сломана. Косинус задаётся при создании коллекции и позже не меняется:

# ветка 0.x
col = client.create_collection("docs", metadata={"hnsw:space": "cosine"})
# ветка 1.x
col = client.create_collection("docs", configuration={"hnsw": {"space": "cosine"}})

Пустая выдача. Лог Number of requested results 10 is greater than number of elements in index 3, updating n_results = 3 означает ровно то, что написано — коллекция почти пуста, обычно потому что приложение и сервер смотрят в разные хранилища.

Фильтры по метаданным. Типы должны совпадать: год строкой "2024" не найдётся условием {"year": 2024}. Пустой фильтр даёт ValueError: Expected where to have exactly one operator, got {} — не подставляйте where={}, просто не передавайте where.

Лимит батча. Массовая загрузка обрывается на явном исключении:

ValueError: Batch size of 41666 is greater than max batch size of 5461

Лимит зависит от версии и сборки — спросите у клиента: client.get_max_batch_size(). Практика: батч 500–1000 документов вместе с client_max_body_size 100m на Nginx проходит стабильно.

Какой сервер под Chroma брать в MAATRIX

Chroma в каталоге apps.maatrix.io нет — сервер приезжает чистым, Chroma ставится по инструкции из этой статьи: venv, юнит systemd, Nginx, от силы полчаса работы. Хочется готовое одним заказом — в каталоге есть родственные сборки: Qdrant — альтернативная векторная база, AnythingLLM/Dify — RAG-конвейеры поверх неё, Ollama — локальные модели, LiteLLM — шлюз к внешним API; ставятся автоматически на Ubuntu и Debian, доступы — в кабинете.

Требования к железу считаются, а не гадаются: HNSW-индекс активной коллекции Chroma держит в памяти, объём — число векторов × размерность × 4 байта на float32, плюс около 130 байт на узел графа, плюс процесс Python на 300–450 МБ. 200 тысяч фрагментов при размерности 768 — около 615 МБ векторов, миллион при 1536 — уже 6,1 ГБ, и это до кеша SQLite и вашего приложения.

Честный минимум: 2 vCPU, 4 ГБ RAM, 40 ГБ NVMe. Хватает на десятки тысяч фрагментов и дефолтную MiniLM рядом. На 1–2 ГБ Chroma формально стартует, но onnxruntime с моделью эмбеддингов съедает около полугигабайта, и первая массовая индексация уходит в OOM — не ошибка Chroma, а Killed в консоли и запись в dmesg.

Комфортный вариант: 4 vCPU, 8–16 ГБ RAM, 80–160 ГБ NVMe. Индекс помещается в память целиком, есть запас под перестроение — нужны две копии на время миграции. Ядра здесь не для одного запроса — HNSW считает вектор быстро в один поток, а vCPU разгоняют параллельные запросы под нагрузкой. Коллекций много и разом они не влезают — включайте CHROMA_SEGMENT_CACHE_POLICY=LRU с CHROMA_MEMORY_LIMIT_BYTES, иначе каждая тронутая коллекция остаётся в памяти навсегда.

Локация — Великобритания (Лондон). Векторная база редко живёт одна: рядом сервис, который считает эмбеддинги и ходит во внешнее API. Из Лондона такие вызовы идут без отказов вида 403 unsupported_country_region_territory, а до Европы канал короткий, плюс юрисдикция — иногда требование заказчика. Вся аудитория внутри России — берите RU, там перевесят 152-ФЗ и пинг. Оплата картой российского банка, СБП, криптовалютой или токеном MAAT — иностранная карта не нужна.

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

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

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

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

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

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

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

Обновил Chroma до 1.x, приложение получает 410 на каждый запрос. Что делать?

Ветка 1.x убрала /api/v1. Обновите chromadb (или chromadb-client) до версии не ниже серверной и проверьте обвязку — LangChain и подобные библиотеки нередко держат старый пин. Откат на прежний образ сервера лечит симптом, не причину.

Можно ли подключить к одному каталогу несколько процессов приложения?

Нет: PersistentClient рассчитан на один процесс, под gunicorn с несколькими воркерами получите sqlite3.OperationalError: database is locked и расхождение индексов в памяти. Поднимите chroma run отдельным сервисом и ходите через HttpClient — штатная схема для сервера.

Удалил половину документов, а файл и память не уменьшились — это баг?

Нет: SQLite не усекает файл сам, запустите chroma utils vacuum --path /var/lib/chroma на остановленном сервисе. Память под HNSW сразу не освободится — удалённые векторы остаются в графе до перестроения. Если корпус регулярно обновляется целиком, стройте новую коллекцию и переключайте на неё приложение.

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

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