fix(metrics): развязать vidconf_pipeline_sessions с основным пулом БД

_refresh_pipeline_sessions_gauge и metrics_endpoint ходили через
Depends(get_session) — основной пул, разделяемый с API-запросами. В
инциденте 07.08 это дало 16 падений в api/metrics.py ровно тогда, когда
метрики были нужнее всего (пул исчерпан). db_up/db_pool_* уже были
развязаны в 0.0.32, эта метрика — нет (мешал тестовый харнесс).

Добавлен get_metrics_session (core/db.py) — отдельный движок с NullPool,
как у check_db_up, но с полноценной ORM-сессией для репозитория. Тестовый
харнесс (app-фикстура) подменяет её на ту же savepoint-сессию, что и
get_session, — иначе /metrics не видел бы данные теста.
This commit is contained in:
2026-08-10 16:09:09 +03:00
parent 0a13589612
commit c4485d43b1
3 changed files with 66 additions and 24 deletions

View File

@@ -65,6 +65,38 @@ async def check_db_up() -> bool:
return True
# --- Сессия для метрик, читающих данные (не только «жив/мёртв»), вне основного
# пула (сессия 37, доделка session 32/33 — см. докстринг `api/metrics.py`) ----
#
# `check_db_up` выше обходится голым соединением ("SELECT 1"), но
# `vidconf_pipeline_sessions` нужна полноценная ORM-сессия (репозиторий,
# группировка по статусу) — `NullPool`, как и у `_probe_engine`: каждый вызов
# открывает новое соединение и сразу закрывает его, бюджет основного пула
# (`engine.pool`) не расходуется. `async_sessionmaker` — тот же паттерн, что
# `async_session_maker` выше, просто на другом движке.
_metrics_probe_engine: AsyncEngine = create_async_engine(
settings.database_url,
poolclass=NullPool,
connect_args={"timeout": settings.db_probe_timeout_s},
)
_metrics_session_maker = async_sessionmaker(_metrics_probe_engine, expire_on_commit=False)
async def get_metrics_session() -> AsyncGenerator[AsyncSession, None]:
"""Зависимость FastAPI для метрик, которым нужна БД, но не основной пул.
В отличие от `get_session()` (основной пул `engine.pool`, конкурирует за
те же 10+10×воркеров соединений, что и API-запросы), сессия здесь открыта
на `_metrics_probe_engine` — исчерпание основного пула эту метрику не
заденет, как и `vidconf_db_up`/`vidconf_db_pool_*`. Тестовый харнесс
(`tests/conftest.py`) подменяет и её на savepoint-сессию теста — так же,
как `get_session` — иначе тест `vidconf_pipeline_sessions` не видел бы
данные, ещё не закоммиченные за пределы savepoint.
"""
async with _metrics_session_maker() as session:
yield session
def db_pool_checked_out() -> int:
"""Число соединений основного пула, занятых прямо сейчас — без обращения к БД.