2 changed files with 36 additions and 0 deletions
|
|
@ -35,6 +35,22 @@ engine = create_engine(
|
||||||
# которые пул закрывает сам. Потолок процесса: 5 + 15 = 20; воркер один
|
# которые пул закрывает сам. Потолок процесса: 5 + 15 = 20; воркер один
|
||||||
# (docker-compose.prod.yml, uvicorn без --workers), Postgres max_connections=100.
|
# (docker-compose.prod.yml, uvicorn без --workers), Postgres max_connections=100.
|
||||||
max_overflow=15,
|
max_overflow=15,
|
||||||
|
# #3408 п.2. Дефолтные 30 с ожидания коннекта длиннее ЛЮБОГО бюджета внешнего
|
||||||
|
# источника в эстиматоре: 8 с (Yandex / Cian / house_meta), 12 с (geocode),
|
||||||
|
# 20 с (Avito IMV — `estimate_avito_imv_timeout_s`, config.py:852). Сам чекаут
|
||||||
|
# прервать `asyncio.wait_for` не может: он занимает поток `asyncio.to_thread`
|
||||||
|
# целиком, а пул потоков конечен (min(32, cpu+4)) — исчерпанный пул коннектов
|
||||||
|
# так превращается в исчерпанный пул потоков. 5 с короче самого КОРОТКОГО
|
||||||
|
# бюджета: занятый пул деградирует ОДИН источник, а не весь запрос.
|
||||||
|
#
|
||||||
|
# Отдельный коммит в конце ветки намеренно (ревью PR #3444): это единственная
|
||||||
|
# правка, которая меняет режим отказа с «медленно» на «быстро с ошибкой», и
|
||||||
|
# едет она во ВСЕ сервисы образа — backend, scraper, tgbot
|
||||||
|
# (tradein-mvp/docker-compose.prod.yml). За 29 ч логов исчерпания пула не было
|
||||||
|
# ни разу, то есть новое значение на проде пока не на чем проверить.
|
||||||
|
# ТРИГГЕР ОТКАТА на 30 с: любое `QueuePool limit ... timed out` в логах
|
||||||
|
# бэкенда ЛИБО рост failed+zombie в `scrape_runs` после деплоя.
|
||||||
|
pool_timeout=5,
|
||||||
)
|
)
|
||||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine, expire_on_commit=False)
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine, expire_on_commit=False)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,3 +49,23 @@ def test_pool_ceiling_covers_declared_concurrency() -> None:
|
||||||
f"({_ESTIMATE_CONCURRENCY} оценок + {_SUGGEST_CONCURRENCY} подсказок + "
|
f"({_ESTIMATE_CONCURRENCY} оценок + {_SUGGEST_CONCURRENCY} подсказок + "
|
||||||
f"{_MAX_DEFERRED_REFRESH_TASKS} фоновых догрузок) — штатная работа исчерпает пул"
|
f"{_MAX_DEFERRED_REFRESH_TASKS} фоновых догрузок) — штатная работа исчерпает пул"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pool_checkout_wait_shorter_than_source_budget() -> None:
|
||||||
|
"""Ожидание коннекта короче бюджета внешнего источника.
|
||||||
|
|
||||||
|
Иначе занятый пул съедает весь бюджет запроса (и поток `asyncio.to_thread`,
|
||||||
|
которых тоже конечное число) вместо того, чтобы деградировать один источник.
|
||||||
|
Сравниваем с самым КОРОТКИМ бюджетом (8 с Yandex/Cian): geocode 12 с и IMV
|
||||||
|
20 с длиннее, их этот же потолок покрывает с запасом.
|
||||||
|
"""
|
||||||
|
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}с"
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue