MAATRIX / Блог / Langfuse не видит трейсы: причины и решение

Langfuse не видит трейсы: причины и решение

Langfuse не видит трейсы: причины и решение

MAATRIX

Дашборд Langfuse пуст, а трейсы не приходят — хотя приложение точно дёргает LLM, и все шесть контейнеров в docker compose ps зелёные. Причина почти никогда не в разметке @observe: событие теряется на одном из четырёх шагов между SDK и ClickHouse — в клиенте, на входе в API, в очереди воркера или в блоб-хранилище. Разберём каждый шаг по порядку — с реальными текстами ошибок и командами проверки.

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

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество 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Отклонено на входе в Web3
Web и Worker живы, трейсов нет часамиЗастряла очередь или ClickHouse4
В логе воркера 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 — десятки моделей в одном окне. Оплата картой РФ и по СБП.