Status
active. Phase 1 (internal logging off, коммит da4ef041) применена 2026-05-19. Phase 1b (memory bump 256Mi/512Mi → 512Mi/1Gi коммит 771e9046 + GOMEMLIMIT=900MiB коммит 103c2b92) применена в тот же день после обнаружения вторичного OOM. Phase 2 — open, пересматривается по триггерам ниже.
Контекст
bifrost — наш LLM gateway. На staging recurrent OOM (BG-12021, алерты с 10 апреля 2026 — три PodOOMKilled подряд: 2:24, 6:29, 10:34) имел два независимых источника, диагностированных последовательно:
Источник #1 — SQLite logs leak (slow grow, часы). По-умолчанию bifrost писал per-call inference data (request body, response, tokens, cost, latency, retries, virtual-key, fallback chain) в локальную SQLite БД ./logs.db внутри pod’а. Файл жил в container’s ephemeral root fs (нет PVC, нет emptyDir). Default retention — 365 дней, SQLite не VACUUM после delete, файл рос монотонно → OOM каждые ~4 часа на 512Mi limit. Pattern: pod рестартал, очищал БД, через 4-8 часов снова.
Источник #2 — concurrent in-flight buffering (burst, минуты). Bifrost под algo-hub fanout обрабатывает ~30+ параллельных POST /v1/chat/completions калов по 5-30 секунд каждый. На пиковую нагрузку in-memory держится: request bodies, accumulating response buffers (LLM payloads 100KB-MB+), retry/fallback state, goroutine stacks, plugin chain clones. Bifrost vendor claim про low memory overhead (~11µs proxy footprint) относится к idle/single-request baseline — peak concurrent buffering выходит далеко за 512Mi. Без bumped limits Phase 1 alone не закрывала OOM: после deploy логирующего фикса pod был OOMKilled через 20 минут (наблюдалось 2026-05-19, Exit Code 137 после deploy da4ef041).
Параллельно existing observability:
- langfuse (HIPAA region) уже инструментирует
analysis-worker,b2c-dashboard,b2b-api,recommendations-portal,patient-portal— trace на бизнес-операцию + generation на каждый LLM-call (модель, prompt-version, tokens, latency, cost, scores). Покрывает все production-сервисы кромеalgo-hub(см. llm-observability-stack, секция «Слепые пятна»). - Bifrost admin UI на
:8080показывал тот же набор данных через/api/logs— фактически duplicate-источник, плюс уязвим к pod restart (полное обнуление) и наблюдённой count/size-based rotation поверх 365-day retention (записи ~37 минут old недоступны при uptime 157 минут — llm-observability-stack фиксирует это как «retention двухуровневый gotcha»).
Вопрос: какой способ хранить bifrost-side per-call data выбрать, чтобы остановить OOM, не плодя дублирующую инфру.
Рассматривали
Bifrost native flags — disable_content_logging + log_retention_days. Оставить logging on, но без request/response bodies + сократить retention до 7-30 дней. Тела — 95% объёма каждой записи, без них размер БД уменьшается на порядки. Сохраняет dashboard (latency/cost/tokens/model). Минус — SQLite не shrinks без VACUUM, поэтому leak только замедляется, не исчезает. Через недели снова OOM.
Postgres для logs_store. Bifrost нативно поддерживает Postgres вместо SQLite. У нас есть inngest-analysis-db (CloudSQL PG 16) с additional_databases = [] — можно добавить БД bifrost бесплатно через terragrunt. Решает память (данные не в RAM пода) и persistent storage за один шаг. Минус — bifrost делит CPU/IO инстанса с inngest/b2b_edge, plus operational overhead на terraform/secret/CloudSQL Proxy. Реализуем в Phase 2 если решим что gateway-audit нужен.
PVC + SQLite. Mount persistent disk вместо ephemeral fs. Не давит на RAM. Минус — bifrost deployment становится stateful (нельзя scale → 2 реплики без shared-disk-проблем), SQLite файл всё равно растёт без VACUUM, через месяцы PVC fills.
emptyDir на node disk. Дать том на ноде с sizeLimit. Запись на диск, не в RAM → OOM по памяти уходит сразу. Bounded. Минус — эфемерно (теряется при перешедулинге пода), и при заполнении SQLite перестаёт писать → может встать прокси.
Sidecar / CronJob для periodic VACUUM. Раз в час exec в pod, чистить старые записи + VACUUM. Минус — race с writes, VACUUM лочит БД, bifrost image не содержит sqlite3 CLI, нужен sidecar. Costly maintenance.
Just raise memory limit. Band-aid. Растёт медленнее → OOM реже, но не исчезает.
Disable logs_store completely. Полностью убирает leak. Bifrost остаётся stateless. Langfuse уже покрывает per-call data на application-слое. Минус — теряем gateway-level visibility: governance rejects, VK denials, provider routing decisions — application видит только final HTTP-статус от bifrost.
Forward logs в external sink — OTLP в Langfuse-OTel. Bifrost поддерживает OpenTelemetry export и нативный Langfuse plugin: вместо записи в SQLite gateway отправляет structured traces в Langfuse-OTel collector. Application-traces и gateway-traces оказываются в одном месте — единая корреляция по trace-id, без дублирующей инфры. Минус — нужен research конкретного config (какие именно flags/plugin в bifrost, что Langfuse-OTel ingest принимает, какие fields маппятся), и trace volume в Langfuse удваивается. Это ведущий кандидат на Phase 2 если выяснится что gateway-audit нужен.
stdout-only + Cloud Logging. Отключить logs_store, переключить bifrost на structured stdout-logging — Cloud Logging уже работает на GKE, поиск через Logs Explorer. Минус — bifrost stdout сейчас содержит только startup + HTTP-access события, не inference; не задокументировано поддерживает ли bifrost structured per-call stdout-режим.
Выбрали
Phase 1: client.enable_logging: false + logs_store.enabled: false — закрывает источник #1 (SQLite leak).
Изменение в argocd-applications/common/bifrost-proxy/values.yaml — два булевых флага в configmap.
Почему
- Langfuse покрывает per-call observability на application-слое. Все production-сервисы кроме algo-hub уже инструментированы. Bifrost dashboard на
:8080был duplicate-источником. - SQLite leak устраняется полностью — файл не создаётся вообще, нет места для роста.
- Zero effort, zero new infra. Один commit в
values.yaml. Нет terraform правок, нет нового секрета, нет PVC, нет sidecar. - Не блокирует Phase 2. Если выяснится что gateway-audit нужен — OTLP-export в Langfuse или Postgres logs_store включается поверх этого решения, не вместо.
Phase 1b: bumped resources + GOMEMLIMIT env var — закрывает источник #2 (concurrent in-flight buffering). Двусоставная правка, обе части необходимы:
resources.requests.memory: 512Mi,resources.limits.memory: 1Gi(с 256Mi/512Mi) — сохраняет стандартное 1:2 соотношение request:limit, соответствует actual production fan-out от algo-hub (30+ параллельных long-running LLM calls × payload буферы 100KB-MB+ × retry/fallback state).deployment.env.GOMEMLIMIT: "900MiB"— soft memory limit для Go runtime, ~88% от 1Gi container limit. Без этого env var Go runtime не знает про container limit и может accumulate heap до hard cgroup-cap до того как GC сработает → OOM при ещё-собираемой памяти. GOMEMLIMIT делает GC aggressive при подходе к 900MiB, оставляя 100MiB headroom для off-heap (goroutine stacks, syscall buffers, plugin chain state). Известный Go-in-container gotcha; upstream bifrost docs рекомендуют 90% от container limit2.
Defaults в Helm chart (256Mi/512Mi, без GOMEMLIMIT) изначально рассчитывались на light traffic. Выросший production load и Go runtime неосведомлённость о container limit делали старые defaults under-provisioned под двум осям одновременно.
Почему отдельная Phase 1b, не band-aid
- Двумерный root cause требует двумерного фикса. Phase 1 убирает growth-over-time, Phase 1b — peak-under-load. Без 1b после Phase 1 OOM сохранялся (наблюдалось 2026-05-19 deploy → 20 минут → OOMKilled).
- Sizing соответствует actual workload. 30+ concurrent LLM-calls по 5-30 сек × payload 100KB-MB × retry/fallback state не помещаются в 512Mi by basic arithmetic.
GOMEMLIMITorthogonal к sizing. Даже с adequate memory limit, без runtime-side hint GC поведение остаётся реактивным к hard cap’у. Memory bump решает «сколько»,GOMEMLIMIT— «когда GC будить».- Нет дополнительной инфры. Правки только в
values.yaml, никаких новых компонентов.
Следствия
Algo-hub становится абсолютно слепым per-LLM-call. В algo-hub нет Langfuse SDK (нет import, нет env-vars). Раньше bifrost /api/logs хотя бы фиксировал что-через-него-прошло — теперь и этот канал ушёл. Виден только e2e на GCP LB + stdout (HTTP-метаданные без LLM-payload). Это резко повышает приоритет algo-hub Langfuse integration — отдельный open thread в llm-observability-stack.
GET /api/logs?limit=N больше не возвращает данные. Bifrost admin UI на :8080 без per-call dashboard. Endpoint /api/logs/<id> для post-hoc reconstruction app-side rejection (использовалось в BG-1429 verification — bifrost зафиксировал 6 compute-LLM-calls со status=success, app-side отверг 3 из них по schema mismatch) больше не работает.
Governance rejects не expose’ятся. VK denials, provider routing decisions, allowed-models violations — application получает HTTP 403, но следа на gateway-side нет. До Phase 1 они тоже не были в /api/logs (плагин отказывает в request middleware, до logging-хука) — изменение симметричное, но теперь и audit-recovery через SQLite CLI на pod’е невозможен.
SQLite leak устранён, OOM закрыт после Phase 1b. На staging после Phase 1 (da4ef041)3: idle memory держалась на 43-45Mi (~8% от 512Mi), но pod был OOMKilled через 20 минут под concurrent load от algo-hub. Phase 1b пакетом — memory bump (771e9046)4 поднял container limit до 1Gi для peak in-flight, плюс GOMEMLIMIT=900MiB (103c2b92)5 синхронизирует Go GC с container limit. Подтверждение — отсутствие PodOOMKilled алертов в течение observation periода после deploy обоих коммитов.
Открытые вопросы
Phase 2 — нужен ли gateway-level audit log? Решается после observation periода. Триггеры пересмотреть:
- Если algo-hub Langfuse integration не покрывает все нужные сигналы — например, governance rejects до VK validation (gateway отбил request до того как application узнал что request состоялся).
- Если придётся дебажить «application получил 403/500 от bifrost, что именно произошло на gateway» без доступа к app-side логам.
- Если регуляторика (HIPAA audit log) потребует независимый от application-слоя audit trail каждого LLM-call.
Если триггер сработал — OTLP-export в Langfuse-OTel предпочтительный кандидат. Gateway-side данные приземляются в том же месте что app-side, единая корреляция, без новой БД. Postgres logs_store через inngest-analysis-db — fallback если OTLP-маршрут окажется неработоспособным.
Algo-hub Langfuse integration. Не зависит от этого решения, но усугубляется им. Без bifrost backup-канала algo-hub /api/analyze-stream — самый слепой endpoint в системе. См. open thread в llm-observability-stack.
Prometheus plugin для aggregated stats. Bifrost capability есть, не активирован (prometheus plugin not found в startup log). До Phase 1 это давало бы p50/p95/p99 latency + RPS + error rate per provider в pull-режиме поверх per-call SQLite. Теперь — единственный возможный канал для aggregated gateway stats, включение становится более ценным.
Связано
- bifrost — vendor сам по себе; operational gotcha-секция ссылается сюда как на rationale текущего состояния
- bifrost-memory-model — техническая карта memory pressure (worker pools / in-flight / GC / FD), куда смотреть при «опять что-то с памятью»
- llm-observability-stack — visibility matrix отражает Phase 1: gateway-колонка обнуляется
- langfuse — primary observability канал, покрывает 95% LLM data; potential Phase 2 sink через OTLP
- llm-proxy-choice — выбор bifrost как gateway, observability tradeoffs скорректированы под Phase 1
- bifrost-custom-plugin-loading — параллельный bifrost-кастомизационный decision
Источники
Сноски
-
BG-1202 «Bifrost proxy OOM killed на staging — утечка памяти через SQLite logs», создан 2026-04-10, https://linear.app/realai-plus/issue/BG-1202. ↩
-
Bifrost Docker performance tuning docs, accessed 2026-05-19, https://docs.getbifrost.ai/deployment-guides/docker-tuning. Цит. по сессии
ildar/4e9b6d1a. ↩ -
Commit
da4ef041(Phase 1 — disable internal logging) наbloodgpt-stage-applications, 2026-05-19, https://github.com/Realai-plus/argocd-applications/commit/da4ef041. ↩ -
Commit
771e9046(Phase 1b — memory bump 256Mi/512Mi → 512Mi/1Gi) наbloodgpt-stage-applications, 2026-05-19, https://github.com/Realai-plus/argocd-applications/commit/771e9046. ↩ -
Commit
103c2b92(Phase 1b —GOMEMLIMIT=900MiB, Go GC aligned с container limit) наbloodgpt-stage-applications, 2026-05-19, https://github.com/Realai-plus/argocd-applications/commit/103c2b92. ↩