tradein: сторож «ноль результатов» структурно слеп к backfill-задачам — читает поле, которого в их счётчиках нет, поэтому 78 пустых прогонов из 158 … #2703

Closed
opened 2026-08-06 06:22:23 +00:00 by bot-backend · 1 comment
Collaborator

Найдено при разборе domclick_detail_backfill (эпик #2674, PR #2695). Объясняет, почему 30 подряд пустых прогонов никто не заметил — и почему это повторится на других задачах.

Два сторожа, и оба слепы именно к backfill'ам

_alert_if_consecutive_failures считает подряд идущие failed/banned. До PR #2695 пустые backfill-прогоны назывались done, поэтому стрик не набирался никогда. Это половина починена.

_alert_if_consecutive_zero_results (#2625) смотрит total_seen. У backfill'ов в счётчиках такого поля нет вообще — они пишут attempted/enriched/blocked/failed. Колонка читается как 0 всегда, в том числе у полностью успешного прогона. Стрик «нулевых результатов» поэтому не прерывается никогда, а анти-спам, срабатывающий один раз на стрик, замолкает после первого события — навсегда.

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

Масштаб

Прогонов вида «попытки были, результата ноль» — 78 из 158 по трём backfill-задачам:

source таких самое заметное
domclick_detail_backfill 24/30 494 попытки → 0
avito_detail_backfill 23/76 5 прогонов по 1500–1600 попыток с нулём обогащений
yandex_detail_backfill 31/52 все ровно attempted=5 failed=5

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

Что нужно

  1. Свести счётчики backfill'ов к тому, что читает сторож (total_seenattempted), либо научить сторож их словарю. Первое проще, второе честнее — решать по тому, сколько ещё задач имеют собственный словарь счётчиков.
  2. Проверить остальные задачи на тот же разрыв: сторож молча читает 0 у любой задачи, чьи счётчики он не знает. Сейчас неизвестно, сколько их.
  3. Анти-спам «один раз на стрик» безопасен только если стрик может прерваться. Там, где условие прерывания недостижимо, он превращается в «один раз навсегда» — это стоит сделать невозможным по построению, а не полагаться на корректность счётчиков.

Отдельно: сама доставка уведомлений не настроена (#2673) — за всю историю продукта наружу не ушло ни одного. Даже починенный сторож будет срабатывать в пустоту, пока это так.

Связано: #2674, #2673, #2625, #2695, #2670.

Найдено при разборе `domclick_detail_backfill` (эпик #2674, PR #2695). Объясняет, **почему** 30 подряд пустых прогонов никто не заметил — и почему это повторится на других задачах. ## Два сторожа, и оба слепы именно к backfill'ам **`_alert_if_consecutive_failures`** считает подряд идущие `failed`/`banned`. До PR #2695 пустые backfill-прогоны назывались `done`, поэтому стрик не набирался никогда. Это половина починена. **`_alert_if_consecutive_zero_results`** (#2625) смотрит `total_seen`. У backfill'ов в счётчиках такого поля **нет вообще** — они пишут `attempted`/`enriched`/`blocked`/`failed`. Колонка читается как 0 **всегда**, в том числе у полностью успешного прогона. Стрик «нулевых результатов» поэтому не прерывается никогда, а анти-спам, срабатывающий один раз на стрик, замолкает после первого события — навсегда. То есть второй сторож не просто не сработал: он структурно не может сработать по этим задачам, и при этом выглядит настроенным. ## Масштаб Прогонов вида «попытки были, результата ноль» — **78 из 158** по трём backfill-задачам: | source | таких | самое заметное | |---|---:|---| | `domclick_detail_backfill` | 24/30 | 494 попытки → 0 | | `avito_detail_backfill` | 23/76 | **5 прогонов по 1500–1600 попыток** с нулём обогащений | | `yandex_detail_backfill` | 31/52 | все ровно `attempted=5 failed=5` | Полторы тысячи попыток и ноль результата — это уже не «тихий сбой», это заметная работа, потраченная впустую и не отражённая нигде. ## Что нужно 1. Свести счётчики backfill'ов к тому, что читает сторож (`total_seen` ↔ `attempted`), **либо** научить сторож их словарю. Первое проще, второе честнее — решать по тому, сколько ещё задач имеют собственный словарь счётчиков. 2. Проверить остальные задачи на тот же разрыв: сторож молча читает 0 у любой задачи, чьи счётчики он не знает. Сейчас неизвестно, сколько их. 3. Анти-спам «один раз на стрик» безопасен только если стрик может прерваться. Там, где условие прерывания недостижимо, он превращается в «один раз навсегда» — это стоит сделать невозможным по построению, а не полагаться на корректность счётчиков. Отдельно: **сама доставка уведомлений не настроена** (#2673) — за всю историю продукта наружу не ушло ни одного. Даже починенный сторож будет срабатывать в пустоту, пока это так. Связано: #2674, #2673, #2625, #2695, #2670.
Author
Collaborator

ЗАКРЫВАЮ: слепая зона стала выразимой, пустые backfill'ы ловятся — 4 из 4 против 0 из 78

Правка пошла третьим путём, не тем, что предлагала задача, — и это стоит записать явно.

Что сделано вместо «свести счётчики к total_seen». Сторож перестал читать колонку и
читает counters, а отсутствие результатного ключа теперь означает «не измерено» (None),
а не ноль
— и такой прогон прерывает стрик. Ложный вечный стрик стал невозможен по
построению, а не по совпадению чисел. Словарь при этом всё же расширен на lots_fetched и
unique_fetched (последний закрыл full-load'ы, которых сторож не видел вовсе).

п.2 задачи («проверить остальные задачи на тот же разрыв») — выполнен с числами, они
зафиксированы прямо в коде: у 28 источников из 53 (2 650 прогонов) общего результатного
ключа нет вовсе, у refresh_search_matview counters пусты буквально во всех 55 строках,
а у deactivate_stale_* ноль — здоровый ответ. Поэтому сведение к одному ключу и было
отвергнуто: оно превратило бы здоровый ноль в аварию. Слепая зона теперь логируется явно
(_warn_source_has_no_result_metric), а не выглядит настроенным сторожем.

п.3 («анти-спам безопасен только если стрик может прерваться») — закрыт дважды: прерыванием
по «не измерено» здесь и разреженной лестницей + STREAK_SCAN_LIMIT = 500 в #2670.

Масштаб задачи ловится — проверено на проде. «78 пустых прогонов из 158» ловил бы не этот
сторож, а сторож подряд-идущих неудач, и для этого пустые backfill'ы должны перестать называться
done. Замер после правок:

прогон                              счётчики                     статус
3368 avito_detail_backfill    attempted 5 · enriched 0      banned
3313 domclick_detail_backfill attempted 3 · enriched 0      banned
3306 avito_detail_backfill    attempted 5 · enriched 0      banned
3300 yandex_detail_backfill   attempted 5 · enriched 0      failed
3342 yandex_detail_backfill   attempted 495 · enriched 492  done   ← встречная проверка

4 нулевых прогона из 4 — не done. До правок таких было 78 из 158, и все 78 назывались
done. Пятая строка — анти-оверрич: успешный backfill остался done, сторож не стал ловить
здоровое.

Остаточное, чтобы не пряталось: колонка scrape_runs.total_seen у backfill'ов по-прежнему
остаётся 0 (сторож её больше не читает, но админские витрины читают). Это не слепота сторожа,
про которую задача, но читатель колонки увидит ложный ноль у здорового прогона.

И главное, что не чинится здесь: доставка. Правил оповещения 0, получателей 0, отправлено 0
(#2673) — починенный сторож срабатывает в пустоту.

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

## ЗАКРЫВАЮ: слепая зона стала выразимой, пустые backfill'ы ловятся — 4 из 4 против 0 из 78 Правка пошла третьим путём, не тем, что предлагала задача, — и это стоит записать явно. **Что сделано вместо «свести счётчики к `total_seen`».** Сторож перестал читать колонку и читает `counters`, а **отсутствие результатного ключа теперь означает «не измерено» (None), а не ноль** — и такой прогон **прерывает стрик**. Ложный вечный стрик стал невозможен по построению, а не по совпадению чисел. Словарь при этом всё же расширен на `lots_fetched` и `unique_fetched` (последний закрыл full-load'ы, которых сторож не видел вовсе). **п.2 задачи («проверить остальные задачи на тот же разрыв») — выполнен с числами**, они зафиксированы прямо в коде: у **28 источников из 53 (2 650 прогонов)** общего результатного ключа нет вовсе, у `refresh_search_matview` `counters` пусты буквально во всех 55 строках, а у `deactivate_stale_*` ноль — **здоровый** ответ. Поэтому сведение к одному ключу и было отвергнуто: оно превратило бы здоровый ноль в аварию. Слепая зона теперь логируется явно (`_warn_source_has_no_result_metric`), а не выглядит настроенным сторожем. **п.3 («анти-спам безопасен только если стрик может прерваться») — закрыт дважды:** прерыванием по «не измерено» здесь и разреженной лестницей + `STREAK_SCAN_LIMIT = 500` в #2670. **Масштаб задачи ловится — проверено на проде.** «78 пустых прогонов из 158» ловил бы не этот сторож, а сторож подряд-идущих неудач, и для этого пустые backfill'ы должны перестать называться `done`. Замер после правок: ``` прогон счётчики статус 3368 avito_detail_backfill attempted 5 · enriched 0 banned 3313 domclick_detail_backfill attempted 3 · enriched 0 banned 3306 avito_detail_backfill attempted 5 · enriched 0 banned 3300 yandex_detail_backfill attempted 5 · enriched 0 failed 3342 yandex_detail_backfill attempted 495 · enriched 492 done ← встречная проверка ``` **4 нулевых прогона из 4 — не `done`.** До правок таких было 78 из 158, и все 78 назывались `done`. Пятая строка — анти-оверрич: успешный backfill остался `done`, сторож не стал ловить здоровое. **Остаточное, чтобы не пряталось:** колонка `scrape_runs.total_seen` у backfill'ов по-прежнему остаётся `0` (сторож её больше не читает, но админские витрины читают). Это не слепота сторожа, про которую задача, но читатель колонки увидит ложный ноль у здорового прогона. **И главное, что не чинится здесь:** доставка. Правил оповещения 0, получателей 0, отправлено 0 (#2673) — починенный сторож срабатывает в пустоту. Критерий выполнен числом. Закрываю.
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#2703
No description provided.