tradein: деактивация по TTL не знает про баны и покрытие — паушальный TTL уже выкосил 9033 живых строки, у Циана первичка не подметается вовсе #2659

Open
opened 2026-08-05 15:17:57 +00:00 by bot-backend · 3 comments
Collaborator

Найдено при разборе пункта 3 #2574. Политика деактивации сегодня непоследовательна, и обе крайности уже стрельнули.

Что живёт на проде

Четыре расписания деактивации: avito (TTL=10 дней, все сегменты, по last_seen_at), cian и yandex (TTL=30, только listing_segment='vtorichka'), domklik (TTL=14, по scraped_at). Для n1 расписание удалено миграцией 165.

Крайность первая: паушальный TTL без учёта банов

TTL=10 у Авито уже один раз выкосил 9 033 строки за 17 дней сплошного бана — и минимум 1 336 из них доказанно вернулись живыми, когда сбор восстановился. То есть задача деактивации приняла «мы не смогли зайти» за «объявление снято».

TTL-джобам нужен гейт: если по источнику идёт полоса банов или покрытие развёртки просело, деактивацию надо приостанавливать, а не исполнять вслепую.

Крайность вторая: не подметаем вовсе

Аргумент в докстринге deactivate_stale_avito.py:6-10 (и миграция 115) верен: городские свипы Циана и Яндекса не держат полного покрытия, они сэмплируют скользящее окно, поэтому паушальный TTL пометил бы мёртвыми живые объявления, куда мы просто не заходили. Числа подтверждают: у когорты cian-новостроек, заведомо живых 20-26 июня, за следующие 30 дней повторно увидены всего 11.7%, а свип покрывает 511 уникальных новостроек за 14 дней из 11 336 активных — 4.5%.

Но применён принцип непоследовательно. Вторичку и новостройки Яндекса собирает одна и та же развёртка, одними и теми же комбинациями (pipeline.py:1895, segments ["NO","YES"]), с почти одинаковым повторным охватом — 72.7% против 61.6% за 30 дней. Вторичку при этом убиваем TTL=30, а новостройки не трогаем. Обоснования для разного обращения нет.

Хуже с Цианом: расписания cian_newbuilding_sweep не существует вообще. Первичка Циана не подтверждается никем и просто висит активной — отсюда 10 000 из 11 336.

Порядок действий

Сначала — не расширять TTL. Правильный первый шаг лежит на стороне чтения: закрыть потребителей фильтром свежести (#2656), это чинит деньги одним PR и лечит сразу всех читателей. is_active сейчас означает разное для разных источников, а scraped_at — одно и то же.

Дальше по данным:

  1. Гейт по банам и покрытию для существующих TTL-джоб (урок Авито).
  2. Либо завести cian_newbuilding_sweep, либо осознанно признать первичку Циана неподтверждаемой и деактивировать её по TTL — но после того, как потребители перестанут зависеть от is_active без свежести, иначе снесём 10 тысяч строк вслепую.
  3. Выровнять обращение с сегментами Яндекса: их собирает одна развёртка, значит и TTL должен быть один.
  4. NULL-сегмент cian/yandex (771 активная строка, 96.4% протухли) — решить отдельно, он проходит все сегментные гарды.

Связано: #2574 (пункт 3), #2656, миграции 115 и 165.

Найдено при разборе пункта 3 #2574. Политика деактивации сегодня непоследовательна, и обе крайности уже стрельнули. ## Что живёт на проде Четыре расписания деактивации: avito (TTL=10 дней, **все** сегменты, по `last_seen_at`), cian и yandex (TTL=30, **только** `listing_segment='vtorichka'`), domklik (TTL=14, по `scraped_at`). Для n1 расписание удалено миграцией 165. ## Крайность первая: паушальный TTL без учёта банов TTL=10 у Авито уже один раз выкосил **9 033 строки за 17 дней сплошного бана** — и минимум **1 336 из них доказанно вернулись живыми**, когда сбор восстановился. То есть задача деактивации приняла «мы не смогли зайти» за «объявление снято». TTL-джобам нужен гейт: если по источнику идёт полоса банов или покрытие развёртки просело, деактивацию надо приостанавливать, а не исполнять вслепую. ## Крайность вторая: не подметаем вовсе Аргумент в докстринге `deactivate_stale_avito.py:6-10` (и миграция 115) верен: городские свипы Циана и Яндекса не держат полного покрытия, они сэмплируют скользящее окно, поэтому паушальный TTL пометил бы мёртвыми живые объявления, куда мы просто не заходили. Числа подтверждают: у когорты cian-новостроек, заведомо живых 20-26 июня, за следующие 30 дней повторно увидены всего **11.7%**, а свип покрывает 511 уникальных новостроек за 14 дней из 11 336 активных — **4.5%**. Но применён принцип непоследовательно. **Вторичку и новостройки Яндекса собирает одна и та же развёртка**, одними и теми же комбинациями (`pipeline.py:1895`, segments `["NO","YES"]`), с почти одинаковым повторным охватом — 72.7% против 61.6% за 30 дней. Вторичку при этом убиваем TTL=30, а новостройки не трогаем. Обоснования для разного обращения нет. Хуже с Цианом: расписания `cian_newbuilding_sweep` **не существует вообще**. Первичка Циана не подтверждается никем и просто висит активной — отсюда 10 000 из 11 336. ## Порядок действий Сначала — **не расширять TTL**. Правильный первый шаг лежит на стороне чтения: закрыть потребителей фильтром свежести (#2656), это чинит деньги одним PR и лечит сразу всех читателей. `is_active` сейчас означает разное для разных источников, а `scraped_at` — одно и то же. Дальше по данным: 1. Гейт по банам и покрытию для существующих TTL-джоб (урок Авито). 2. Либо завести `cian_newbuilding_sweep`, либо осознанно признать первичку Циана неподтверждаемой и деактивировать её по TTL — но **после** того, как потребители перестанут зависеть от `is_active` без свежести, иначе снесём 10 тысяч строк вслепую. 3. Выровнять обращение с сегментами Яндекса: их собирает одна развёртка, значит и TTL должен быть один. 4. NULL-сегмент cian/yandex (771 активная строка, 96.4% протухли) — решить отдельно, он проходит все сегментные гарды. Связано: #2574 (пункт 3), #2656, миграции 115 и 165.
Author
Collaborator

Разбор выполнен, все числа перепроверены на проде (read-only). Первый шаг — гейт по здоровью сбора — в PR #2710. Расширение охвата TTL не делаю, ниже почему, с числами.

Что из issue подтвердилось точно

утверждение проверка
4 расписания: avito TTL=10 все сегменты / cian+yandex TTL=30 vtorichka / domklik TTL=14 по scraped_at; deactivate_stale_n1 нет подтверждено, scrape_schedules id 111/133/132/143
cian_newbuilding_sweep не существует подтверждено, среди 52 расписаний нет
9 033 строки за отрезок бана точно: сумма counters->>'deactivated' по deactivate_stale_avito за 10.07-26.07 = 9033
≥1 336 вернулись живыми порядок подтверждён, у меня 1 270 (см. ниже про метод)
у cian-новостроек 11.7% повторных за 30 дней 12.4% на когорте 20-26.06 (n=161)
yandex 72.7% vtorichka против 61.6% novostroyki 71.6% против 62.9% на той же когорте (n=1114 / n=3125)
~10 000 из 11 336 активных первичек Циана протухли 10 048 из 11 395 активных с last_seen_at старше 30 суток
NULL-сегмент 771 активная, 96.4% протухли 768 активных, 742 протухли (96.6%)

Расхождение по «1 336»: я считал так — из listing_source_snapshots беру avito-строки, у которых в какие-то сутки отрезка 10.07-26.07 снимок last_seen_at был старше 10 суток (то есть TTL был обязан их снять; таких 42 535), и смотрю их текущее состояние: 1 270 сейчас активны И last_seen_at позже 31.07. Если у вас была другая отсечка по датам — расхождение в ней; вывод не меняется.

Опровергнутая предпосылка: «полоса банов»

Гейт по scrape_runs.status='banned' не сработал бы. Из трёх провалов сбора за июль баны видны только в одном:

источник окно прогонов/сут banned total_seen что снял TTL
avito 10.07-30.07 4-5 4-5 0 9 033
yandex 18.07-30.07 (13 сут) 5, все done 0 0 ~839 vtorichka
domklik 20.07-30.07 (11 сут) 1-2, все done 0 0 6 131 разом 02.08

Два провала из трёх прошли с нулём банов и статусом done на каждом прогоне. То есть проблема шире, чем в issue: это не «Авито забанили», а «мы вообще не замечаем, что источник перестал собираться». Домкликовский случай — тот же самый механизм, что и авитовский, повторённый через две недели на другом источнике: сейчас у домклика 376 активных строк из 6 594.

Поэтому критерий здоровья в PR #2710 — не статус прогона, а результат: сколько строк источник подтвердил свежими за 3 суток, по той же колонке свежести и тому же срезу source+segments, что и сам UPDATE. Пороги (avito 1500, yandex/cian 500, domklik 200) взяты из восстановленного по listing_source_snapshots ряда подтверждений по дням и лежат между максимумом провала и минимумом здоровых суток — у авито 970 < 1500 < 3542. Тест проигрывает этот ряд по дням и требует блокировки каждых суток провала.

Пункт 3 (Яндекс): обращение выровнено НЕ будет, и это ответ по числам

Несогласованность подтверждаю: одна развёртка, segments=["NO","YES"] (pipeline.py:1712), и объёмы подтверждений у сегментов одинаковые — за 3 суток vtorichka 897..2206, novostroyki 1340..2353 (у первички даже чуть больше). Обоснования разного обращения в этих числах нет.

Но выравнивать в сторону «включить TTL=30 первичке» нельзя, и мешает не первичка, а вторичка. Повторный охват 71.6% за 30 суток означает: из строк, заведомо живых 20-26.06, 28.4% мы просто не переобошли. Доля повторных встреч меряет покрытие нашего обхода, а не жизнь объявления — в этом продукте это уже доказано контрольной группой (источник с почти полным покрытием даёт ноль «возвратов», источник с третью покрытия — 218 в сутки). Значит TTL=30 на yandex/vtorichka уже сегодня снимает неизвестную долю живых, и распространить эту ошибку на первичку (65.8% повторных даже за 41 сутки, 3 662 строки под нож) — это повторить авитовский случай осознанно.

Честного критерия «объявление снято» из наших данных не извлекается: last_seen_at не отличает «снято» от «не зашли». Отличает только тот путь, где площадка ответила 404 — avito_detail_backfill, статус 'closed' (#2674), и он есть лишь у одного источника. Пока признак не выражается, правильный исход — не подбирать TTL получше, а не расширять его. Что выразимо: гейт по здоровью (PR #2710, уже сделан) и фильтр свежести на чтении (#2656).

Итог по Яндексу: выравнивание — в сторону снятия TTL с vtorichka, а не в сторону навешивания на novostroyki. Делать это можно только после #2656, иначе читатели, зависящие от is_active без свежести, получат 3 872 протухшие строки как живые. Оставляю пункт 3 открытым с этой формулировкой.

Пункт 2 (первичка Циана): TTL не включаю

Повторный охват 12.4% за 30 суток и 14.3% за 41 — это не «объявления умерли», это отсутствие свипа. TTL=30 снял бы 10 048 строк, из которых живых почти все. Порядок из issue («сначала покрытие, потом TTL») верен: сначала cian_newbuilding_sweep, и только когда охват станет измеримым — разговор про TTL. До этого протухшая первичка закрывается фильтром свежести на чтении (#2656), а не деактивацией.

Пункт 4 (NULL-сегмент)

742 активные протухшие строки из 768 (cian 212 + yandex 530). Проходят все сегментные гарды. Отдельного решения от меня не требовалось; отмечу только, что после #2656 они закроются на чтении вместе с остальным протухшим, и трогать их TTL не понадобится.

Сколько строк затрагивает каждое изменение

изменение строк статус
гейт по здоровью (PR #2710) 0 строк listings, 4 строки scrape_schedules сделано
TTL=30 на yandex/novostroyki 3 662 не делаю, ждёт #2656
TTL=30 на cian/novostroyki 10 048 не делаю, ждёт свипа + #2656
TTL=30 на NULL-сегмент cian+yandex 742 не делаю, закроется #2656

Побочный эффект гейта, о котором стоит знать заранее: deactivate_stale_domklik начнёт пропускать прогоны сразу после деплоя (62 подтверждения за 3 суток против порога 200). Это верный исход — сбор по домклику фактически стоит, — но он делает видимой поломку, которая до сих пор была тихой.

Разбор выполнен, все числа перепроверены на проде (read-only). Первый шаг — гейт по здоровью сбора — в PR #2710. Расширение охвата TTL **не делаю**, ниже почему, с числами. ## Что из issue подтвердилось точно | утверждение | проверка | |---|---| | 4 расписания: avito TTL=10 все сегменты / cian+yandex TTL=30 vtorichka / domklik TTL=14 по `scraped_at`; `deactivate_stale_n1` нет | подтверждено, `scrape_schedules` id 111/133/132/143 | | `cian_newbuilding_sweep` не существует | подтверждено, среди 52 расписаний нет | | 9 033 строки за отрезок бана | **точно**: сумма `counters->>'deactivated'` по `deactivate_stale_avito` за 10.07-26.07 = 9033 | | ≥1 336 вернулись живыми | порядок подтверждён, у меня **1 270** (см. ниже про метод) | | у cian-новостроек 11.7% повторных за 30 дней | **12.4%** на когорте 20-26.06 (n=161) | | yandex 72.7% vtorichka против 61.6% novostroyki | **71.6% против 62.9%** на той же когорте (n=1114 / n=3125) | | ~10 000 из 11 336 активных первичек Циана протухли | **10 048 из 11 395** активных с `last_seen_at` старше 30 суток | | NULL-сегмент 771 активная, 96.4% протухли | **768 активных, 742 протухли (96.6%)** | Расхождение по «1 336»: я считал так — из `listing_source_snapshots` беру avito-строки, у которых в какие-то сутки отрезка 10.07-26.07 снимок `last_seen_at` был старше 10 суток (то есть TTL был обязан их снять; таких 42 535), и смотрю их текущее состояние: 1 270 сейчас активны И `last_seen_at` позже 31.07. Если у вас была другая отсечка по датам — расхождение в ней; вывод не меняется. ## Опровергнутая предпосылка: «полоса банов» Гейт по `scrape_runs.status='banned'` **не сработал бы**. Из трёх провалов сбора за июль баны видны только в одном: | источник | окно | прогонов/сут | `banned` | `total_seen` | что снял TTL | |---|---|---|---|---|---| | avito | 10.07-30.07 | 4-5 | 4-5 | 0 | 9 033 | | **yandex** | **18.07-30.07 (13 сут)** | **5, все `done`** | **0** | **0** | **~839 vtorichka** | | **domklik** | **20.07-30.07 (11 сут)** | **1-2, все `done`** | **0** | **0** | **6 131 разом 02.08** | Два провала из трёх прошли с нулём банов и статусом `done` на каждом прогоне. То есть проблема шире, чем в issue: это не «Авито забанили», а «мы вообще не замечаем, что источник перестал собираться». Домкликовский случай — тот же самый механизм, что и авитовский, повторённый через две недели на другом источнике: сейчас у домклика **376 активных строк из 6 594**. Поэтому критерий здоровья в PR #2710 — не статус прогона, а результат: сколько строк источник подтвердил свежими за 3 суток, по той же колонке свежести и тому же срезу `source+segments`, что и сам UPDATE. Пороги (avito 1500, yandex/cian 500, domklik 200) взяты из восстановленного по `listing_source_snapshots` ряда подтверждений по дням и лежат между максимумом провала и минимумом здоровых суток — у авито 970 < 1500 < 3542. Тест проигрывает этот ряд по дням и требует блокировки каждых суток провала. ## Пункт 3 (Яндекс): обращение выровнено НЕ будет, и это ответ по числам Несогласованность подтверждаю: одна развёртка, `segments=["NO","YES"]` (`pipeline.py:1712`), и объёмы подтверждений у сегментов **одинаковые** — за 3 суток vtorichka 897..2206, novostroyki 1340..2353 (у первички даже чуть больше). Обоснования разного обращения в этих числах нет. Но выравнивать в сторону «включить TTL=30 первичке» нельзя, и мешает не первичка, а **вторичка**. Повторный охват 71.6% за 30 суток означает: из строк, заведомо живых 20-26.06, 28.4% мы просто не переобошли. Доля повторных встреч меряет **покрытие нашего обхода**, а не жизнь объявления — в этом продукте это уже доказано контрольной группой (источник с почти полным покрытием даёт ноль «возвратов», источник с третью покрытия — 218 в сутки). Значит `TTL=30` на yandex/vtorichka **уже сегодня** снимает неизвестную долю живых, и распространить эту ошибку на первичку (65.8% повторных даже за 41 сутки, **3 662 строки** под нож) — это повторить авитовский случай осознанно. Честного критерия «объявление снято» из наших данных не извлекается: `last_seen_at` не отличает «снято» от «не зашли». Отличает только тот путь, где площадка ответила 404 — `avito_detail_backfill`, статус `'closed'` (#2674), и он есть лишь у одного источника. Пока признак не выражается, правильный исход — не подбирать TTL получше, а не расширять его. Что выразимо: гейт по здоровью (PR #2710, уже сделан) и фильтр свежести на чтении (#2656). **Итог по Яндексу: выравнивание — в сторону снятия TTL с vtorichka, а не в сторону навешивания на novostroyki. Делать это можно только после #2656**, иначе читатели, зависящие от `is_active` без свежести, получат 3 872 протухшие строки как живые. Оставляю пункт 3 открытым с этой формулировкой. ## Пункт 2 (первичка Циана): TTL не включаю Повторный охват 12.4% за 30 суток и 14.3% за 41 — это не «объявления умерли», это отсутствие свипа. TTL=30 снял бы **10 048 строк**, из которых живых почти все. Порядок из issue («сначала покрытие, потом TTL») верен: сначала `cian_newbuilding_sweep`, и только когда охват станет измеримым — разговор про TTL. До этого протухшая первичка закрывается фильтром свежести на чтении (#2656), а не деактивацией. ## Пункт 4 (NULL-сегмент) **742 активные протухшие строки** из 768 (cian 212 + yandex 530). Проходят все сегментные гарды. Отдельного решения от меня не требовалось; отмечу только, что после #2656 они закроются на чтении вместе с остальным протухшим, и трогать их TTL не понадобится. ## Сколько строк затрагивает каждое изменение | изменение | строк | статус | |---|---|---| | гейт по здоровью (PR #2710) | 0 строк `listings`, 4 строки `scrape_schedules` | сделано | | TTL=30 на yandex/novostroyki | 3 662 | **не делаю**, ждёт #2656 | | TTL=30 на cian/novostroyki | 10 048 | **не делаю**, ждёт свипа + #2656 | | TTL=30 на NULL-сегмент cian+yandex | 742 | **не делаю**, закроется #2656 | Побочный эффект гейта, о котором стоит знать заранее: `deactivate_stale_domklik` начнёт пропускать прогоны сразу после деплоя (62 подтверждения за 3 суток против порога 200). Это верный исход — сбор по домклику фактически стоит, — но он делает видимой поломку, которая до сих пор была тихой.
Author
Collaborator

Продолжение разбора после #2710/#2711/#2765. Все числа — прод, read-only, 2026-08-09. Правка — PR #2797.

Что из issue уже закрыто (проверено на живом проде)

Гейт здоровья #2710 работает и виден в счётчиках. deactivate_stale_domklik пропускается третьи сутки подряд — 07.08 confirmations: 59, 08.08 и 09.08 confirmations: 0, каждый раз skipped_unhealthy: 1, deactivated: 0. Остальные три исполняются: avito confirmations: 1578-1703, cian 1613-4753, yandex 2172-2214. То есть предсказанный побочный эффект наступил ровно как обещано, и ни одна строка домклика с тех пор не снята.

Опровергнутая предпосылка № 1: возвращать 9 033 строки нечего и незачем

Из 9 033 7 531 всё ещё неактивны (avito, last_seen_at в 30.06-16.07). Остальные ~1 500 вернулись сами: upsert в scraper_kit/base.py на ON CONFLICT ставит is_active = true, поэтому любая несправедливо снятая строка воскресает в тот момент, когда свип снова её видит. Отдельная процедура возврата дублировала бы то, что уже делает сбор.

А по оставшимся 7 531 возвращать нечего: за 10 суток здорового сбора avito (31.07-09.08) не увидена ни одна из них — 0 из 7 531. Пометить их живыми значило бы утверждать то, чего сбор не подтвердил ни разу. Плюс это ничего не изменило бы для продукта: читатели фильтруют по свежести, а scraped_at у них — начало июля.

Журнал в духе house_merge_log тут тоже не нужен: журнал делает обратимым разрушительное действие (удаление строки). Деактивация ничего не разрушает — is_active снимается и ставится обратно сбором, а след в истории уже пишется снимком 'stale' (#2674).

Опровергнутая предпосылка № 2: ban_kind гейту не поможет

Гейт стоит ниже по течению от причины: и инфра-бан, и бан площадки проявляются одинаково — падением числа подтверждений, и гейт ловит оба. Обратный случай (площадка забанила нас, но подтверждения идут) означает, что сбор фактически работает — блокировать деактивацию не за что.

Данные это подтверждают с обеих сторон. Два провала из трёх прошли вообще без банов (уже в разборе выше). А сейчас ban_kind заполняется — и он не совпадает с тем, лежит ли сбор: за 14 суток у avito_city_sweep_* есть banned/platform по одному прогону на Серов (02.08), Каменск-Уральский (03.08), Первоуральск (06.08), при этом все три города собираются, и conf_10d у них равен числу активных строк. Ban-признак сработал бы там, где сбор жив.

Что осталось и почему это не «подкрутить TTL»

Гейт отвечает «источник собирается?». Он не отвечает на второй вопрос: достаточно ли ttl_days, чтобы молчание означало снятие? Пока свип возвращается к строке реже, чем раз в ttl_days, TTL меряет наш обход, а не жизнь объявления — и источник при этом здоров, гейт молчит.

С деплоя гейта 06.08 TTL снял 1 028 строк, 127 (12.4%) уже снова активны. По городам:

срез снято снова активны
cian / Екатеринбург 103 103 (100%)
yandex / Екатеринбург 24 24 (100%)
cian / без города 560 0
yandex / без города 341 0

В единственном городе с настоящим покрытием ложны все снятия. Возраст у всех 127 на момент снятия — 29.9..30.3 суток при TTL=30: срабатывание ровно на границе, свип возвращался на 31-34-е сутки.

Разрывы переобхода против текущих порогов (listing_source_snapshots, 40 суток, срез = срез TTL-джобы):

источник / сегмент p90 p99 TTL TTL/p99
domklik / vtorichka 1.9 3.1 14 4.5
cian / vtorichka 10.9 26.6 30 1.13
yandex / vtorichka 5.7 43.0 30 0.70
avito / vtorichka 29.1 42.1 10 0.24

Домклик — контрольная группа из своих же данных: при почти полном суточном обходе порог лежит в 4.5 раза выше хвоста, и снятие у него действительно означает снятие. У трёх остальных порог внутри хвоста собственного обхода. Это и есть «паушальный TTL» из заголовка issue, только теперь измеренный.

Поэтому в PR #2797 не новая константа, а измеряемый факт: какой самый большой возраст, при котором свип за последнее окно ДОКАЗАЛ строку живой. эффективный TTL = max(ttl_days, пол), тот же срез и та же колонка свежести, что у UPDATE. Побочно это закрывает и исходную просьбу про баны: после провала сбора хвост разрывов распухает, пол поднимается, деактивация замирает сама — через результат, а не через причину.

Пункты 2-4 issue: без изменений

Первичка Циана, выравнивание сегментов Яндекса и NULL-сегмент по-прежнему ждут #2656 — обоснование в разборе выше в силе. Замечу только, что пол переобхода снимает с них срочность: yandex/novostroyki имеет p99 разрыва 38.0 суток, то есть TTL=30 на них навесить было бы прямой ошибкой, и теперь это видно числом, а не рассуждением.

Продолжение разбора после #2710/#2711/#2765. Все числа — прод, read-only, 2026-08-09. Правка — PR #2797. ## Что из issue уже закрыто (проверено на живом проде) **Гейт здоровья #2710 работает и виден в счётчиках.** `deactivate_stale_domklik` пропускается третьи сутки подряд — 07.08 `confirmations: 59`, 08.08 и 09.08 `confirmations: 0`, каждый раз `skipped_unhealthy: 1, deactivated: 0`. Остальные три исполняются: avito `confirmations: 1578-1703`, cian `1613-4753`, yandex `2172-2214`. То есть предсказанный побочный эффект наступил ровно как обещано, и ни одна строка домклика с тех пор не снята. ## Опровергнутая предпосылка № 1: возвращать 9 033 строки нечего и незачем Из 9 033 **7 531 всё ещё неактивны** (avito, `last_seen_at` в 30.06-16.07). Остальные ~1 500 вернулись сами: upsert в `scraper_kit/base.py` на `ON CONFLICT` ставит `is_active = true`, поэтому любая несправедливо снятая строка воскресает в тот момент, когда свип снова её видит. Отдельная процедура возврата дублировала бы то, что уже делает сбор. А по оставшимся 7 531 возвращать нечего: **за 10 суток здорового сбора avito (31.07-09.08) не увидена ни одна из них — 0 из 7 531.** Пометить их живыми значило бы утверждать то, чего сбор не подтвердил ни разу. Плюс это ничего не изменило бы для продукта: читатели фильтруют по свежести, а `scraped_at` у них — начало июля. Журнал в духе `house_merge_log` тут тоже не нужен: журнал делает обратимым **разрушительное** действие (удаление строки). Деактивация ничего не разрушает — `is_active` снимается и ставится обратно сбором, а след в истории уже пишется снимком `'stale'` (#2674). ## Опровергнутая предпосылка № 2: `ban_kind` гейту не поможет Гейт стоит **ниже по течению** от причины: и инфра-бан, и бан площадки проявляются одинаково — падением числа подтверждений, и гейт ловит оба. Обратный случай (площадка забанила нас, но подтверждения идут) означает, что сбор фактически работает — блокировать деактивацию не за что. Данные это подтверждают с обеих сторон. Два провала из трёх прошли вообще без банов (уже в разборе выше). А сейчас `ban_kind` заполняется — и он **не совпадает** с тем, лежит ли сбор: за 14 суток у `avito_city_sweep_*` есть `banned/platform` по одному прогону на Серов (02.08), Каменск-Уральский (03.08), Первоуральск (06.08), при этом все три города собираются, и `conf_10d` у них равен числу активных строк. Ban-признак сработал бы там, где сбор жив. ## Что осталось и почему это не «подкрутить TTL» Гейт отвечает «источник собирается?». Он не отвечает на второй вопрос: **достаточно ли `ttl_days`, чтобы молчание означало снятие?** Пока свип возвращается к строке реже, чем раз в `ttl_days`, TTL меряет наш обход, а не жизнь объявления — и источник при этом здоров, гейт молчит. С деплоя гейта 06.08 TTL снял 1 028 строк, **127 (12.4%) уже снова активны**. По городам: | срез | снято | снова активны | |---|---|---| | cian / Екатеринбург | 103 | **103 (100%)** | | yandex / Екатеринбург | 24 | **24 (100%)** | | cian / без города | 560 | 0 | | yandex / без города | 341 | 0 | В единственном городе с настоящим покрытием ложны **все** снятия. Возраст у всех 127 на момент снятия — 29.9..30.3 суток при TTL=30: срабатывание ровно на границе, свип возвращался на 31-34-е сутки. Разрывы переобхода против текущих порогов (`listing_source_snapshots`, 40 суток, срез = срез TTL-джобы): | источник / сегмент | p90 | p99 | TTL | TTL/p99 | |---|---|---|---|---| | **domklik / vtorichka** | 1.9 | 3.1 | 14 | **4.5** | | cian / vtorichka | 10.9 | 26.6 | 30 | 1.13 | | yandex / vtorichka | 5.7 | 43.0 | 30 | 0.70 | | avito / vtorichka | 29.1 | 42.1 | 10 | **0.24** | Домклик — контрольная группа из своих же данных: при почти полном суточном обходе порог лежит в 4.5 раза выше хвоста, и снятие у него действительно означает снятие. У трёх остальных порог внутри хвоста собственного обхода. Это и есть «паушальный TTL» из заголовка issue, только теперь измеренный. Поэтому в PR #2797 не новая константа, а измеряемый факт: какой самый большой возраст, при котором свип за последнее окно ДОКАЗАЛ строку живой. `эффективный TTL = max(ttl_days, пол)`, тот же срез и та же колонка свежести, что у UPDATE. Побочно это закрывает и исходную просьбу про баны: после провала сбора хвост разрывов распухает, пол поднимается, деактивация замирает сама — через результат, а не через причину. ## Пункты 2-4 issue: без изменений Первичка Циана, выравнивание сегментов Яндекса и NULL-сегмент по-прежнему ждут #2656 — обоснование в разборе выше в силе. Замечу только, что пол переобхода снимает с них срочность: yandex/novostroyki имеет p99 разрыва 38.0 суток, то есть TTL=30 на них навесить было бы прямой ошибкой, и теперь это видно числом, а не рассуждением.
Author
Collaborator

Прод 12.08: п.1 закрыт и работает, пп.2-4 по-прежнему ждут #2656 — задача остаётся

Пункт 1 (гейт по банам и покрытию для TTL-джоб) — сделан и виден в счётчиках. PR #2797 (пол переобхода) смержен 09.08 17:26 и живёт на проде. Три последних суток, все четыре джобы done ежесуточно:

джоба confirmations (10.08 → 12.08) revisit_floor_days ttl_days_effective deactivated
deactivate_stale_avito 6934 → 7138 52 52 (номинал 10) 0
deactivate_stale_cian 4812 → 5193 34 → 37 37 (номинал 30) 0
deactivate_stale_yandex 2150 → 2168 75 75 (номинал 30) 0
deactivate_stale_domklik 372 → 376 23 → 25 25 (номинал 14) 0

Пол считается из данных (самый большой возраст, при котором свип ДОКАЗАЛ строку живой), а не константой, и у всех четырёх источников он уже выше номинального TTL. Следствие: deactivated = 0 у всех, третьи сутки подряд. Класс ложных снятий, измеренный 09.08 (1 028 снятых, 127 воскресли, в Екатеринбурге ложны 100% — 103 из 103 cian и 24 из 24 yandex), больше не воспроизводится: снимать перестали вовсе.

Ноль здесь имеет причину «уже починено», а не «джоба не бежала»: все четыре отработали done сегодня утром, просто порог поднялся выше хвоста собственного обхода.

Побочно закрылся и исходный сюжет про баны: deactivate_stale_domklik больше не уходит в skipped_unhealthy (confirmations 376 против порога 200), но и не снимает ничего — пол 25 суток против TTL 14. Гейт по здоровью и пол переобхода работают вместе, не мешая друг другу.

Пункты 2, 3, 4 — без изменений, и это осознанное состояние, а не забытое:

  • п.2 первичка Циана (10 048 протухших активных) — ждёт cian_newbuilding_sweep; TTL до появления охвата включать нельзя;
  • п.3 выравнивание сегментов Яндекса — в сторону снятия TTL с vtorichka, а не навешивания на novostroyki (p99 разрыва переобхода у первички 38.0 суток при TTL 30), и только после #2656;
  • п.4 NULL-сегмент cian+yandex (742 протухшие активные) — закроется фильтром свежести на чтении вместе с остальным.

Задача остаётся открытой на #2656. Кодовой работы по TTL в ней больше нет — есть ожидание чужой правки, после которой все три пункта решаются одним разговором.

## Прод 12.08: п.1 закрыт и работает, пп.2-4 по-прежнему ждут #2656 — задача остаётся **Пункт 1 (гейт по банам и покрытию для TTL-джоб) — сделан и виден в счётчиках.** PR #2797 (пол переобхода) смержен 09.08 17:26 и живёт на проде. Три последних суток, все четыре джобы `done` ежесуточно: | джоба | confirmations (10.08 → 12.08) | revisit_floor_days | ttl_days_effective | deactivated | |---|---|---:|---:|---:| | `deactivate_stale_avito` | 6934 → 7138 | 52 | **52** (номинал 10) | 0 | | `deactivate_stale_cian` | 4812 → 5193 | 34 → 37 | **37** (номинал 30) | 0 | | `deactivate_stale_yandex` | 2150 → 2168 | 75 | **75** (номинал 30) | 0 | | `deactivate_stale_domklik` | 372 → 376 | 23 → 25 | **25** (номинал 14) | 0 | Пол считается из данных (самый большой возраст, при котором свип ДОКАЗАЛ строку живой), а не константой, и у всех четырёх источников он уже выше номинального TTL. Следствие: **deactivated = 0 у всех, третьи сутки подряд**. Класс ложных снятий, измеренный 09.08 (1 028 снятых, 127 воскресли, в Екатеринбурге ложны 100% — 103 из 103 cian и 24 из 24 yandex), больше не воспроизводится: снимать перестали вовсе. Ноль здесь имеет причину «**уже починено**», а не «джоба не бежала»: все четыре отработали `done` сегодня утром, просто порог поднялся выше хвоста собственного обхода. Побочно закрылся и исходный сюжет про баны: `deactivate_stale_domklik` больше не уходит в `skipped_unhealthy` (confirmations 376 против порога 200), но и не снимает ничего — пол 25 суток против TTL 14. Гейт по здоровью и пол переобхода работают вместе, не мешая друг другу. **Пункты 2, 3, 4 — без изменений, и это осознанное состояние, а не забытое:** - **п.2** первичка Циана (10 048 протухших активных) — ждёт `cian_newbuilding_sweep`; TTL до появления охвата включать нельзя; - **п.3** выравнивание сегментов Яндекса — в сторону **снятия** TTL с `vtorichka`, а не навешивания на `novostroyki` (p99 разрыва переобхода у первички 38.0 суток при TTL 30), и только после #2656; - **п.4** NULL-сегмент cian+yandex (742 протухшие активные) — закроется фильтром свежести на чтении вместе с остальным. Задача остаётся открытой **на #2656**. Кодовой работы по TTL в ней больше нет — есть ожидание чужой правки, после которой все три пункта решаются одним разговором.
lekss361 added the
bug
data
priority/p2
scope/backend
scrapers
tradein
labels 2026-08-16 10:25:12 +00:00
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#2659
No description provided.