gendesign/tradein-mvp/backend/tests/test_3408_pool_ceiling.py
bot-backend 9f696299de
All checks were successful
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 9s
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m16s
fix(mera): sync-БД источников эстиматора — с event loop в поток и не через фетч
Замер на проде 11.09 (изнутри хоста, тот же контейнер):
- одна оценка 0.44 с (повтор адреса) / 0.97 с (новый адрес), из них БД 252/458 мс;
- N=8 параллельных — все 200, heartbeat /health p95 5-7 мс, max 113-160 мс:
  loop сегодня НЕ голодает, «добавить воркеров uvicorn» замером не подтверждается
  (и умножило бы на N оба семафора, пять in-process лимитеров и пул);
- зато одна фоновая догрузка Яндекса держала коннект пула 8.5 с (лиз прокси
  33.856 → запись 42.334), а таких задач разрешено 8 при пуле 15.

Правки:
- estimator `_db_step`: SELECT/UPSERT кэша источников уходят в `asyncio.to_thread`
  и завершают транзакцию — коннект возвращается в пул ДО внешнего HTTP;
- core/db: max_overflow 10→15 (потолок 20 на процесс ≥ 4+4+8 объявленных
  потолков одновременности) и pool_timeout 30→5 с (короче бюджета источника 8 с,
  иначе занятый пул съедает и бюджет запроса, и поток to_thread).

Локальный замер ДО/ПОСЛЕ на тех же величинах: loop стоял 301 мс (0 тиков соседней
корутины) → 0.2 мс (23.5k тиков); ожидание коннекта соседом во время фетча —
таймаут пула → 0.1 мс.

Refs #3083, #3408
2026-09-12 01:01:54 +05:00

54 lines
2.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Пул коннектов не меньше суммы потолков одновременности процесса (#3408 п.1).
Процесс сам объявляет, сколько одновременной работы он допускает: 4 оценки, 4
подсказки, 8 фоновых догрузок. Каждая из этих единиц работы держит СВОЮ сессию.
Если сумма больше пула, исчерпать пул можно штатной нагрузкой — и упереться не в
ресурс, а в `QueuePool limit ... timed out`, причём на публичной ручке.
На origin/main сумма 16 против дефолта SQLAlchemy 5+10=15 — тест красный.
Гейт нужен не ради текущих чисел, а ради будущих: поднять `_ESTIMATE_CONCURRENCY`
или `_MAX_DEFERRED_REFRESH_TASKS`, не поднимая пула, станет видно здесь.
Добавится воркер uvicorn (#3083) — числа per-process не меняются, но суммарный
потолок коннектов умножается на число воркеров; это отдельное решение, не это.
"""
from __future__ import annotations
import os
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")
from app.api.public.mera import _SUGGEST_CONCURRENCY
from app.api.v1.trade_in import _ESTIMATE_CONCURRENCY
from app.core.db import engine
from app.services.estimator import _MAX_DEFERRED_REFRESH_TASKS
def test_pool_ceiling_covers_declared_concurrency() -> None:
declared = _ESTIMATE_CONCURRENCY + _SUGGEST_CONCURRENCY + _MAX_DEFERRED_REFRESH_TASKS
ceiling = engine.pool.size() + engine.pool._max_overflow
assert ceiling >= declared, (
f"пул {ceiling} меньше суммы потолков одновременности {declared} "
f"({_ESTIMATE_CONCURRENCY} оценок + {_SUGGEST_CONCURRENCY} подсказок + "
f"{_MAX_DEFERRED_REFRESH_TASKS} фоновых догрузок) — штатная работа исчерпает пул"
)
def test_pool_checkout_wait_shorter_than_source_budget() -> None:
"""Ожидание коннекта короче бюджета внешнего источника.
Иначе занятый пул съедает весь бюджет запроса (и поток `asyncio.to_thread`,
которых тоже конечное число) вместо того, чтобы деградировать один источник.
"""
from app.core.config import settings
budget = min(
settings.estimate_yandex_valuation_timeout_s,
settings.estimate_cian_valuation_timeout_s,
)
assert engine.pool._timeout < budget, (
f"pool_timeout {engine.pool._timeout}с ≥ бюджета источника {budget}с"
)