No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#3444
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/3083-estimate-throughput"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Часть #3083 и #3408. Заголовок #3083 («тяжёлый /estimate внутри event loop блокирует всё») на текущем коде НЕ воспроизводится — тяжёлая часть уже унесена в
asyncio.to_thread(~20 мест), параллелизм ограничен семафором 4. Замер это показывает прямо, и вместо гипотезы найден другой, измеримый дефект.Замер ДО (прод, 11.09, изнутри хоста, мимо Caddy; md5 файлов в контейнере сверен с origin/main — мерил тот код, что правлю):
pg_stat_statements)/healthEvent loop не голодает: при восьми одновременных оценках лёгкий запрос отвечает за 3 мс на p95, максимум удержания — 160 мс. Хвост 8.9 с в пробе N=4 — не блокировка, а честное асинхронное ожидание внешнего тира (
house_metadata(overpass) exceeded 8.0s budget — degrading to None (#654)).Настоящий дефект — удержание коннекта БД через чужой HTTP. По логу одной фоновой догрузки:
proxy_pool: leased … provider=yandex19:14:33.856 →yandex_valuation: fresh fetch saved19:14:42.334 = 8.5 с с открытой транзакцией. Таких задач разрешено 8 (_MAX_DEFERRED_REFRESH_TASKS) при пуле 5+10. Замер подтверждает: при N=8 «повтор» 8 из 9 коннектов —idle in transaction.Правка:
app/services/estimator.py:698—_db_step[T]: синхронный шаг БД уходит вasyncio.to_threadи завершает транзакцию (commit, на ошибкеrollback) — коннект возвращается в пул ДО внешнего HTTP. Применён на всех четырёх сайтах из #3408 п.2::771,:851,:887,:1027,:1120.app/core/db.py:30—max_overflow=15(было 10): потолок пула обязан быть не меньше суммы потолков одновременности, которые процесс сам объявляет (4 оценки + 4 подсказки + 8 фоновых = 16 против 15).pool_sizeне тронут.app/core/db.py:37—pool_timeout=5(было 30): ждать коннект дольше бюджета источника (8 с) бессмысленно.trade_in.py:83,estimator.py:944).Замер ПОСЛЕ (деплой недоступен, поэтому на тех же величинах, что мерила прод-проба, переключением кода patch-файлом):
Фальсификация (origin/main): 5 failed по значению —
loop простоял весь SELECT кэша: тиков всего 0,коннект удерживается транзакцией всё время внешнего HTTP,пул 15 меньше суммы потолков одновременности 16,pool_timeout 30.0с ≥ бюджета источника 8.0с. На ветке 5 passed.Прогоны:
5744 passed, 35 skipped(rc=0); ruff чисто.Суммарный потолок коннектов (явно): на процесс 5+15=20;
app/core/db.pyобщий для трёх прод-процессов (tradein-backend1 воркер,tradein-scraper,tradein-tgbot) → худший случай 3×20=60 + exporter ≈ 62 изmax_connections=100(живьём сейчас 10). Если однажды появятся воркеры uvicorn: 20×N, при N=4 это 120 > 100 — то есть многопроцессность требует одновременного решения по пулу, это не независимая ручка.Воркеры uvicorn намеренно НЕ добавлены: замер не показывает ни дефицита CPU (12 ядер, load 1.0), ни голодания loop'а, зато размножение процессов множит два семафора, пять in-process лимитеров, реестр фоновых задач и сам пул.
Приёмка на проде (признак отсутствует в старой версии):
docker exec tradein-backend python -c "from app.core.db import engine as e; print(e.pool.size(), e.pool._max_overflow, e.pool._timeout)"→5 15 5.0(на старом5 10 30.0); повтор пробыburst8— пикidle in transactionв хвосте ≤ числа живых запросов (было 6–8 при N=8), heartbeat не хуже ДО; за сутки нольQueuePool limit ... timed out.Риск отката:
pool_timeout=5действует и на scraper/tgbot (общий модуль) — исчерпанный пул теперь падает через 5 с вместо 30 с. Если батчи скрапера начнут на этом падать — откатывать толькоpool_timeout,max_overflowоставить.Не сделано осознанно: #3408 п.3 (синхронный curl в админ-ручке
cian_price_history.py:140) и п.4 (бюджет на ветке acquire);estimate_via_cian_valuationимеет ту же форму «сессия открыта через фетч», но она общая со скрапером, где коммит в середине менял бы семантику батча — это следующий шаг, а не забытый.