[P1] yandex full-load: _leaf/_degraded передают on_bucket(key, len(seen), …) — int уезжает в save_listings как lots → TypeError, ручной full-load Яндекса ничего не сохраняет #3375

Closed
opened 2026-09-05 20:07:16 +00:00 by bot-backend · 3 comments
Collaborator

Находка ревью PR #3373 (#3368), подтверждена по коду: providers/yandex/serp.py в _leaf/_degraded зовёт on_bucket(bucket_key, len(seen), …) (int), а run_yandex_full_load._on_bucket типизирован lots: list и передаёт значение в save_listings (base.py:388 for lot in lots) → при N>0 TypeError, прогон падает (admin.py:1571). Расхождение живёт с коммита 3be80620; parity-тесты не ловят — подменяют скрейпер и шлют list.

Следствие: yandex full-load не сохраняет ничего — #3362/#3368 чинят чекпоинт над неработающим сейвом. Смягчение: у функции нет job'а в планировщике (#3366), единственный вход — админский POST, т.е. прод-расписание не затронуто; но любой ручной запуск бесполезен.

Лечение

Согласовать контракт on_bucket: либо провайдер передаёт список лотов (как ждёт pipeline и делает save), либо pipeline перестаёт ждать список и берёт лоты из другого канала (как у city-свипов). Проверить, кто ещё зовёт on_bucket с int (cian/avito?). Тест по значению: реальный run_yandex_full_load с фейком провайдера, отдающим ≥1 лот → save_listings получает список и вызывается ровно один раз на бакет; фальсификация — вернуть int → TypeError/падение.

Приёмка

  • ручной full-load Яндекса на проде (узкий payload, 1 комнатность) → counters.lots_saved > 0, статус done;
  • parity-тест с настоящим провайдером-заглушкой, шлющей int, красный до фикса.

Refs #3368, PR #3373, #3366, #3362.

Находка ревью PR #3373 (#3368), подтверждена по коду: `providers/yandex/serp.py` в `_leaf`/`_degraded` зовёт `on_bucket(bucket_key, len(seen), …)` (int), а `run_yandex_full_load._on_bucket` типизирован `lots: list` и передаёт значение в `save_listings` (`base.py:388 for lot in lots`) → при N>0 `TypeError`, прогон падает (`admin.py:1571`). Расхождение живёт с коммита 3be80620; parity-тесты не ловят — подменяют скрейпер и шлют list. Следствие: **yandex full-load не сохраняет ничего** — #3362/#3368 чинят чекпоинт над неработающим сейвом. Смягчение: у функции нет job'а в планировщике (#3366), единственный вход — админский POST, т.е. прод-расписание не затронуто; но любой ручной запуск бесполезен. ## Лечение Согласовать контракт `on_bucket`: либо провайдер передаёт список лотов (как ждёт pipeline и делает save), либо pipeline перестаёт ждать список и берёт лоты из другого канала (как у city-свипов). Проверить, кто ещё зовёт `on_bucket` с int (cian/avito?). Тест по значению: реальный `run_yandex_full_load` с фейком провайдера, отдающим ≥1 лот → `save_listings` получает список и вызывается ровно один раз на бакет; фальсификация — вернуть int → TypeError/падение. ## Приёмка - [ ] ручной full-load Яндекса на проде (узкий payload, 1 комнатность) → `counters.lots_saved > 0`, статус done; - [ ] parity-тест с настоящим провайдером-заглушкой, шлющей int, красный до фикса. Refs #3368, PR #3373, #3366, #3362.
Author
Collaborator

PR #3378 смержен, деплой ea4fb88b на проде (BUILD_SHA совпал в backend и scraper; маркер bucket_lots в providers/yandex/serp.py контейнера — 8 вхождений, в старой сборке 0). Тест после deep-ревью пинит все три точки (_degraded, одностраничный и многостраничный лист) — три мутации len(seen) краснеют TypeError по отдельности.

Приёмка по эффекту — первый yandex_full_load после деплоя: counters.unique_fetched > 0, рост listings по source=yandex за окно прогона, без TypeError в логах. Единственный вход — админский POST (#3366, расписания нет); запускаю ограниченную пробу с coop-cancel после первых бакетов.

PR #3378 смержен, деплой `ea4fb88b` на проде (BUILD_SHA совпал в backend и scraper; маркер `bucket_lots` в `providers/yandex/serp.py` контейнера — 8 вхождений, в старой сборке 0). Тест после deep-ревью пинит все три точки (`_degraded`, одностраничный и многостраничный лист) — три мутации `len(seen)` краснеют `TypeError` по отдельности. Приёмка по эффекту — первый `yandex_full_load` после деплоя: `counters.unique_fetched > 0`, рост `listings` по source=yandex за окно прогона, без `TypeError` в логах. Единственный вход — админский POST (#3366, расписания нет); запускаю ограниченную пробу с coop-cancel после первых бакетов.
Author
Collaborator

Проба по эффекту (run 6153, 22:00 UTC) заблокирована инфраструктурой, не кодом: 18 бакетов partial, unique_fetched=0, TypeError в логах — 0. Причина — сайдкар для yandex поднимался на SCRAPER_PROXY_URL (выключенный узел 9, 407) → InvalidIP/fetch 503 → проба гейта падает → _degraded → пустые страницы. У tradein-backend нет USE_PROXY_POOL_* (есть только у scraper) → аренды из пула нет. Разбор и фикс — #3386. Прогон снят coop-cancel через 3 минуты. Приёмка #3375 переносится на первый full-load после #3386.

Проба по эффекту (run 6153, 22:00 UTC) **заблокирована инфраструктурой, не кодом**: 18 бакетов `partial`, `unique_fetched=0`, `TypeError` в логах — 0. Причина — сайдкар для yandex поднимался на `SCRAPER_PROXY_URL` (выключенный узел 9, 407) → `InvalidIP` → `/fetch 503` → проба гейта падает → `_degraded` → пустые страницы. У `tradein-backend` нет `USE_PROXY_POOL_*` (есть только у scraper) → аренды из пула нет. Разбор и фикс — #3386. Прогон снят coop-cancel через 3 минуты. Приёмка #3375 переносится на первый full-load после #3386.
Author
Collaborator

Приёмка по эффекту выполнена (06.09 ~22:50 UTC, после #3386): run 6157 yandex_full_load — за первые 2 закрытых бакета counters.unique_fetched=250, saved_inserted=39, saved_updated=200, price_history_rows=76; 250 yandex-объявлений получили last_seen_at в окне прогона; TypeError в логах backend — 0. До фикса тот же путь падал на первом непустом бакете (object of type 'int' has no len()) и не сохранял ничего. Прогон снят coop-cancel (полный обход — часы; расписания у full-load нет, #3366).

**Приёмка по эффекту выполнена (06.09 ~22:50 UTC, после #3386):** run **6157** `yandex_full_load` — за первые 2 закрытых бакета `counters.unique_fetched=250`, `saved_inserted=39`, `saved_updated=200`, `price_history_rows=76`; 250 yandex-объявлений получили `last_seen_at` в окне прогона; `TypeError` в логах backend — 0. До фикса тот же путь падал на первом непустом бакете (`object of type 'int' has no len()`) и не сохранял ничего. Прогон снят coop-cancel (полный обход — часы; расписания у full-load нет, #3366).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3375
No description provided.