Долг: постфактум-ревью медиа-самолечения (3c9ce72, влито в main без ревью) #43

Open
opened 2026-07-08 04:26:10 +03:00 by agent_vscode · 0 comments
Collaborator

Контекст

Инцидент: перестали грузиться картинки в ленте (серые заглушки). Первопричина — Kurigram сериализует скачивания через один слот (MAX_CONCURRENT_TRANSMISSIONS=1), а зомби-соединение с медиа-DC (upload.GetFile уходил в вечный таймаут) держало этот слот, блокируя все загрузки. Watchdog не видел проблему, т.к. пробит только главный DC (get_me). Лечилось руками через docker restart.

Фикс 3c9ce72 (fix(media): self-heal zombie media-DC connection so images keep loading) сделан и влит напрямую в main без обычного PR-ревью под давлением прод-аварии. Этот issue — долг на ретроспективное ревью.

Изменённые файлы: telegram_client.py, config.py, api_server.py, dockercompose.yml, tests/mock_config.py, tests/test_media_selfheal.py.

Что сделано

  1. Streak таймаутов → пересоздание соединения (telegram_client.py): N подряд идущих таймаутов скачивания (MEDIA_TIMEOUT_RESTART_THRESHOLD, дефолт 5) → _restart_client() (переиспользован путь watchdog). Успех сбрасывает streak.
  2. max_concurrent_transmissions (config.py + Client, дефолт 3) — чтобы одна зависшая загрузка не была мгновенным полным отказом.
  3. Negative-cache с бэкоффом (api_server.py) — стабильно падающий файл не долбится каждые 60с: пропуск в download_new_files, быстрый 503 Retry-After в get_media, record/clear в _download_deduped._runner и background_download_worker (FloodWait бэкофф не взводит).

На что смотреть при ревью (потенциальные риски)

  • Restart-петля: если Telegram/прокси реально лежат, streak будет упираться в порог и рестартить клиент по кругу. Нужна ли пауза/кулдаун между авто-рестартами? Как это уживается с watchdog-рестартом и flap-детектором?
  • Порог 5 и таймаут 120с: до рестарта может пройти до ~10 мин. Не занизить ли для медиа? Не считать ли таймауты больших видео отдельно (сейчас их спасает только «успех сбрасывает streak»).
  • max_concurrent_transmissions=3: обоснование значения; больше параллельных соединений к медиа-DC — нет ли риска FLOOD_WAIT; согласование с HTTP_DOWNLOAD_SEMAPHORE(3) и BACKGROUND_DOWNLOAD_SEMAPHORE(2).
  • Реальная отмена зависшей загрузки: предполагается, что asyncio.wait_for при таймауте действительно отменяет download_media и освобождает get_file_semaphore Kurigram. Тестом это НЕ покрыто (есть только юниты на streak/бэкофф) — стоит проверить на реальном зависании.
  • _download_failures растёт без вытеснения — теоретически неограниченно (ограничено числом когда-либо падавших файлов; удалённые файлы вычищаются из БД). Нужна ли эвикция по времени/размеру?
  • Бэкофф переживает выздоровление: после успешного авто-рестарта медиа-соединения negative-cache не очищается — восстановившийся файл будет отдавать 503 до истечения своего бэкоффа (до 60с на первый раз). Приемлемо? Не чистить ли кэш при рестарте?
  • Консистентность ключа бэкоффа (str(channel), int(post_id), file_unique_id) во всех точках (get_media / _runner / worker / download_new_files) — перепроверить, что str(channel)==str(channel_id) и post_id везде int.
  • Покрытие тестами: добавлены tests/test_media_selfheal.py (5 тестов) — нет интеграционного сценария «джем → авто-восстановление».

Примечания

Диагностика подтверждена на живом контейнере: после рестарта тот самый «зависший» файл скачался за <2с. Прод расклинен ручным рестартом на момент фикса.

