Langfuse не видит трейсы: причины и решение
Дашборд Langfuse пуст, а трейсы не приходят — хотя приложение точно дёргает LLM, и все шесть контейнеров в docker compose ps зелёные. Причина почти никогда не в разметке @observe: событие теряется на одном из четырёх шагов между SDK и ClickHouse — в клиенте, на входе в API, в очереди воркера или в блоб-хранилище. Разберём каждый шаг по порядку — с реальными текстами ошибок и командами проверки.
Содержание
- Путь трейса от SDK до дашборда: где именно он теряется
- Клиент ничего не отправил: асинхронный SDK и незавершённый flush
- Сервер отклонил событие: не те ключи, не тот host, не та версия
- Очередь не двигается: воркер жив, а трейсы не обрабатываются
- Скрытая причина: воркер не достучался до блоб-хранилища
- Трейс на самом деле есть: проект, окружение, часы и просто задержка
- Какой сервер под Langfuse брать в MAATRIX
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Путь трейса от SDK до дашборда: где именно он теряется
Если docker compose ps показывает все контейнеры в Up, а раздел Traces пуст — инфраструктура ни при чём: это не «контейнер не поднялся» (тому разбор — Langfuse на сервере: частые ошибки и решения), а случай честнее — всё живо, а данные не долетают.
SDK копит спаны в фоновом потоке и сбрасывает пачкой на /api/public/ingestion или, для OTEL-экспортёров, на /api/public/otel/v1/traces. langfuse-web принимает пачку, пишет сырое событие в S3-совместимое хранилище и кладёт в очередь Redis только ссылку — ответ 200 клиенту значит «принял в очередь», не «сохранил в ClickHouse». Дальше langfuse-worker разбирает очередь, скачивает событие из хранилища и пишет в ClickHouse, откуда читает дашборд. Трейс может застрять на любом из четырёх шагов, не оставив следа на предыдущих.
| Симптом | Где застряло | Раздел |
|---|---|---|
| В debug-логе SDK нет попыток отправки | Не покинуло клиента | 2 |
Failed to export span batch code: 401 | Отклонено на входе в Web | 3 |
| Web и Worker живы, трейсов нет часами | Застряла очередь или ClickHouse | 4 |
В логе воркера Failed to upload event to S3 | Обрыв воркер ↔ хранилище | 5 |
| Трейс находится, просто не там | Ложная тревога | 6 |
Проверка первым делом:
curl -s "http://127.0.0.1:3000/api/public/health?failIfDatabaseUnavailable=true"
Базовый /api/public/health по умолчанию не проверяет Postgres — отвечает 200, даже если база лежит, чтобы сервис не падал из-за временного сбоя БД; с failIfDatabaseUnavailable=true проверка честная. 200 — дальше не про web, а про то, что происходит с событием после приёма.
Клиент ничего не отправил: асинхронный SDK и незавершённый flush
Самая частая причина — не баг, а архитектура: SDK не шлёт HTTP-запрос на каждый вызов @observe, а пишет в очередь в памяти и сбрасывает её по таймеру (flush_interval/LANGFUSE_FLUSH_INTERVAL) или по размеру пачки (flush_at/LANGFUSE_FLUSH_AT). Короткоживущий процесс — скрипт, cron-задача, ячейка Jupyter, AWS Lambda — почти всегда завершается раньше, чем сработает таймер: событие остаётся в памяти уже мёртвого процесса, без исключения, лога или ответа сервера.
Лечится явным вызовом перед выходом:
from langfuse import get_client
langfuse = get_client()
@observe()
def process_request(payload):
...
process_request(data)
langfuse.flush() # обязательно в скриптах, cron, serverless, Jupyter
В долгоживущем процессе уместнее langfuse.shutdown() — он ещё и дожидается in-flight запросов. Для serverless вызывайте flush до return: иначе рантайм заморозит процесс раньше, чем уйдёт запрос.
Проверить, пытается ли SDK вообще что-то отправить, проще всего через debug:
langfuse = get_client(debug=True)
или LANGFUSE_DEBUG=True: в логе появятся попытки экспорта, их отсутствие значит, что дело в буфере, а не в сети. Полезен и langfuse.auth_check() — лёгкий запрос к API, True/False без ожидания первого трейса.
Три частых способа тихо потерять данные без сети вообще:
tracing_enabled=False(в SDK v2 —enabled) — трейсинг отключили для теста и забыли включить обратно.sample_rateменьше 1.0 — часть трейсов отбрасывается на клиенте намеренно, выглядит как «иногда доходит».- Пустые
input/outputпри живом трейсе — не потеря, а отключённый захват аргументов:@observe(capture_input=True, capture_output=True)илиLANGFUSE_OBSERVE_DECORATOR_IO_CAPTURE_ENABLED=true.
Развернуть за пару минут
Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть LangfuseСервер отклонил событие: не те ключи, не тот host, не та версия
Если debug-лог показывает честную попытку отправки, а трейсов всё равно нет — смотрите код ответа. Самый частый и обманчивый случай:
Failed to export span batch code: 401, reason: {"message":"Invalid credentials. Confirm that you've configured the correct host."}
Сообщение прямо просит проверить host — и в девяти случаях из десяти дело именно в нём: без явной LANGFUSE_BASE_URL клиент стучится в https://cloud.langfuse.com, публичное облако, регион EU. При self-hosted это значит, что ключи вашего сервера летят на чужой домен, где такого проекта нет — curl до вашего сервера отвечает, а SDK всё равно получает 401, потому что стучится не туда.
echo $LANGFUSE_BASE_URL # пусто — вот и причина
export LANGFUSE_BASE_URL=https://langfuse.example.com
Второй слой той же ошибки — публичный (pk-lf-...) и секретный (sk-lf-...) ключи перепутаны или взяты из чужого проекта: UI открыт на «Project A», а переменные окружения — от «Project B», и трейсы уходят в другой, невидимый сейчас проект.
Третий источник — рассинхрон версий: с 2025 года Python и JS/TS SDK переписаны на OpenTelemetry, и self-hosted сервер обязан быть не старше 3.63.0, иначе /api/public/otel либо не существует, либо не понимает формат свежего клиента — стоит сверить, когда обновлялся образ и когда ставился SDK.
Отдельно — переход на Langfuse v4, общедоступный для self-hosted с 17 августа 2026 года: в режимах dual и legacy события от SDK младше Python 4.7.0 или JS/TS 5.4.0 не теряются, а показываются с задержкой около 15 минут, пока идёт двойная запись в старые и новые таблицы ClickHouse. Зависшие фоновые миграции на self-managed Postgres видно напрямую:
SELECT name, failed_at, failed_reason
FROM background_migrations
WHERE finished_at IS NULL;
Пустой результат — задачи завершились штатно, 15 минут значит «ещё не появился», не «потерян». Держать self-hosted на v3 бесконечно не вариант: патчи для неё обещаны только до января 2027 года.
Очередь не двигается: воркер жив, а трейсы не обрабатываются
Есть категория коварнее Restarting из статьи про частые ошибки: langfuse-worker стабильно Up, ничего тревожного не пишет в лог — и именно поэтому выглядит здоровым, хотя очередь давно не двигается. docker compose ps видит, что процесс жив, а не то, что он что-то делает.
У воркера свой health-эндпоинт с двумя параметрами, которых нет в базовой проверке:
curl -s "http://127.0.0.1:3030/api/health?failIfQueueConsumptionStuck=true"
curl -s "http://127.0.0.1:3030/api/health?failIfEventPropagationStuck=true"
Первый вернёт 503, если очередь Redis не двигалась дольше 60 минут (LANGFUSE_QUEUE_CONSUMPTION_STUCK_THRESHOLD_MINUTES). Второй — про фоновую job переноса событий в новые таблицы ClickHouse при двойной записи из раздела 3: не более 15 минут с последнего запуска (LANGFUSE_EVENT_PROPAGATION_STUCK_THRESHOLD_MINUTES). Оба 200 — к разделу 5, один 503 — застряло здесь.
Если очередь стоит при живом Redis, следующий подозреваемый — ClickHouse, с точки зрения записи новых данных, а не миграций при старте:
docker compose logs --tail=200 langfuse-worker | grep -iE "clickhouse|insert"
Таймаут или connection refused на clickhouse:8123 означает то же, что при миграциях — сеть или неверный CLICKHOUSE_URL — но проявляется не падением контейнера, а тихо растущей очередью.
Отдельная причина на переходе с v3 на v4: миграция схемы ClickHouse требует расширенный набор грантов сверх обычной записи — ALTER ADD COLUMN, ALTER VIEW MODIFY QUERY, DROP VIEW, права на system.parts/system.mutations. На управляемом ClickHouse с урезанной учёткой без DDL воркер упадёт на этом шаге при первом запуске новой версии — события копятся, обработать их некому.
Скрытая причина: воркер не достучался до блоб-хранилища
Причина, которую реже всего подозревают первой. С архитектуры третьей ветки блоб-хранилище (S3 или совместимое — MinIO, любой S3-провайдер) обязательно для self-hosted установки, не опционально для медиа: туда пишется сырое тело события на шаге приёма в Web, оттуда его читает воркер. Мост не работает — конвейер стоит намертво, а приложение всё равно получает 200 OK от /api/public/ingestion, ведь запись в очередь и обработка разнесены во времени.
В логе воркера при таком сбое встречается ровно такая строка — реальная формулировка, не раз всплывавшая в трекере проблем Langfuse:
Failed to upload event to S3
Важнее ошибки то, что Web и Worker обязаны смотреть на одно хранилище одними данными. Разъехавшиеся значения LANGFUSE_S3_EVENT_UPLOAD_* между двумя .env — итог правки секретов по отдельности: Web кладёт события в бакет исправно, Worker стучится туда чужими ключами и получает отказ. Проверяется бисекцией — заглянуть в бакет напрямую:
AWS_ACCESS_KEY_ID=minio AWS_SECRET_ACCESS_KEY=miniosecret \
aws --endpoint-url http://127.0.0.1:9000 s3 ls s3://langfuse/events/ | tail -5
Объекты есть, а трейсов в UI нет — проблема у Worker, забрать не может. Бакет пуст при работающем приложении — проблема у Web, до S3 события не доходят вообще.
Отдельная ловушка с MinIO и любым не-AWS хранилищем — переменная LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE. Без true клиент обращается к бакету по virtual-hosted стилю вида langfuse.имя-хоста, которого в DNS нет — вместо отказа доступа получаете ошибку резолвинга, похожую на проблему сети:
LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=http://minio:9000
LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE=true
С настоящим AWS S3 добавляется ещё один подозреваемый — права IAM: PutObject нужен Web, GetObject — Worker'у, и ключ только на запись пройдёт первую половину конвейера, а на второй молча застрянет.
Трейс на самом деле есть: проект, окружение, часы и просто задержка
Прежде чем перепроверять инфраструктуру заново, исключите частые ложные тревоги.
Не тот проект. Ключи и открытый дашборд смотрят на разные проекты — итог склеенного из двух .env. Публичный ключ pk-lf-... принадлежит одному проекту; сверьте с настройками в UI, а не полагайтесь на память.
Фильтр окружения. У Langfuse есть встроенное environment: без явного указания трейс помечается default. Переменная LANGFUSE_TRACING_ENVIRONMENT (или параметр environment, приоритетнее) разносит staging и production в одном проекте, а в UI есть общий фильтр по окружению. Выставили production и забыли — трейсы из staging выглядят как «не приходят», хотя просто отфильтрованы.
Расхождение часов. Дашборд по умолчанию показывает последние 24 часа. Без NTP на сервере события попадают в ClickHouse с меткой за пределами окна UI:
timedatectl status
System clock synchronized: no — вот причина; чинится chrony или timedatectl set-ntp true.
Просто ещё не появился. Обработка асинхронна на каждом шаге, несколько секунд между отправкой и появлением в UI — норма. Во время миграции v3→v4 с двойной записью задержка для устаревших SDK доходит до 15 минут (раздел 3). Расширьте диапазон дат и подождите минуту, прежде чем перезапускать контейнеры.
Какой сервер под Langfuse брать в MAATRIX
Причины из разделов 2–6 объединяет одно: конвейер доставки трейсов у self-hosted Langfuse — отдельная многошаговая система. Расчёт ресурсов под связку из шести контейнеров — в материале установка Langfuse на Ubuntu 24.04; здесь — конкретно о надёжности доставки.
Первое — число реплик воркера: единственный langfuse-worker при перезапуске останавливает разбор очереди, пока поднимается заново, а при живом продакшене это регулярные провалы «трейсы не приходят», которые чинят себя сами. Вторая реплика в docker-compose.yml — не про производительность, а про то, чтобы очередь не вставала целиком из-за одного перезапуска. Второе — сетевая близость: Failed to upload event to S3 с таймаутом вместо отказа доступа типична, когда воркер и хранилище разнесены через интернет; держать все шесть контейнеров в одной приватной сети — требование к надёжности.
Честный минимум: 4 vCPU, 8 ГБ RAM, 80 ГБ NVMe. Одна реплика воркера, MinIO на том же диске — хватает для одного проекта и умеренного потока. При заметном трафике очередь разбирается впритык, и перезапуск воркера ощущается как временная пропажа трейсов из раздела 4.
Комфортный вариант: 8 vCPU, 16 ГБ RAM, 150–200 ГБ NVMe. Две реплики воркера и запас под растущий бакет сырых событий — туда пишется не только финальный трейс, но и промежуточное состояние каждого батча.
В каталоге apps.maatrix.io Langfuse ставится автоматически при заказе — стек поднимается настроенным на Ubuntu или Debian, адрес консоли и ключи появляются в личном кабинете, в разделе «Доступ». Второй воркер или отдельный диск под ClickHouse — уже донастройка.
Локация — Великобритания. В трейсах оседают промпты и ответы модели — в проде это сообщения пользователей. Для команды с аудиторией в Европе держать копию по соседству с GDPR — разумная позиция, а короткий пинг до серверов в ЕС снижает задержку batch-записей. Подробнее — в материале VPS в Великобритании для доступа к нейросетям.
Оплата — картами российских банков, по СБП, криптовалютой или токеном MAAT: иностранная карта не нужна, хотя сервер физически стоит в Лондоне.
Развернуть за пару минут
Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть LangfuseОбсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Частые вопросы
Трейсы появляются, но с задержкой в 10–15 минут — это нормально?
Для обычной отправки нет: flush_interval — секунды, не минуты. 10–15 минут — симптом двойной записи при миграции self-hosted с v3 на v4 для устаревших SDK (Python младше 4.7.0, JS/TS младше 5.4.0). Обновите SDK — задержка исчезает.
Как быстрее всего понять, воркер жив, а не просто завис?
docker compose ps покажет статус контейнера, а curl "http://127.0.0.1:3030/api/health?failIfQueueConsumptionStuck=true" — застряла ли очередь дольше часа. Оба способа не требуют доступа к коду и отвечают за секунды.
Ключи верные, а всё равно 401 Invalid credentials. Confirm that you've configured the correct host. В чём дело?
Почти всегда не в ключах: LANGFUSE_BASE_URL не задана, и SDK по умолчанию стучится на https://cloud.langfuse.com, а не на ваш self-hosted сервер. Проверьте echo $LANGFUSE_BASE_URL, прежде чем перегенерировать ключи.
Нужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.