fix(scraper-kit): резюм exhaustive не пере-пробивает готовые полосы — containment по done-леджеру до пробы, 17 911 запросов → 0 #3358

Merged
bot-backend merged 1 commit from fix/3315-exhaustive-containment-skip into main 2026-09-05 18:17:02 +00:00
Collaborator

Closes #3315. Резюм avito_full_load_exhaustive честно зачитывал done-корзины, но бисекция пере-пробивала поддиапазоны ВНУТРИ готовых полос живыми запросами (skip проверялся в листе, после probe) — на пуле из 1-2 нод бан-бюджет сгорал на готовой территории, прогресс за три прогона — ноль.

Где встал гейт

Не в avito-ветках, а на входе в узел рекурсии общего движка: walk_price_range (pricing/bisection.py) получил should_skip: SkipFn | None, проверяемый первой строкой ДО probe и проброшенный во все 4 рекурсивных вызова — покрытое поддерево обрезается на любой глубине. Avito (serp.py) строит предикат один раз на комнатность. Другие провайдеры не тронуты (None = прежнее поведение); лист-скип по точному ключу оставлен для инкрементального пути.

Покрытие (done_range_skipper)

Ключ label:lo:hi (включительно; hi=open → +inf); только ключи своего label, битые пропускаются (не покрывают, обход не роняют); отрезки сливаются при lo <= prev_hi + 1 — пересекающиеся И смежные (половины бисекции лежат встык, без «+1» шов пробивался бы заново); True, если один слитый отрезок содержит [q_lo, q_hi]. Пустой/чужой леджер → гейт не ставится.

Тесты

test_3315_avito_exhaustive_containment_skip.py: 48 passed (-k avito serp/exhaustive/checkpoint/bisect) + 92 passed (-k bisect/pricing/parity — golden-parity cian/yandex цела). Фальсификация (откат исходников): полоса [4062500, 4124999] внутри зачтённой [4000000, 4999999], а обход сходил в сеть 3 раз(а), [4.5М, 5.5М] в объединении двух смежных корзин — 63 раз(а), резюм готовой комнатности сделал 17911 запрос(ов) вместо нуля — все по значению счётчика fetch.

