Chroma на сервере: частые ошибки и решения
Chroma заводится на ноутбуке в три строки — и ровно поэтому на сервере ломается там, где не ждёшь: клиент получает 410 вместо ответа, коллекция после рестарта контейнера пуста, а поиск честно возвращает 200 и полную чушь. Почти все ошибки Chroma сводятся к десятку причин: версия API, версия sqlite3, каталог хранилища, размерность вектора и метрика расстояния. Разбираем по симптомам — с точными текстами ошибок, командами проверки и рабочими конфигами.
Содержание
- Сначала три команды: жив ли сервер и та ли у него версия API
- Chroma не устанавливается и не стартует: ошибки sqlite3, venv и порта
- Клиент не подключается: 410 на v1, tenant not found и лишний chromadb
- Открытая база и 401: аутентификация, которую переименовали в 0.5
- Данные пропали после рестарта: пути, PersistentClient и database is locked
- Отвечает, но не тем: размерность, метрика, embedding function и лимит батча
- Какой сервер под Chroma брать в MAATRIX
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество 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 — десятки моделей в одном окне. Оплата картой РФ и по СБП.