fix(ptica): соединение БД отпускается до похода за фотографией наружу (#2464-C) #2928
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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#2928
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/2464c-photos-session-hold"
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?
Последний пункт кластера C. В отличие от #2927 это другой дефект — не про executor,
поэтому отдельным PR.
Что происходит
GET /api/v1/photos/{obj}/{file}берёт сессию черезDepends(get_db), делаетSELECT—и держит соединение пула всё время синхронного фетча к ДОМ.РФ. SQLAlchemy открывает
транзакцию на первом запросе, поэтому она ещё и висит
idle-in-transaction.Насколько это горит — замер, а не рассуждение
domrf_kn_photos, 19.08.2026:То есть почти каждый запрос картинки — удержание соединения на время внешнего похода
(
_UPSTREAM_TIMEOUT= 8 с, connect 4 с).Пул при этом дефолтный:
create_engine(settings.database_url, pool_pre_ping=True)вapp/core/db.py— безpool_size, значит 5 + 10 overflow = 15 соединений на весьбэкенд. Страница отчёта тянет картинки пачкой; пятнадцать таких запросов занимают пул
целиком, и за ними встают все остальные ручки, включая
/analyze.Правка
db.close()сразу после того, как значения строки разложены по локальным переменным — дофетча и до генерации миниатюры.
close()не делает сессию непригодной: следующийdb.executeпрозрачно берёт новое соединение и открывает свою транзакцию.Одна строка, но место выбрано не наугад: до неё — только
SELECTи распаковка, после — всямедленная работа.
Тест проверяет свойство, а не текст
Проверяется факт отпускания —
in_transaction()в момент фетча, — а не наличиеdb.close()в исходнике. Иначе тест фиксировал бы реализацию: любая другая корректнаяправка (отдельная сессия под запись, вынос фетча наружу) сделала бы его красным без причины.
Сессия в тесте настоящая (SQLite in-memory), не
MagicMock: на мокеin_transactionбыл бы выдумкой, а проверяется именно поведение сессии.
Наружу тест не ходит —
_fetch_upstreamподменён шпионом. ДОМ.РФ трогать нельзя, тамжёсткий WAF-бан.
Против кода из main:
pytest tests/test_2464c_photos_session_release.py— 3 passedpytest tests/api/v1— зелёный (rc=0)ruff check— cleanЧто этот PR не делает
Не трогает размер пула. 15 соединений на весь бэкенд — отдельный разговор: правильное
число зависит от
max_connectionsPostgres и от того, сколько процессов ходит в ту жебазу (backend, worker, beat). Менять его наугад значит переносить дефицит в другое место.
Здесь устранена причина, по которой пул выедался запросами картинок.
Не чинит 98.9% промахов кеша. Это отдельная задача — прогрев или фоновая загрузка;
сейчас каждый первый показ картинки идёт наружу, и это по-прежнему так.
Refs #2464
GET /api/v1/photos/{obj}/{file} берёт сессию через Depends(get_db), делает SELECT — и держит соединение пула всё время синхронного фетча к ДОМ.РФ. SQLAlchemy открывает транзакцию на первом запросе, поэтому она ещё и висит idle-in-transaction. ЗАМЕР 19.08 по domrf_kn_photos: всего фотографий 165 208 закешировано локально 1 889 (1.1%) пойдут ленивым путём 163 319 (98.9%) То есть почти каждый запрос картинки — удержание соединения на время внешнего похода (_UPSTREAM_TIMEOUT 8 с, connect 4 с). Пул при этом дефолтный: create_engine в app/core/db.py без pool_size, значит 5 + 10 overflow = 15 соединений на весь бэкенд. Страница отчёта тянет картинки пачкой — пятнадцать таких запросов занимают пул целиком, и за ними встают ВСЕ остальные ручки. Правка: db.close() сразу после того, как значения строки разложены по локальным переменным, — до фетча и до генерации миниатюры. close() не делает сессию непригодной: следующий db.execute прозрачно берёт новое соединение. Тест проверяет ФАКТ отпускания (in_transaction() в момент фетча), а не наличие db.close() в тексте — иначе он фиксировал бы реализацию, а не свойство. Сессия в тесте настоящая (SQLite), не мок: на моке in_transaction был бы выдумкой. Наружу тест не ходит — _fetch_upstream подменён, ДОМ.РФ трогать нельзя. Двусторонний: против main падает ровно проверка отпускания; два контроля — «закешированная миниатюра всё ещё отдаётся с диска» и «незарегистрированная фотография всё ещё 404» — зелёные с обеих сторон. Хунк форматирования — не мой: pre-commit ruff v0.7.4 против 0.15.12 (#2864). Refs #2464