feat(tradein/estimate): внешние оценки не ждутся в запросе — 9 секунд превращаются в 1 #3055
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#3055
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/estimate-external-sources-background"
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?
Подготовка к публикации в РБК 30.08.
Замер, с которого всё началось
Расчёт по новому адресу — 8-12.4 с. По уже виденному (кэш 24 ч) — 0.4-0.8 с. Разбивка одного запроса по логам прода:
Семь с половиной секунд из восьми с половиной — ожидание чужих HTTP. Наша база и сам расчёт укладываются в секунду. Геокодинг ни при чём: с готовыми координатами те же 7.5-10.8 с.
Публикация приведёт аудиторию на новые адреса, то есть мимо суточного кэша. Масштабирование контейнеров тут не помогает — время уходит на ожидание чужого ответа, а не на наши вычисления.
Статья, к слову, о том, как медленная оценка срывает сделки.
Что сделано
У обоих источников появился режим «только кэш» (
fetch_on_miss). ПриESTIMATE_EXTERNAL_SOURCES_BACKGROUND=trueзапрос делает лишь чтение кэша — локальный запрос на миллисекунды, — а свежая загрузка уходит в фон и наполняет кэш к следующему обращению по тому же адресу.Почему это безопасно. Деградация источника в
None— не новое состояние ответа: ровно так же ведёт себя таймаутestimate_*_valuation_timeout_s, и этот путь работает в проде сегодня. Контракт API не меняется, фронт уже умеет отсутствие этих полей.Две ловушки, найденные при реализации
Наивный
asyncio.gatherдвух источников небезопасен. Обе функции принимают один и тот жеdbи делают внутриdb.commit()— коммит одной зафиксировал бы незавершённую работу другой. По этой же причине фоновая задача открывает свою сессию: сессия запроса закрывается вместе с ответом.Неограниченный
create_taskпревратил бы ускорение в отказ. Всплеск по новым адресам — ровно тот случай, ради которого режим и сделан — породил бы сотни параллельных задач с сессиями и HTTP-клиентами приmax_connections=100иmem_limit: 768mу backend. Очередь ограничена восемью; переполнение не ошибка, следующий запрос попробует снова.Чего намеренно НЕ сделано
Снижение таймаутов до 4 с — предлагалось, отменено замером. Свежий запрос к Яндексу занимает 6 с; таймаут 4 обрывал бы его почти всегда, кэш бы не наполнялся, и источник оказался бы тихо отключён. Таймаут здесь страховка от патологии, а не регулятор задержки.
Параллельный запуск двух источников — даёт полторы секунды из восьми с половиной (общее время стало бы максимумом из 6 и 1.5). Имеет смысл как отдельная правка ради худшего случая (16 с → 8 с), но не как способ ускорить обычный запрос.
Проверка
Семь тестов: режим «только кэш» не ходит в сеть, дефолт по-прежнему ходит, фоновая задача берёт свою сессию и закрывает её, гасит ошибки, не падает без event loop, удерживает ссылку на задачу, очередь ограничена.
Фальсифицированы: на неизменённом коде падают 5 из 7 — проходит только сторож неизменности дефолта, что и правильно.
Смежные тесты оценщика зелёные: 29 штук (бюджет ЦИАН, интеграция ЦИАН, клиентские координаты, аудит).
Включение
Дефолт в коде
False— другие окружения не меняются. На проде включено черезdocker-compose.prod.ymlу сервисаbackend(этоtradein-backend, uvicorn с эндпоинтом оценки).После мержа
Замерить на проде тот же новый адрес и убедиться, что ответ ~1 с, а в логах появилась отложенная догрузка. Затем нагрузочный прогон по нашему пути с прогретым кэшем — бить чужие API пачкой с боевого IP не нужно.
Замер на проде 2026-08-22, разбивка одного расчёта по логам: 10.659 старт 10.827 дом найден 0.17 с 11.565 аналоги, 49 кандидатов 0.74 с 11.579 ДКП-коридор 0.01 с 17.576 yandex_valuation ← 6.0 с 19.159 cian_valuation ← 1.5 с 19.252 готово итого 8.6 с Семь с половиной секунд из восьми с половиной — ожидание чужих HTTP. Наша база и сам расчёт укладываются в секунду. По уже виденному адресу (кэш 24 ч) — 0.4-0.8 с, по новому — 8-12.4 с. Геокодинг ни при чём: с готовыми координатами те же 7.5-10.8. Публикация в РБК 30.08 приведёт аудиторию на НОВЫЕ адреса, то есть мимо кэша. Масштабирование контейнеров тут не помогает: время уходит на ожидание чужого ответа, а не на наши вычисления. Что сделано: у обоих источников появился режим «только кэш» (fetch_on_miss). При включённом ESTIMATE_EXTERNAL_SOURCES_BACKGROUND запрос делает лишь чтение кэша (локальный запрос на миллисекунды), а свежая загрузка уходит в фон и наполняет кэш к следующему обращению по тому же адресу. Почему это безопасно: деградация источника в None — НЕ новое состояние ответа. Ровно так же ведёт себя таймаут estimate_*_valuation_timeout_s, и этот путь работает в проде сегодня. Контракт API не меняется. Фоновая задача берёт СВОЮ сессию: сессия запроса закрывается вместе с ответом, а обе функции источников делают внутри себя db.commit() — переиспользование чужой сессии зафиксировало бы её незавершённую работу. По той же причине отвергнут наивный asyncio.gather двух источников на одной сессии. Очередь догрузки ограничена восемью задачами. Без потолка всплеск по новым адресам — ровно тот случай, ради которого режим и сделан — породил бы сотни параллельных задач с сессиями и HTTP-клиентами при max_connections 100 и mem_limit 768m у backend, то есть отказ вместо ускорения. Дефолт в коде False: поведение других окружений не меняется. На проде режим включён через docker-compose.prod.yml у сервиса backend. Отдельно НЕ сделано, хотя предлагалось: снижение таймаутов до 4 с. Замер показал, что свежий запрос к Яндексу занимает 6 с — таймаут 4 обрывал бы его почти всегда, кэш бы не наполнялся, и источник оказался бы тихо отключён. Таймаут здесь страховка от патологии, а не регулятор задержки. Тесты: 7 штук на режим «только кэш», собственную сессию, гашение ошибок, удержание ссылки на задачу и потолок очереди. Фальсифицированы — на неизменённом коде падают 5 из 7 (проходит только сторож неизменности дефолта). Смежные тесты оценщика (29 штук: бюджет ЦИАН, клиентские координаты, аудит) зелёные.