Честные оговорки

  • cian/yandex на should_skip не переведены — тот же дефект вероятен, отдельный issue;
  • если прошлый прогон записал корзину done, собрав её частично, containment это не переоткроет — как и старый лист-скип;
  • источник всё равно не завершится, пока пул не пополнен (#2638) — правка делает так, чтобы каждая попытка тратила бан-бюджет на НОВУЮ работу.

Прод-приёмка

Следующий резюм-прогон exhaustive: в логе нет total=None for room_studii [4062500…]-проб внутри done-полос; число запросов до первой новой корзины — единицы, не десятки.

Closes #3315. Резюм `avito_full_load_exhaustive` честно зачитывал done-корзины, но бисекция пере-пробивала поддиапазоны ВНУТРИ готовых полос живыми запросами (skip проверялся в листе, после probe) — на пуле из 1-2 нод бан-бюджет сгорал на готовой территории, прогресс за три прогона — ноль. ## Где встал гейт Не в avito-ветках, а на входе в узел рекурсии общего движка: `walk_price_range` (`pricing/bisection.py`) получил `should_skip: SkipFn | None`, проверяемый первой строкой ДО `probe` и проброшенный во все 4 рекурсивных вызова — покрытое поддерево обрезается на любой глубине. Avito (`serp.py`) строит предикат один раз на комнатность. Другие провайдеры не тронуты (`None` = прежнее поведение); лист-скип по точному ключу оставлен для инкрементального пути. ## Покрытие (`done_range_skipper`) Ключ `label:lo:hi` (включительно; `hi=open` → +inf); только ключи своего label, битые пропускаются (не покрывают, обход не роняют); отрезки сливаются при `lo <= prev_hi + 1` — пересекающиеся И смежные (половины бисекции лежат встык, без «+1» шов пробивался бы заново); True, если один слитый отрезок содержит `[q_lo, q_hi]`. Пустой/чужой леджер → гейт не ставится. ## Тесты `test_3315_avito_exhaustive_containment_skip.py`: `48 passed` (-k avito serp/exhaustive/checkpoint/bisect) + `92 passed` (-k bisect/pricing/parity — golden-parity cian/yandex цела). Фальсификация (откат исходников): `полоса [4062500, 4124999] внутри зачтённой [4000000, 4999999], а обход сходил в сеть 3 раз(а)`, `[4.5М, 5.5М] в объединении двух смежных корзин — 63 раз(а)`, **`резюм готовой комнатности сделал 17911 запрос(ов) вместо нуля`** — все по значению счётчика fetch. ## Честные оговорки - cian/yandex на `should_skip` не переведены — тот же дефект вероятен, отдельный issue; - если прошлый прогон записал корзину done, собрав её частично, containment это не переоткроет — как и старый лист-скип; - источник всё равно не завершится, пока пул не пополнен (#2638) — правка делает так, чтобы каждая попытка тратила бан-бюджет на НОВУЮ работу. ## Прод-приёмка Следующий резюм-прогон exhaustive: в логе нет `total=None for room_studii [4062500…]`-проб внутри done-полос; число запросов до первой новой корзины — единицы, не десятки.
bot-backend added 1 commit 2026-09-05 18:07:44 +00:00
fix(avito): skip fully-covered price ranges before probing on resume
All checks were successful
CI Trade-In / changes (pull_request) Successful in 15s
CI / changes (pull_request) Successful in 18s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m48s
c048e8f2c9
Резюм exhaustive-обхода пере-пробивал уже зачтённую территорию: skip
проверялся в листе, ПОСЛЕ probe, поэтому дерево бисекции спускалось в
поддиапазоны done-корзин живыми запросами (прогон 5718: 21 минута внутри
room_studii:4000000:4999999, ноль новых корзин). На пуле из 1-2 нод это
сжигает весь бан-бюджет до первой НОВОЙ работы.

Ключи чекпоинта — границы ДИНАМИЧЕСКОЙ бисекции: при сдвиге рынка новый
лист ключом не равен старому даже внутри покрытого диапазона, поэтому
сравнение строк бесполезно. done_range_skipper парсит ключи room:lo:hi
(hi=open → бесконечность) в отрезки, сливает пересекающиеся и смежные и
отдаёт предикат покрытия; walk_price_range проверяет его на входе в узел,
ДО probe, и обрезает готовые поддеревья без единого запроса.

Closes #3315
bot-backend merged commit 25d3db2ae7 into main 2026-09-05 18:17:02 +00:00
Author
Collaborator

Deep-ревью (после мержа, для записи): — property-прогон 100k случайных леджеров против оракула «множество покрытых точек»: 0 ложных skip; label:0:open не покрывает несобранное, потому что ключ попадает в леджер только при complete=True (serp.py:1628).

Несущая предпосылка, которую стоит держать в голове (не блокер): run_avito_full_load обслуживает оба режима одним леджером done_buckets; инкрементальные ключи — целые seed-брекеты, их объединение = [0, ∞) на комнатность. Exhaustive-резюм с такого леджера пропустил бы комнатность целиком. Сегодня это блокирует _pick_resume (scheduler.py:588-604, 672-689: кандидат обязан иметь params IS NOT DISTINCT FROM текущих). Containment усиливает цену будущего послабления этого гейта с «перескрапим лишнего» до «молча пропустим комнатность» — при правке _pick_resume перечитать done_range_skipper.

Побочно замечено (пред-существующее): комментарий serp.py:1266 «pmax эксклюзивный сверху» расходится с арифметикой (везде hi включительно) — если pmax правда эксклюзивен, точка mid терялась и до этого PR.

Follow-up на cian/yandex — #3359.

Deep-ревью (после мержа, для записи): ✅ — property-прогон 100k случайных леджеров против оракула «множество покрытых точек»: 0 ложных skip; `label:0:open` не покрывает несобранное, потому что ключ попадает в леджер только при `complete=True` (`serp.py:1628`). **Несущая предпосылка, которую стоит держать в голове (не блокер):** `run_avito_full_load` обслуживает оба режима одним леджером `done_buckets`; инкрементальные ключи — целые seed-брекеты, их объединение = `[0, ∞)` на комнатность. Exhaustive-резюм с такого леджера пропустил бы комнатность целиком. Сегодня это блокирует `_pick_resume` (`scheduler.py:588-604, 672-689`: кандидат обязан иметь `params IS NOT DISTINCT FROM` текущих). Containment усиливает цену будущего послабления этого гейта с «перескрапим лишнего» до «молча пропустим комнатность» — при правке `_pick_resume` перечитать `done_range_skipper`. Побочно замечено (пред-существующее): комментарий `serp.py:1266` «pmax эксклюзивный сверху» расходится с арифметикой (везде hi включительно) — если pmax правда эксклюзивен, точка `mid` терялась и до этого PR. Follow-up на cian/yandex — #3359.
Sign in to join this conversation.
No reviewers
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#3358
No description provided.