Авито: добор карточек больше не сжигает 1600 попыток на упавшем сайдкаре и везде ходит браузером #3560
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#3560
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/avito-detail-backfill"
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?
Четыре коммита, по одному на задачу. Главное: добор карточек Авито перестаёт впустую тратить 1600 попыток, когда отказывает наш сайдкар. Кроме того, добор теперь ходит браузером во всех процессах, которые его запускают. Миграций нет.
#3439: серия отказов нашего тракта не обрывала добор
Что было. Прогоны 7193, 7218, 7264 и 7302 (15–16.09), а также 6608, 6631, 6643 и 6662 (10.09): в каждом 1600 попыток, обогащено от 1 до 10 карточек, около 1599 блоков, статус
done, длительность 70+ минут. В окне 7302 (16.09 22:45–00:00 UTC) в логахtradein-browser3258 строкInvalidIPи 6380 строк с503(провереноdocker logs --since/--until). У прогона 6885 (13.09) те же 1600 попыток приban_kinds {"infra": 1591}, но статусbanned. Всего за 14 суток 17 таких прогонов дали 25 668 попыток из 29 498 (87 %) и 55 карточек. Остальные 64 прогона за 3830 попыток обогатили 746 карточек.Почему. Это регресс #3288/#3367.
record_block(kind != platform)уходил вrecord_failure(), то есть только в знаменатель окна. Доля считает лишь platform, safety-net смотрит на серию platform, а счётчика серии infra не было вовсе. Серия отказов сайдкара любой длины ничем не обрывала прогон, а финализатор #3367 при одной обогащённой карточке ставилdone.Что сделано.
BlockRatioBreakerдобавленыinfra_streak_limitи счётчик серии infra. Серию обнуляет любой ответ площадки: успех, её блок или 404.abort_reason()возвращает"infra_streak"и проверяет этот критерий первым.avito_detail_backfillпорог равенmax_consecutive_failures(по умолчанию 25). Это тот же разряд «отказ нашей стороны», что и отказы-не-блоки. При обрыве вызываетсяmark_failedс причиной «тракт недоступен: N отказов нашего тракта (сайдкар/пул) подряд без единого ответа площадки…», в counters пишутсяabort_reason=infra_streakиban_kinds. Статус неbanned, потому что площадка молчала. Финал идёт мимоmark_backfill_finished, иначе одна карточка снова превращает шторм вdone.Не сделано. Пункт 3 задачи не сделан: вердикт
browser_unfitузлу, на котором сайдкар не поднял браузер. Это вердикт пула прокси и протокол сайдкара, нужна отдельная задача. На 17.09 у узла 15browser_fail_streak=0иbrowser_unfit_since=NULL. ПоэтомуClosesдля #3439 не ставлю.#3288: диагноз блоков был только у
banned-прогоновЧто было. Пункт 5 из комментария от 05.09.
mark_backfill_finishedкладётcounters.ban_kindsтолько на ветке бана. У 7193, 7264 и 7302 (done) диагноза в БД нет. Что это были отказы сайдкара, видно лишь косвенно:block_streak_histogramсчитает только серии platform.Что сделано. Задача сама кладёт
counters.ban_kindsперед любым финализатором. Статусы и колонкаban_kindне тронуты, контракты #3196 и #2764 остались как были. Стоп-кран для серии infra, вторая половина пункта 5, сделан в коммите #3439.Что осталось открытым, поэтому без
Closes:banned;scheduler.py).Пункт 5 сам сформулирован как «решение, не код».
#3045: ручной добор через админку шёл curl'ом
Что было. Шаг 3 сделали только через
environmentсервиса scraper, а вconfig.pyосталосьdefault=True. При этом добор исполняет иtradein-backend: ручкаPOST /scrape/avito-detail-backfillвызываетrun_avito_detail_backfillв своём процессе. Комментарий «true только у tradein-backend, который добор не запускает» был неверен.Что сделано. Дефолт изменён на
False, исправлены три комментария, называвшие curl «прод-дефолтом». Для прода нужен ещё оверрайд в compose: хостовый.env.runtime:37задаётtrueявно. Он в коммите #3182.Шаг 4 (нагрузка), замер по
scrape_runsза 14 суток. Рабочие прогоны браузером: в среднем 60 попыток за 15 минут, 236 попыток в час, 64 прогона. Узлы:scrape_runs.proxy_id= 13, 14 и 15. Все три — mobileproxy (Tele2 и МегаФон). ASOCKS-узел (id=1) привязан к yandex, avito-добор через него не ходил ни разу. Упереться в лимиты ASOCKS этот путь не может. Основной расход давали шторма сайдкара (87 % попыток), их обрывает #3439.#3182: разные значения флага в контейнерах
Что было.
printenvна проде 17.09:AVITO_DETAIL_BACKFILL_USE_CURLtradein-scraperfalsetradein-backendtruetradein-tgbottruetradein-browsertruetrueприходит из хостовогоbackend/.env.runtime:37.YANDEX_COOKIES_FILE=/app/yandex_cookies.jsonзадан в.env.runtime:17, но файла нет ни в одном контейнере, а полеsettings.yandex_cookies_fileне читал ни один модуль: был только мок в тесте, куки Яндекса давно хранятся в БД (#3192).Что сделано.
docker-compose.prod.ymlуbackendиtgbotзаданоAVITO_DETAIL_BACKFILL_USE_CURL: "false": у всех сервисов образа backend одно значение. tgbot добор не исполняет, но так правило проверяется без списка исключений.SCRAPER_FETCH_MODE=browserиAVITO_IMV_USE_BROWSER_FETCHER=true.yandex_cookies_fileудалено. Настройкаextra="ignore"не даёт процессу упасть на старте из-за переменной, которая осталась в.env.runtime.Тесты
Все прогоны локально (macOS),
rcснят с самого pytest.tests/test_3288b_block_kind_ratio.py: 20 passed. Из них новые:attempted=26,mark_failed,abort_reason=infra_streak,ban_kinds {"infra": 25};ban_kindsв counters.Прежний контроль #3367 («20 infra + 10 успехов → done») зелёный.
tests/test_3045_use_curl_default.py: 2 passed (Settingsбез env даётFalse; env по-прежнему переключает на curl).tests/test_3182_detail_transport_one_value.py: 2 passed (разобранный compose: у каждого сервиса образа backend"false";SettingsсYANDEX_COOKIES_FILEв env стартует без поля).Соседние тесты avito-добора (
tasks/test_avito_detail_backfill,test_3338,test_3384,test_3283g,test_3251,test_backfill_honest_status,test_2764,tasks/test_3283): 98 passed, 1 skipped.Весь сьют:
DATABASE_URL=… uv run python -m pytest tests/ -q -p no:cacheprovider— 6237 passed, 42 skipped, rc=0 (109 с).uv run ruff check app tests: rc=0.ruff format --checkпо 8 изменённым файлам: rc=0.packages/scraper-kit: тестов нет (pytest rc=5, no tests ran).Фальсификация (красный прогон при сломанном фиксе, исходник восстановлен,
diff -qчист)AssertionError: attempted=1600 vs исходы=1600 … ожидали 26/assert (1600, 1600) == (26, 26).AssertionError: abort_reason='infra_streak': 24 + ответ площадки (platform) + 24 — серии в 25 подряд не было.AssertionError: прогон ушёл в backfill-honest-status: там 1 карточка превращает шторм в 'done'.ban_kinds:assert None == {'infra': 20}.default=True:AssertionError: assert True is False.{'backend': None} != {'backend': 'false'}; с возвращённым полем:AssertionError: assert not True.Деплой
tradein-backend,tradein-scraper(через drain) иtradein-tgbot.tradein-browserне меняется. Перед мержем нужно проверить, что сейчас ничего не бежит:SELECT id, source, started_at FROM scrape_runs WHERE status='running'. Пересоздание scraper убивает работающие прогоны.YANDEX_COOKIES_FILE) и 37 (AVITO_DETAIL_BACKFILL_USE_CURL=true) в/opt/gendesign/tradein-mvp/backend/.env.runtimeтеперь просто шум. После их удаленияprintenvвtradein-browserперестанет показыватьtrue.Приёмка на проде (после деплоя; сроки от даты деплоя)
docker exec tradein-backend printenv AVITO_DETAIL_BACKFILL_USE_CURLи то же дляtradein-tgbotдаютfalse.docker exec tradein-backend python -c 'from app.core.config import settings; print(settings.avito_detail_backfill_use_curl, hasattr(settings, "yandex_cookies_file"))'печатаетFalse False.InvalidIP//fetch 503в логахtradein-browser): Ожидание: ни одного прогона сattempted≥ 500 иenriched/attempted< 1 %. У штормового прогонаstatus='failed',abort_reason='infra_streak',attemptedоколо 25, вban_kindsдоминируетinfra. Если шторма за 7 суток не было, приёмка не пройдена и не провалена: переносится на следующее окно с датой.done) естьcounters->'ban_kinds'.Не вошло в PR
?context=вsource_url). Автор в комментарии от 23.08 прямо просит не править вслепую: знак эффекта неизвестен, и протухшийsearchHashможет выглядеть подозрительнее, чем его отсутствие. Первый пункт приёмки — A/B-замер на проде (переключение режима и прогоны на одном узле). Это запись на прод и решение владельца. Судьбаlistings.phones— тоже решение.scrape_proxy_rotations.noteили отдельная колонка). Вызов ротации уже проведён в #3302/#3313.Closes #3045
Closes #3182
🤖 Generated with Claude Code
Пункт 5 из комментария 05.09: ban_kind пишется только у banned-прогонов, у done/failed диагноза в БД нет. Прод: 7193, 7218, 7264, 7302 — по 1600 попыток, ~1599 блоков, status='done', counters без ban_kinds; что это были отказы сайдкара, а не площадки, видно лишь по косвенному признаку (мелкая block_streak_histogram считает только platform-серии). mark_backfill_finished кладёт перепись в counters только на ветке бана. Задача теперь кладёт counters.ban_kinds сама перед любым финализатором (mark_backfill_finished / mark_failed пустого пула). Статусы и колонка ban_kind не тронуты — контракты #3196/#2764 как были. Тест по значению: 20 отказов сайдкара + 10 успехов — counters, уходящие в финализатор, несут {"infra": 20}. Фальсификация: без строки — «assert None == {'infra': 20}». Стоп-кран для infra-серии (вторая половина п.5) — отдельным коммитом #3439. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>Прод: 7193, 7218, 7264, 7302 (15-16.09) и 6608/6631/6643/6662 (10.09) — по 1600 попыток, 1-10 карточек, ~1599 блоков, status='done', по 70+ минут каждый. В те же окна в логах tradein-browser шторм «browser launch failed … InvalidIP» и «/fetch 503». 6885 (13.09) — те же 1600 попыток при ban_kinds {"infra": 1591}, но 'banned'. Корень — регресс #3288/#3367: record_block(kind != platform) уходит в record_failure(), то есть только в знаменатель окна. Числитель доли растёт лишь на platform, safety-net смотрит на platform-серию, счётчика серии infra нет вовсе. Серия отказов сайдкара любой длины не рвала прогон ничем, а финализатор #3367 при одной карточке ставил 'done'. - BlockRatioBreaker: infra_streak_limit и счётчик серии infra. Серию гасит любой ответ площадки — успех, её блок, 404. abort_reason() -> "infra_streak" (проверяется первым), abort_explanation() называет эту величину. - avito_detail_backfill: порог = max_consecutive_failures (дефолт 25) — тот же разряд «отказ нашей стороны», что и отказы-не-блоки. На обрыве — mark_failed «тракт недоступен: N отказов … подряд без единого ответа площадки», в counters abort_reason=infra_streak и ban_kinds. Не 'banned' — площадка молчала; не через mark_backfill_finished — там одна карточка даёт 'done'. Тесты по значению: 1 успех + 1599 отказов сайдкара -> attempted=26, mark_failed, abort_reason=infra_streak, ban_kinds {"infra": 25}; обрыв ровно на 25-м (24 — нет); 24 infra + ответ площадки (успех/блок/404) + 24 — не обрыв; 24 infra + ответ, трижды — снапшот пройден; 25% настоящих блоков — не обрыв; прежний контроль #3367 (20 infra + 10 успехов -> done) зелёный. Фальсификация: без проводки порога — «assert (1600, 1600) == (26, 26)»; без сброса серии на блоке площадки — «abort_reason='infra_streak': 24 + ответ площадки (platform) + 24»; без ветки финала — «прогон ушёл в backfill-honest-status: там 1 карточка превращает шторм в 'done'». Пункт 3 задачи (browser_unfit узлу, на котором сайдкар не поднял браузер) не сделан: это вердикт пула прокси и протокол сайдкара, отдельная задача. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>