Files
jellybit/tasks/items/background-error-noise.md
T
av c5d62d76ee docs: канон поднят с версии 7 до 12
- каталог задач переехал в tasks/ в корне, спринт упразднён — приоритет
  теперь порядок строк в BACKLOG.md, четыре задачи набора вернулись в беклог
- гейт: путь docs.py переведён на av-dev-docs вместо снесённого av-dev-pm,
  добавлены шаги tasks.py check и openspec.py check
- относительные ссылки внутри задач и ссылки из docs/ на задачи починены
2026-08-09 19:09:53 +03:00

7.5 KiB

🐞 Тормозить опрос qBittorrent бэкоффом при недоступности и эскалировать устойчивый сбой

  • Тип: fix
  • Категория: Инфраструктура
  • Зачем: недоступный qBittorrent опрашивается каждые 5 с и даёт WARN на каждом тике: нужен экспоненциальный бэкофф до минутного потолка со сбросом по первому успеху и ERROR на устойчивой деградации

Остаток от задачи «классификация доменных ошибок + конвенции логирования» (основное реализовано, см. ниже) плюс бэкофф опроса, заказанный 2026-08-06. Речь о поведении фонового цикла, пока зависимость лежит: с какой частотой он её дёргает и каким уровнем об этом пишет.

Что уже сделано (не переоткрывать)

Коммит f8fb4fa (Tier A) + коммит этой задачи закрыли:

  • Классификация доменных ошибок: sentinel worker.ErrInvalidInput→400; обёртки ErrConflict в Cancel/Retry/Defer/Undo; layout.ErrCollision→409 в classifyErr и ветка в tgbot; logCmd относит новые классы в DEBUG.
  • Конвенции: logging.md — команды воркера = доменная граница, таблица уровней доменных отказов (граница команды vs асинхронная стадия), правило про *url.Error/секреты в URL, канон категории state transition (унифицированы cancel/retry/relink/recovery). errors.md — таблица маппинга ошибка→статус, развилка «транзиентный ответ vs персистентная диагностика» решена как (а): error_msg/reasons — операторская поверхность владельца (сырой текст ок, секреты запрещены; аудит показал, что секреты туда не текут).
  • Мелочи: reason-коды const-блок; лог-поля iddownload_id; preview WARN; комментарий у parseIgnored.

Остаток

Шум ext.* ERROR решено оставить как есть (2026-08-06). Запись «зависимость недоступна» на каждом тике — легитимный сигнал транспортного слоя, и гасится он уровнем сбора логов, а не кодом. Варианты с пониженным уровнем у logging.ExtCall и с дедупом отклонены: первый заводит второе правило уровня для того же класса вызовов, второй даёт транспортному логгеру память о состоянии.

Остаются две вещи, и обе стоят на одном счётчике подряд-идущих сбоев тика.

Бэкофф опроса (решение 2026-08-06). Пока qBittorrent недоступен, цикл продолжает дёргать его каждые poll_interval (5 с) — недоступную зависимость незачем опрашивать с рабочей частотой. Интервал растёт экспоненциально от poll_interval до потолка порядка минуты; первый успешный ответ возвращает рабочий интервал сразу, без ступенчатого спуска. Бэкофф заодно снимает и остроту шума: записей становится столько же на событие, но событий — единицы в минуту.

Эскалация уровня. Сейчас сбой тика — всегда WARN, сколько бы тиков подряд он ни падал. docs/conventions/logging.md требует иного: устойчивый сбой N тиков подряд — это реальная деградация, и она пишется ERROR.

Воспроизведение

  1. Остановить qBittorrent (локально, не на umbar).
  2. Смотреть лог воркера в течение нескольких минут поллинга.
  3. Наблюдается: запрос к qBittorrent уходит каждые 5 секунд всё время недоступности, а доменная запись poll failed идёт WARN на каждом тике и остаётся WARN бесконечно.
  4. Ожидается: интервал опроса растёт до минутного потолка, а после N подряд-идущих неудачных тиков уровень поднимается до ERROR — деградация отличается от разового промаха.
  5. Поднять qBittorrent обратно: опрос возвращается к poll_interval с первого успешного ответа.

Затрагивает

  • цикл поллинга воркера (internal/worker) — счётчик подряд-идущих сбоев, текущий интервал тика и его сброс по успеху;
  • секция [worker] конфига и config.example.toml — потолок бэкоффа и порог эскалации;
  • docs/database.md, таблица «Настройки с числовым значением» — дом обоих чисел;
  • docs/architecture.md, «Характер потока» — там сказано, что фон непрерывный с периодом поллинга; переменный интервал это уточняет;
  • docs/conventions/logging.md — правило эскалации уже записано, меняться не должно; задача приводит код к нему.

Критерии приёмки

  • При подряд-идущих сбоях интервал опроса растёт экспоненциально от poll_interval и упирается в потолок из конфига, дальше не растёт (оракул: тест цикла с подставным клиентом и управляемыми часами — проверяет последовательность интервалов).
  • Первый успешный ответ возвращает poll_interval немедленно (оракул: тот же тест, сценарий «серия сбоев, успех, сбой» — после успеха интервал рабочий).
  • Сбой тика ниже порога пишется WARN, начиная с N-го подряд — ERROR, а успешный тик сбрасывает счётчик (оракул: тест, считающий уровни записей на сценарии «сбой, сбой, успех, сбой»).
  • Потолок бэкоффа и порог эскалации читаются из конфига и описаны в config.example.toml с единицами и диапазоном (оракул: task gate, шаг канона — сверка с database.md).