Skip to content

investigation: теряется ли проактивная защита от флуда при рестарте воркера — замер до фикса #1419

Description

@axisrow

Наблюдение

Реактивное состояние флуда персистентно: accounts.flood_wait_until
(src/database/schema.py:9), пишется в pool_flood.py:241-246, читается
на каждом выборе аккаунта (account_lease_pool.py:188-191). Переживает рестарт.

Проактивное состояние — только в памяти:

Механизм Хранилище Файл
TelegramRateLimitGate self._limiters — обычный dict rate_limit_gate.py:141
FloodCircuitBreaker self._breakers — обычный dict flood_breaker.py:124
ResolveRateLimiter deque в памяти rate_limiter.py:66

Рестарт воркера обнуляет все три. Формально это означает, что после перезапуска
процесс стартует с чистым окном и может немедленно выдать полный залп, а автостоп
(3 флуда → пауза 300 с) забывает уже накопленные срабатывания.

Грепом подтверждено: вопрос перезапуска в этих файлах вообще не обсуждается
ни слова о restart/персистентности в комментариях. То есть это не осознанный
компромисс с обоснованием, а слепое пятно.

⚠️ Сначала замер, только потом фикс

Это issue НЕ на реализацию персистентности. Сначала надо доказать по данным,
что проблема реальна, иначе это фикс из головы — ровно то, чего проект избегает
(принцип: калибровать замером, а не догадкой).

Что померить по data/app.log и логам рестартов:

  1. Как часто воркер вообще перезапускается в реальной эксплуатации.
    Если раз в неделю — проблема умозрительная, и issue закрывается как
    «не подтвердилось».
  2. Случались ли флуды в первые секунды после старта — то есть реализовался
    ли сценарий «забыли окно → сразу залп». Искать корреляцию отметок старта
    процесса и всплесков Flood wait.
  3. Терялось ли состояние автостопа в момент, когда оно было нужно — то есть
    был ли рестарт между накопленными флудами на одной паре (операция, телефон).
    Это наиболее опасный сценарий: автостоп существует именно против серии,
    приведшей к бану на 14.8 ч (flood_breaker.py:10-14 — 85 флудов за сутки).

Если замер подтвердит — тогда решать как чинить

Варианты (не выбирать заранее):

  • Персистить только состояние автостопа (самое ценное, самое редко меняющееся),
    оставив окна лимитера в памяти.
  • Персистить всё через существующий механизм настроек — прецедент есть:
    resolve-backoff уже персистится в settings JSON (resolve_guard.py:255-300),
    включая миграцию старого формата.
  • Ничего не персистить, но при старте применять «прогревочную» паузу.

⚠️ PyrateLimiter с его SQLiteBucket не подходит: синхронный, ведёт свой
sqlite3.Connection мимо Database._write_lock (правило проекта из #569
все записи только через db.transaction() / db.execute_write()).

Acceptance Criteria

  • Замер выполнен и приложен: частота рестартов, наличие/отсутствие корреляции
    «старт → всплеск флудов», случаи потери состояния автостопа.
  • Явный вывод: проблема подтверждена или опровергнута. Опровержение —
    полноценный результат, issue закрывается без кода.
  • Если подтверждена — отдельное решение о способе, согласованное с владельцем
    (правило epic: замена самописного кода на battle-tested библиотеки (реестр находок + решений) #782: замена/подключение обсуждается заранее).
  • Никакой реализации до вывода замера.

Файлы

  • src/telegram/rate_limit_gate.py:141_limiters
  • src/telegram/flood_breaker.py:124_breakers, :220reset
  • src/telegram/rate_limiter.py:66ResolveRateLimiter
  • src/telegram/resolve_guard.py:255-300 — прецедент персистентности через settings
  • data/app.log, логи рестартов воркера

Контекст

Часть Phase 2 эпика #1331. Обнаружено при редизайне #1416.
Отдельно отмечено исследователем: чинить только после замера, иначе фикс из головы.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions