Из таблицы убитых деплоем: yandex_city_sweep 15.08 прожил 65 мин, 12.08 —
2ч33м; оба потеряны целиком — у свипа не было чекпоинтов вовсе.
Единица чекпоинта — combo (сегмент × комнатность × ценовой диапазон), ровно
то, чем цикл обхода уже итерируется. Три слоя:
- провайдер: skip_combos (ни одного HTTP по собранным) + on_combo для каждого
ПРОЙДЕННОГО combo, включая пустые — иначе пустой combo не попадал бы в
чекпоинт и перечитывался бы вечно; оборванный отказом combo (gate failure)
on_combo по-прежнему не вызывает;
- пайплайн: done_combos → heartbeat с done_buckets (мерж jsonb, финализаторы
не затирают); подхват гейтится единственным якорем — combo_label не содержит
якоря, multi-anchor подхват пропускал бы чужие якоря;
- планировщик: resume_run_id=_pick_resume(...) в диспатче (generic-механизм
#2845 — params-идентичность, свежесть точки, потолок цепочки — бесплатно).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Прод-факт: прогон 4707 (23.08) подхватил 42 корзины у 4117, был убит деплоем
на 26-й минуте до завершения первой НОВОЙ корзины — и не успел ни разу
написать heartbeat с done_buckets. Его собственный чекпоинт пуст: следующий
кандидат увидел бы no_checkpoint, цепочка оборвалась бы с потерей 42 корзин.
_resume_decision при вердикте 'ok' теперь кладёт done_buckets предшественника
в counters-заготовку нового прогона — она персистится при claim, до старта
пайплайна. Heartbeat мержит jsonb: первый настоящий bucket-heartbeat
перезапишет ключ надмножеством, двойной записи нет. Отказные вердикты
чекпоинт не наследуют (прогон с нуля не должен врать о собранном).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>