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
Замер на проде 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
54 lines
2.9 KiB
Python
54 lines
2.9 KiB
Python
"""Пул коннектов не меньше суммы потолков одновременности процесса (#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}с"
|
||
)
|