feat(telegram): calibrate the gate's history category from production logs (#1418) - #1441
Conversation
… logs (#1418) The released telethon-floodgate 0.1.0 default (600/min) never bound: the production app.log shows 209 FLOOD_WAITs on messages.getHistory with collector peaks of 115 channel fetches per minute, far below that guard. Pass the empirically measured boundary (30 req/~30s, the 31st returned FLOOD_WAIT_3, minus a 20% margin) as a category_limits override when the pool constructs its gate. Categories with no flood signal in the logs (send, admin_action, channel_lifecycle, default) keep the package defaults and their needs-calibration note. Regression tests: the pool ships the calibrated spec, and a peak burst defers the 25th history call inside the 30s window (fake clock). Part of #1331 Closes #1418 Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
#1418: калибровка rate_limit_gate по прод-логам — отчёт (Phase 2 эпика #1331)Дата: 2026-09-24. Источник данных: 1. МетодикаСкрипт
2. Что показал лог2.1 Распределение по операциям (2666 спаренных)
2.2 Длительности
2.3 Динамика (Phase 1 работает)
Аккаунты: 2.4 Нагрузка коллектора (Collecting-строк на аккаунт-минуту)
Перед 2.5 Спецвопрос issue №4 (history 600/мин) — ответЛимит не «слишком высок», он не срабатывал вовсе: максимум наблюдаемой 2.6 Спецвопрос issue №5 (warm_dialog_cache под гейтом?) — ответВсе вызовы 3. Калибровочная таблицаПрод-пакет = PyPI
Дифф (минимальный): 4. Координация с пакетом (важно)В локальной копии 5. Пропускная способность сбора (критерий AC)Живой замер до/после невозможен (запрещены живые вызовы) — оценка по логу:
6. Связка с #1419 (рестарты)Замер #1419 дополнительно показывает: 44 % стартов ловят флуд в первые 60 с 7. Проверки
8. Риски / follow-ups
|
axisrow
left a comment
There was a problem hiding this comment.
Проверил дифф против origin/main (2 коммита: калибровка + перенос отчёта из репо). Что сверено: (1) семантика category_limits в telethon-floodgate 0.1.0 — specs.update мержит частичный словарь с дефолтами, переопределена только history, send (30/60) и остальные остаются пакетными (сверено с исходником установленного 0.1.0); (2) пин telethon-floodgate>=0.1.0,<0.2 совместим — RateLimitSpec и category_limits есть уже в 0.1.0, форма spec идентична (jitter_sec=0.0 у обоих), дрейфа нет; (3) единственная прод-точка конструирования гейта — client_pool.py:199, обходных путей нет; (4) tests/test_rate_limit_gate.py — 13 passed на фейковых часах, детерминированно. ВЕРДИКТ: ГОТОВО К МЕРЖУ (approve), блокеров нет. Одна содержательная находка вне диффа, не блокирующая (см. инлайн): калибровка впервые делает путь дефера живым в проде, но главный путь сбора не обрабатывает TelegramRateLimitedError — рекомендую ветку reschedule по образцу UsernameResolveRateLimitedError следом или в этот PR. Мелочь к описанию: «медианные активные минуты (26-50/мин) ниже 48/мин» — верх границы 50 кэп 48/мин превышает, деферы будут и в рутинной работе.
#1441) With history calibrated to 24/30s (#1418) the gate legitimately binds on peak collector minutes (median 50 fetches/min vs the 48/min cap), so TelegramRateLimitedError now reaches the queue's main collection path. Handle it like the neighbouring UsernameResolveRateLimitedError branch: reschedule with run_after = now + retry_after + buffer and a pending note, instead of FAILED + logger.exception on every peak. Co-Authored-By: Claude Code <noreply@anthropic.com>
🔍 Local review (cycle 1) — round eb580d1d-9a90-4cb1-9607-d15531152dbfReviewed locally (
Фокус-проверки цикла (все пройдены, FIX'ов нет):
CI зелёный на head |
axisrow
left a comment
There was a problem hiding this comment.
Ревью нового head 666c23b (дельта с прошлого ревью: один коммит fix(queue)). Прошлая находка закрыта корректно: (1) порядок веток в isinstance-цепочке верный — UsernameResolve-ветки раньше, новая ветка до NoActiveCollectionClientsError/ConnectionError; конфликтов иерархии нет (обе resolve-ошибки — прямые RuntimeError, TelegramRateLimitedError им не родня); (2) TelegramPeerRateLimitedError (подкласс, per-peer send) поглощается новой веткой — соответствует докстрингу пакета «every existing TelegramRateLimitedError handler absorbs it unchanged», семантика «операция недоступна сейчас» сохраняется; (3) reschedule_collection_task сбрасывает error/started_at/completed_at и ставит PENDING — ассерты теста валидны и реально отловят регрессию (без ветки статус был бы FAILED + error заполнен); (4) тест зеркалит существующий resolve-тест из того же файла (нижняя граница run_after без верхней — флейка не будет), TelegramRateLimitedError(+7001, history, 17.0) + буфер 5с = порог 22с — сходится. ВЕРДИКТ: ГОТОВ К МЕРЖУ (approve). Остаток прошлой находки — не блокирует: scheduler-путь collect_all_channels по-прежнему пишет gate-дефер в stats[errors] через generic except (collection.py:380) — цикл продолжается, данных потерь нет, добирается следующим проходом; только наблюдаемость. Можно follow-up: stats[deferred] += 1 по образцу UsernameResolveRateLimitedError рядом (371-379).
Что
Калибровка Phase 2 (эпик #1331) по прод-логам: пул передаёт гейту
category_limits={"history": 24/30s}вместо дефолта пакета 0.1.0 (600/мин),который никогда не срабатывал. Остальные категории — данных в логах нет,
остаются без изменений с пометкой «требует калибровки».
Данные (data/app.log, 2026-06-12 … 2026-09-01)
Полный разбор с методикой:
tcf-1418-calib-report.md— приложен первым комментарием PR (в репо не хранится).stream_messages(категория history)warm_dialog_cache(dialogs 1/60)Аудит AC «warm_dialog_cache под гейтом на всех путях»: все 10 call-сайтов
src/идут через единственный transport-методbackends.py:402; составныетеги (
collect_channel_warm_dialog_cache, …) покрываются суффиксным правиломendswith— регресс класса #1336 закрыт в пакете.Калибровочная таблица
Координация с пакетом
В локальной копии
telethon-floodgateуже есть uncommittedHISTORY_SPEC = 24/30(не выпущено). Прод с PyPI 0.1.0 живёт с 600/60 — оверрайд в донорезакрывает прод сразу; после релиза 0.1.1 оверрайд снимается (записано в
комментарии константы).
Пропускная способность (AC)
Живой замер невозможен (без живых Telegram-вызовов) — оценка по логу:
медианные активные минуты (26–50/мин) ниже 48/мин — без изменений; пики
деферятся, каналы не теряются (инкрементальный min_id добирает следующим
проходом); дефер warm-prefetch сбор канала не останавливает (
collection.py:491).Тесты
tests/test_rate_limit_gate.py: +2 (пул применяет спеку; 25-й вызовв 30-с окне деферится, фейковые часы) — 13 passed.
ruff checkчисто; полный сюит (parallel + serial) — зелёный, см. CI.ловят флуд в первые 60 с), но персистентность breaker-состояний —
отдельное согласование.
Part of #1331
Closes #1418
🤖 Generated with Claude Code