[HIGH] tradein: статус «забанен» на инфраошибке — из-за него сбор Авито замедлили вдвое по ложному основанию #2686

Closed
opened 2026-08-05 22:47:05 +00:00 by bot-backend · 4 comments
Collaborator

Найдено при разборе полного обхода Авито (эпик #2674, PR #2685). Это не «ещё один статус, который врёт» — этот конкретный уже стоил нам решения.

Что случилось

Все пять прогонов avito_full_load_exhaustive со статусом «забанен» несут один и тот же текст ошибки: 503 от нашего собственного браузерного сайдкара, у которого не поднялся камуфокс. Площадка тут ни при чём — в те же 45 дней другие задачи Авито отработали успешно 20, 21 и 63 раза.

Корень был в мёртвом прокси-аккаунте, прописанном в окружении сайдкара (#2613): полный обход строил свой браузерный клиент мимо пула и падал именно в этот запасной путь. Починено не этой задачей — #2637 подключил пул, #2616 снёс мёртвые переменные.

Чего это стоило

Миграция 206 прочитала статус «забанен» буквально — как «площадка распознаёт паттерн полного обхода» — и на этом основании замедлила задачу более чем вдвое и перевела на недельный такт.

Основание было ложным. Мы деградировали собственный сбор, потому что статус сказал «нас забанили», а на самом деле у нас не стартовал браузер.

Побочно это же породило вторую находку: недельный такт разошёлся с двухдневным окном ретроспективы, и прогон стал видеть трое суток из семи (чинится в #2685).

Почему статус врёт

Инфраструктурный отказ — 503 от своего же сервиса — попадает в ту же категорию, что блокировка площадкой. Различить их снаружи нельзя, а решения они требуют противоположных: на блокировку правильно замедлиться и сменить адрес, на упавший сайдкар — перезапустить сайдкар и не трогать темп.

Плюс это дало третий эффект, чисто кодовый: 503 классифицируется как мягкий бан и уходит в бюджет ротации адреса, а ротация снята — значит условие повтора никогда не истинно, а бюджет обычных повторов лежал в недостижимой ветке. Ноль повторов на самую частую ошибку: один блип съедал бакет, четыре подряд — весь прогон. Это чинится в #2685.

Что предлагается

Развести «нас заблокировала площадка» и «у нас упала инфраструктура» на уровне статуса. Набор статусов ограничен проверкой в схеме, поэтому нужен либо новый статус, либо явное поле причины.

Важная деталь, из-за которой это не тривиально: пометка «забанен», в отличие от «упал», сохраняет чекпоинт пройденных бакетов — то есть у неё есть побочная функция, которую нельзя потерять при разведении.

Почему это HIGH

Третий случай за сутки, когда врущий статус приводит к неверному решению — но первый, где решение уже принято и живёт в проде больше недели. Пока статусы смешивают наши сбои с чужими, любой вывод вида «площадка нас палит» надо перепроверять вручную, а это ровно то, чего от статуса ждут.

Связано: #2674, #2685, #2657, #2670, #2613, #2616, #2637, миграция 206.

Найдено при разборе полного обхода Авито (эпик #2674, PR #2685). Это не «ещё один статус, который врёт» — этот конкретный уже стоил нам решения. ## Что случилось Все пять прогонов `avito_full_load_exhaustive` со статусом «забанен» несут один и тот же текст ошибки: **503 от нашего собственного браузерного сайдкара**, у которого не поднялся камуфокс. Площадка тут ни при чём — в те же 45 дней другие задачи Авито отработали успешно 20, 21 и 63 раза. Корень был в мёртвом прокси-аккаунте, прописанном в окружении сайдкара (#2613): полный обход строил свой браузерный клиент **мимо пула** и падал именно в этот запасной путь. Починено не этой задачей — #2637 подключил пул, #2616 снёс мёртвые переменные. ## Чего это стоило Миграция 206 прочитала статус «забанен» буквально — как «площадка распознаёт паттерн полного обхода» — и на этом основании **замедлила задачу более чем вдвое и перевела на недельный такт**. **Основание было ложным.** Мы деградировали собственный сбор, потому что статус сказал «нас забанили», а на самом деле у нас не стартовал браузер. Побочно это же породило вторую находку: недельный такт разошёлся с двухдневным окном ретроспективы, и прогон стал видеть трое суток из семи (чинится в #2685). ## Почему статус врёт Инфраструктурный отказ — 503 от своего же сервиса — попадает в ту же категорию, что блокировка площадкой. Различить их снаружи нельзя, а решения они требуют противоположных: на блокировку правильно замедлиться и сменить адрес, на упавший сайдкар — перезапустить сайдкар и **не** трогать темп. Плюс это дало третий эффект, чисто кодовый: 503 классифицируется как мягкий бан и уходит в бюджет ротации адреса, а ротация снята — значит условие повтора никогда не истинно, а бюджет обычных повторов лежал в недостижимой ветке. **Ноль повторов на самую частую ошибку**: один блип съедал бакет, четыре подряд — весь прогон. Это чинится в #2685. ## Что предлагается Развести «нас заблокировала площадка» и «у нас упала инфраструктура» на уровне статуса. Набор статусов ограничен проверкой в схеме, поэтому нужен либо новый статус, либо явное поле причины. Важная деталь, из-за которой это не тривиально: пометка «забанен», в отличие от «упал», **сохраняет чекпоинт пройденных бакетов** — то есть у неё есть побочная функция, которую нельзя потерять при разведении. ## Почему это HIGH Третий случай за сутки, когда врущий статус приводит к неверному решению — но первый, где решение уже **принято и живёт в проде** больше недели. Пока статусы смешивают наши сбои с чужими, любой вывод вида «площадка нас палит» надо перепроверять вручную, а это ровно то, чего от статуса ждут. Связано: #2674, #2685, #2657, #2670, #2613, #2616, #2637, миграция 206.
Author
Collaborator

Уточнения после ревью PR #2685 — часть цифр в тексте выше поправлена, а диагноз стал сильнее.

Диагноз подтверждён и расширен

Я написал «все пять прогонов полного обхода несут текст про сайдкар». Замер шире: за 45 суток из 96 забаненных прогонов Авито 90 несут тот же текст, а настоящая блокировка площадкой — ровно 4.

То есть проблема не в одной задаче: тот же сайдкар убил 21 прогон городского свипа, 21 по Нижнему Тагилу и 23 по новостройкам. Мы почти два месяца считали, что нас банит Авито, а падал наш собственный браузер.

Фикс повторов стоит в общей воронке всех браузерных обходов Авито, поэтому чинит их все разом.

Поправка к числу

В тексте выше и в комментарии миграции стояло «на возрасте ровно 7 суток пик в 51% наблюдений». Верно 32.6% (1212 из 3714); 51% получается только если ограничить знаменатель возрастами 0–13.

И объяснение механизма было неверным

Я написал, что у Авито недельный авто-подъём объявлений. На самом деле это квантование относительных меток нашим же парсером: «неделю назад» превращается ровно в минус семь суток, «две недели» — в минус четырнадцать.

Доказательство — плато: при настоящем недельном подъёме возрасты 8–13 были бы заполнены непрерывно, а там три лота из 2174. Плюс пик привязан к дате наблюдения, а не ко дню недели.

Практический вывод от этого крепнет: окно в шесть суток резало бы не «по пику распределения», а по границе квантования — весь бакет «неделю назад», внутри которого реальный возраст от 7 до 13 суток, пропадал бы целиком. Замер подтверждает: охват 446 лотов против 1275.

Предупреждение на будущее — важное именно для этой задачи

Эта задача доказывает, что такт замедлили по ложному основанию. Значит вероятный следующий шаг — вернуть ежедневный такт. Если это сделать, окно останется семисуточным: ни миграция, ни планировщик его не сужают.

Получится ежедневный прогон с семисуточной глубиной — восьмикратная нагрузка каждый день вместо недельной.

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

Уточнения после ревью PR #2685 — часть цифр в тексте выше поправлена, а диагноз стал **сильнее**. ## Диагноз подтверждён и расширен Я написал «все пять прогонов полного обхода несут текст про сайдкар». Замер шире: за 45 суток из **96** забаненных прогонов Авито **90 несут тот же текст**, а настоящая блокировка площадкой — ровно **4**. То есть проблема не в одной задаче: тот же сайдкар убил 21 прогон городского свипа, 21 по Нижнему Тагилу и 23 по новостройкам. **Мы почти два месяца считали, что нас банит Авито, а падал наш собственный браузер.** Фикс повторов стоит в общей воронке всех браузерных обходов Авито, поэтому чинит их все разом. ## Поправка к числу В тексте выше и в комментарии миграции стояло «на возрасте ровно 7 суток пик в 51% наблюдений». Верно **32.6%** (1212 из 3714); 51% получается только если ограничить знаменатель возрастами 0–13. ## И объяснение механизма было неверным Я написал, что у Авито недельный авто-подъём объявлений. На самом деле это **квантование относительных меток нашим же парсером**: «неделю назад» превращается ровно в минус семь суток, «две недели» — в минус четырнадцать. Доказательство — плато: при настоящем недельном подъёме возрасты 8–13 были бы заполнены непрерывно, а там три лота из 2174. Плюс пик привязан к дате наблюдения, а не ко дню недели. Практический вывод от этого **крепнет**: окно в шесть суток резало бы не «по пику распределения», а **по границе квантования** — весь бакет «неделю назад», внутри которого реальный возраст от 7 до 13 суток, пропадал бы целиком. Замер подтверждает: охват 446 лотов против 1275. ## Предупреждение на будущее — важное именно для этой задачи Эта задача доказывает, что такт замедлили по ложному основанию. Значит вероятный следующий шаг — **вернуть ежедневный такт**. Если это сделать, окно останется семисуточным: ни миграция, ни планировщик его не сужают. Получится ежедневный прогон с семисуточной глубиной — восьмикратная нагрузка каждый день вместо недельной. **Возвращаете такт — возвращайте и окно.** Предупреждение вписывается в комментарий миграции, но пусть будет и здесь: тот, кто откатит 206, скорее всего придёт сначала сюда.
Author
Collaborator

Замер на всей истории, а не на пяти прогонах

В теле задачи разобраны 5 прогонов avito_full_load_exhaustive. Расклад по всем avito-прогонам со статусом «забанен» (прод, 2026-08-06):

Что на самом деле произошло Прогонов Период
Наш сайдкар (browser unavailable (proxy may be down)) 92 04.07 – 03.08
Площадка (Avito SERP firewall … IP banned) 9 16.06 – 03.08
HTTP 429 / 403 / пустая выдача 13 30.05 – 23.06

92 из 114 (81%) прогонов, помеченных «нас забанили», — это отказ нашего собственного браузерного сайдкара. Задето не одно задание, а пять: avito_full_load, avito_full_load_exhaustive, avito_city_sweep, avito_city_sweep_nizhniy_tagil, avito_newbuilding_sweep.

Разделитель уже лежит в данных

Оба случая пишут разный текст ошибки в одну и ту же строку статуса:

banned ← avito SERP browser-sidecar error (page=1): browser unavailable (proxy may be down)
banned ← Avito SERP firewall (browser-mode) at page=2 — IP banned

То есть информация для разведения статусов есть на месте записи и теряется ровно в момент присвоения статуса. Классификатору не нужен новый источник знания — нужно перестать схлопывать.

Причина, судя по всему, устранена — а последствие решения живёт

Последний «бан» любого вида: 03.08. С 04.08 (после #2634 + #2640) — 7 прогонов, все done, ни одного banned.

При этом деградация из миграции 206 (недельный такт, замедление более чем вдвое) всё ещё в силе, и её основание теперь дважды несостоятельно: оно было ложным изначально и описываемого явления больше не происходит.

Возврат такта сознательно не предлагаю здесь — это решение вынесено в #2687 на данные 9–10.08. Эта задача про другое: пока статус смешивает наш отказ с чужим, любое такое решение снова будет приниматься по неверному признаку.

Что важно не сломать

Пометка «забанен», в отличие от «упал», сохраняет чекпоинт пройденных бакетов. При разведении статусов эта побочная функция должна остаться у обоих исходов: и наш отказ, и блокировка площадки одинаково не должны заставлять следующий прогон начинать с нуля.

## Замер на всей истории, а не на пяти прогонах В теле задачи разобраны 5 прогонов `avito_full_load_exhaustive`. Расклад по **всем** avito-прогонам со статусом «забанен» (прод, 2026-08-06): | Что на самом деле произошло | Прогонов | Период | |---|---:|---| | **Наш сайдкар** (`browser unavailable (proxy may be down)`) | **92** | 04.07 – 03.08 | | Площадка (`Avito SERP firewall … IP banned`) | 9 | 16.06 – 03.08 | | HTTP 429 / 403 / пустая выдача | 13 | 30.05 – 23.06 | **92 из 114 (81%) прогонов, помеченных «нас забанили», — это отказ нашего собственного браузерного сайдкара.** Задето не одно задание, а пять: `avito_full_load`, `avito_full_load_exhaustive`, `avito_city_sweep`, `avito_city_sweep_nizhniy_tagil`, `avito_newbuilding_sweep`. ## Разделитель уже лежит в данных Оба случая пишут **разный текст ошибки** в одну и ту же строку статуса: ``` banned ← avito SERP browser-sidecar error (page=1): browser unavailable (proxy may be down) banned ← Avito SERP firewall (browser-mode) at page=2 — IP banned ``` То есть информация для разведения статусов есть на месте записи и теряется ровно в момент присвоения статуса. Классификатору не нужен новый источник знания — нужно перестать схлопывать. ## Причина, судя по всему, устранена — а последствие решения живёт Последний «бан» любого вида: **03.08**. С 04.08 (после #2634 + #2640) — **7 прогонов, все `done`, ни одного `banned`**. При этом деградация из миграции 206 (недельный такт, замедление более чем вдвое) **всё ещё в силе**, и её основание теперь дважды несостоятельно: оно было ложным изначально и описываемого явления больше не происходит. Возврат такта сознательно не предлагаю здесь — это решение вынесено в #2687 на данные 9–10.08. Эта задача про другое: пока статус смешивает наш отказ с чужим, любое такое решение снова будет приниматься по неверному признаку. ## Что важно не сломать Пометка «забанен», в отличие от «упал», **сохраняет чекпоинт пройденных бакетов**. При разведении статусов эта побочная функция должна остаться у обоих исходов: и наш отказ, и блокировка площадки одинаково не должны заставлять следующий прогон начинать с нуля.
Author
Collaborator

Поправка к моему замеру выше: «баны прекратились» — неверно

Я написал: «Последний "бан" любого вида: 03.08. С 04.08 — 7 прогонов, все done, ни одного banned». Это опровергнуто ретро-классификацией, которую сделал PR #2711, и я перепроверил вручную.

Все avito-прогоны с 04.08:

3107 avito_newbuilding_sweep        done       04.08 02:03
3122 avito_city_sweep               done       04.08 06:23
3126 avito_detail_backfill          done       04.08 06:53
3177 avito_detail_backfill          done       05.08 01:38
3180 avito_newbuilding_sweep        done       05.08 02:31
3196 avito_city_sweep               done       05.08 06:18
3251 avito_city_sweep_nizhniy_tagil done       06.08 00:28
3253 avito_city_sweep_kamensk_ur.   done       06.08 01:10
3267 avito_newbuilding_sweep        done       06.08 04:46
3273 avito_city_sweep_pervouralsk   banned  ← platform | 06.08 05:51
     Avito SERP firewall (browser-mode) at page=3 — IP banned
3276 avito_city_sweep               done       06.08 06:25
3283 avito_city_sweep_verkh.pyshma  done       06.08 07:40
3287 avito_city_sweep_serov         done       06.08 08:10

Тринадцать прогонов, а не семь, и один из них — блокировка площадкой, сегодня.

Ретро-классификация всей истории после миграции 218:

ban_kind = infra      92 прогона   04.07 → 03.08
ban_kind = platform   23 прогона   30.05 → 06.08

Что было верно, а что нет

Верно: инфраструктурные баны действительно прекратились 03.08 — #2634/#2640 сработали, и это подтверждается тем, что infra ряд обрывается ровно там.

Неверно: обобщение «баны любого вида прекратились». Я взял все banned avito-прогоны одним запросом, увидел, что свежих нет, и распространил вывод на оба вида — то есть сделал ровно ту ошибку, против которой заведена эта задача: посчитал два разных явления одним, потому что они носили одну метку. Различитель, о котором я писал в предыдущем комментарии, работает и в обратную сторону — без него мой собственный вывод оказался смешением.

Почему это меняет вход в #2687

Решение о такте Авито 9-10.08 надо принимать по ряду ban_kind='platform', и этот ряд не обнулился: последняя блокировка площадкой — сегодня. Довод «явления больше не происходит, деградацию можно снимать» относился к infra и на platform не переносится.

Соответственно, довод из моего первого комментария («основание деградации теперь несостоятельно дважды») сохраняет силу только в первой половине: основание было ложным изначально (81% «банов» — наш собственный сайдкар), но утверждать, что описываемого явления больше не происходит, я не могу.

## Поправка к моему замеру выше: «баны прекратились» — неверно Я написал: «Последний "бан" любого вида: **03.08**. С 04.08 — 7 прогонов, все `done`, ни одного `banned`». Это опровергнуто ретро-классификацией, которую сделал PR #2711, и я перепроверил вручную. Все avito-прогоны с 04.08: ``` 3107 avito_newbuilding_sweep done 04.08 02:03 3122 avito_city_sweep done 04.08 06:23 3126 avito_detail_backfill done 04.08 06:53 3177 avito_detail_backfill done 05.08 01:38 3180 avito_newbuilding_sweep done 05.08 02:31 3196 avito_city_sweep done 05.08 06:18 3251 avito_city_sweep_nizhniy_tagil done 06.08 00:28 3253 avito_city_sweep_kamensk_ur. done 06.08 01:10 3267 avito_newbuilding_sweep done 06.08 04:46 3273 avito_city_sweep_pervouralsk banned ← platform | 06.08 05:51 Avito SERP firewall (browser-mode) at page=3 — IP banned 3276 avito_city_sweep done 06.08 06:25 3283 avito_city_sweep_verkh.pyshma done 06.08 07:40 3287 avito_city_sweep_serov done 06.08 08:10 ``` Тринадцать прогонов, а не семь, и **один из них — блокировка площадкой, сегодня**. Ретро-классификация всей истории после миграции 218: ``` ban_kind = infra 92 прогона 04.07 → 03.08 ban_kind = platform 23 прогона 30.05 → 06.08 ``` ## Что было верно, а что нет **Верно:** инфраструктурные баны действительно прекратились 03.08 — #2634/#2640 сработали, и это подтверждается тем, что `infra` ряд обрывается ровно там. **Неверно:** обобщение «баны любого вида прекратились». Я взял все `banned` avito-прогоны одним запросом, увидел, что свежих нет, и распространил вывод на оба вида — то есть **сделал ровно ту ошибку, против которой заведена эта задача**: посчитал два разных явления одним, потому что они носили одну метку. Различитель, о котором я писал в предыдущем комментарии, работает и в обратную сторону — без него мой собственный вывод оказался смешением. ## Почему это меняет вход в #2687 Решение о такте Авито 9-10.08 надо принимать по ряду `ban_kind='platform'`, и **этот ряд не обнулился**: последняя блокировка площадкой — сегодня. Довод «явления больше не происходит, деградацию можно снимать» относился к `infra` и на `platform` не переносится. Соответственно, довод из моего первого комментария («основание деградации теперь несостоятельно дважды») сохраняет силу только в первой половине: основание было ложным изначально (81% «банов» — наш собственный сайдкар), но утверждать, что описываемого явления больше не происходит, я не могу.
Author
Collaborator

ЗАКРЫВАЮ: разведение живёт на проде, чекпоинт у обоих исходов сохранён

Замер на живом проде 2026-08-07 (миграция 218 применена, ретро-классификация отработала):

ban_kind   прогонов   период
infra           92    2026-07-04 → 2026-08-03
platform        24    2026-05-30 → 2026-08-07
unknown          2    2026-08-06

Ваши 92/23 воспроизвелись до единицы; platform стало 24, потому что 06.08 добавилась
блокировка avito_city_sweep_pervouralsk, а 07.08 — avito_detail_backfill.

Раздельно видны все пять задетых заданий, ради чего пункт и заводился:

avito_full_load                infra 22 · platform 6
avito_newbuilding_sweep        infra 23 · platform 1
avito_city_sweep               infra 21 · platform 13
avito_city_sweep_nizhniy_tagil infra 21
avito_full_load_exhaustive     infra  5

То, что просила задача, сделано именно так, как она просила. Разведение — явным полем
причины, а не новым статусом; значение приходит от места порождения отказа (тип исключения),
а не из разбора текста ошибки; status у обоих исходов остаётся banned, то есть
чекпоинт done_buckets сохраняется и при нашем сбое, и при блокировке площадки — побочная
функция, которую вы просили не потерять, не потеряна (runs.py:591-606).

Плюс к вашей постановке добавилось то, чего в ней не было: дефолт стал unknown, а не
platform (#2764 / PR #2765, миграция 234). Логика та же, что у самой задачи, — метка не должна
выглядеть доказательством, не будучи им.

Что не закрывается этой задачей и правильно вынесено в другие: деградация такта из миграции
206 (решение — #2687, данные 09-10.08) и предупреждение «возвращаете такт — возвращайте и окно».

Критерий выполнен числом. Закрываю.

## ЗАКРЫВАЮ: разведение живёт на проде, чекпоинт у обоих исходов сохранён **Замер на живом проде 2026-08-07** (миграция 218 применена, ретро-классификация отработала): ``` ban_kind прогонов период infra 92 2026-07-04 → 2026-08-03 platform 24 2026-05-30 → 2026-08-07 unknown 2 2026-08-06 ``` Ваши 92/23 воспроизвелись до единицы; `platform` стало 24, потому что 06.08 добавилась блокировка `avito_city_sweep_pervouralsk`, а 07.08 — `avito_detail_backfill`. Раздельно видны все пять задетых заданий, ради чего пункт и заводился: ``` avito_full_load infra 22 · platform 6 avito_newbuilding_sweep infra 23 · platform 1 avito_city_sweep infra 21 · platform 13 avito_city_sweep_nizhniy_tagil infra 21 avito_full_load_exhaustive infra 5 ``` **То, что просила задача, сделано именно так, как она просила.** Разведение — явным полем причины, а не новым статусом; значение приходит от места **порождения** отказа (тип исключения), а не из разбора текста ошибки; `status` у обоих исходов остаётся `banned`, то есть **чекпоинт `done_buckets` сохраняется и при нашем сбое, и при блокировке площадки** — побочная функция, которую вы просили не потерять, не потеряна (`runs.py:591-606`). Плюс к вашей постановке добавилось то, чего в ней не было: дефолт стал `unknown`, а не `platform` (#2764 / PR #2765, миграция 234). Логика та же, что у самой задачи, — метка не должна выглядеть доказательством, не будучи им. Что **не** закрывается этой задачей и правильно вынесено в другие: деградация такта из миграции 206 (решение — #2687, данные 09-10.08) и предупреждение «возвращаете такт — возвращайте и окно». Критерий выполнен числом. Закрываю.
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#2686
No description provided.