## Контекст Инцидент: перестали грузиться картинки в ленте (серые заглушки). Первопричина — Kurigram сериализует скачивания через один слот (`MAX_CONCURRENT_TRANSMISSIONS=1`), а зомби-соединение с медиа-DC (`upload.GetFile` уходил в вечный таймаут) держало этот слот, блокируя все загрузки. Watchdog не видел проблему, т.к. пробит только главный DC (`get_me`). Лечилось руками через `docker restart`. Фикс `3c9ce72` (`fix(media): self-heal zombie media-DC connection so images keep loading`) сделан и влит **напрямую в `main` без обычного PR-ревью** под давлением прод-аварии. Этот issue — долг на **ретроспективное ревью**. **Изменённые файлы:** `telegram_client.py`, `config.py`, `api_server.py`, `dockercompose.yml`, `tests/mock_config.py`, `tests/test_media_selfheal.py`. ## Что сделано 1. **Streak таймаутов → пересоздание соединения** (`telegram_client.py`): N подряд идущих таймаутов скачивания (`MEDIA_TIMEOUT_RESTART_THRESHOLD`, дефолт 5) → `_restart_client()` (переиспользован путь watchdog). Успех сбрасывает streak. 2. **`max_concurrent_transmissions`** (`config.py` + Client, дефолт 3) — чтобы одна зависшая загрузка не была мгновенным полным отказом. 3. **Negative-cache с бэкоффом** (`api_server.py`) — стабильно падающий файл не долбится каждые 60с: пропуск в `download_new_files`, быстрый 503 `Retry-After` в `get_media`, `record/clear` в `_download_deduped._runner` и `background_download_worker` (FloodWait бэкофф не взводит). ## На что смотреть при ревью (потенциальные риски) - [ ] **Restart-петля:** если Telegram/прокси реально лежат, streak будет упираться в порог и рестартить клиент по кругу. Нужна ли пауза/кулдаун между авто-рестартами? Как это уживается с watchdog-рестартом и flap-детектором? - [ ] **Порог 5 и таймаут 120с:** до рестарта может пройти до ~10 мин. Не занизить ли для медиа? Не считать ли таймауты больших видео отдельно (сейчас их спасает только «успех сбрасывает streak»). - [ ] **`max_concurrent_transmissions=3`:** обоснование значения; больше параллельных соединений к медиа-DC — нет ли риска FLOOD_WAIT; согласование с `HTTP_DOWNLOAD_SEMAPHORE(3)` и `BACKGROUND_DOWNLOAD_SEMAPHORE(2)`. - [ ] **Реальная отмена зависшей загрузки:** предполагается, что `asyncio.wait_for` при таймауте действительно отменяет `download_media` и освобождает `get_file_semaphore` Kurigram. Тестом это НЕ покрыто (есть только юниты на streak/бэкофф) — стоит проверить на реальном зависании. - [ ] **`_download_failures` растёт без вытеснения** — теоретически неограниченно (ограничено числом когда-либо падавших файлов; удалённые файлы вычищаются из БД). Нужна ли эвикция по времени/размеру? - [ ] **Бэкофф переживает выздоровление:** после успешного авто-рестарта медиа-соединения negative-cache не очищается — восстановившийся файл будет отдавать 503 до истечения своего бэкоффа (до 60с на первый раз). Приемлемо? Не чистить ли кэш при рестарте? - [ ] **Консистентность ключа бэкоффа** `(str(channel), int(post_id), file_unique_id)` во всех точках (get_media / _runner / worker / download_new_files) — перепроверить, что `str(channel)==str(channel_id)` и `post_id` везде int. - [ ] **Покрытие тестами:** добавлены `tests/test_media_selfheal.py` (5 тестов) — нет интеграционного сценария «джем → авто-восстановление». ## Примечания Диагностика подтверждена на живом контейнере: после рестарта тот самый «зависший» файл скачался за <2с. Прод расклинен ручным рестартом на момент фикса.
Sign in to join this conversation.