Перейти к содержанию
Innovialabs

Как масштабировать Telegram-бота под высокую нагрузку

Продвинутое3 мин

Коротко

Принимайте апдейты через webhook и сразу кладите их в очередь, обрабатывайте stateless-воркерами с состоянием в Redis, защищайтесь от повторов по update_id, а исходящие сообщения пропускайте через общий rate limiter.

Архитектура в одном абзаце

Бот под нагрузкой — это не один процесс, а конвейер:

  1. Webhook-приёмник получает апдейт, проверяет секрет и кладёт его в очередь. Ответ Telegram — сразу.
  2. Очередь задач (Redis Streams, RabbitMQ, Kafka, Celery, arq и т. п.) буферизует пики.
  3. Воркеры без собственного состояния обрабатывают апдейты. Их можно запускать столько, сколько нужно.
  4. Redis хранит FSM-состояния, кеш, блокировки и счётчики лимитов.
  5. Отправщик выпускает исходящие сообщения с учётом лимитов Telegram.

Почему webhook, а не long polling

Long polling (getUpdates) нельзя горизонтально масштабировать: одновременно забирать апдейты может только один потребитель, второй получит ошибку конфликта. Для высокой нагрузки нужен webhook:

  • Telegram сам присылает апдейты на HTTPS-адрес;
  • приёмников можно поставить несколько за балансировщиком;
  • параметр max_connections в setWebhook ограничивает число параллельных соединений от Telegram;
  • secret_token в setWebhook позволяет проверять заголовок X-Telegram-Bot-Api-Secret-Token и отсекать чужие запросы.

Приёмник должен отвечать быстро. Если обработка идёт прямо в запросе и тормозит, Telegram копит очередь и повторяет доставку, а пользователи видят задержки.

Порядок апдейтов внутри чата

Параллельная обработка ломает порядок: второе сообщение пользователя может обработаться раньше первого, и FSM уйдёт не в то состояние.

Решения:

  • партиционирование по chat_id: апдейты одного чата всегда попадают в одну партицию или очередь и обрабатываются последовательно;
  • блокировка на чат в Redis на время обработки;
  • разные чаты при этом обрабатываются параллельно.

Идемпотентность

Апдейт может прийти дважды: Telegram повторяет доставку при ошибке или таймауте, очередь повторяет задачу после падения воркера. Защита:

# Помечаем update_id как обработанный; ключ живёт сутки
is_new = await redis.set(f"upd:{update.update_id}", 1, nx=True, ex=86400)
if not is_new:
    return  # дубль — пропускаем

Но этого мало, если воркер упал посреди обработки. Поэтому побочные эффекты тоже делайте идемпотентными:

  • создание заказа — с уникальным ключом (update_id или callback_query.id);
  • списание баланса — в транзакции с проверкой, что операция ещё не проведена;
  • отправка сообщения — с записью «уже отправлено» перед повторной попыткой.

Состояние в Redis

Если FSM хранится в памяти процесса, второй воркер о нём не знает. Выносите всё состояние наружу:

  • FSM и данные диалога — в Redis (в aiogram это RedisStorage), с TTL для брошенных диалогов;
  • долговременные данные (пользователи, заказы) — в основной БД;
  • кеш справочников — в Redis, чтобы не дёргать БД на каждое сообщение.

Тогда любой воркер может обработать любой апдейт, а перезапуск ничего не теряет.

Горизонтальное масштабирование

  • Приёмники и воркеры — stateless-контейнеры, масштабируются по длине очереди или CPU.
  • Отдельные очереди для разных классов задач: интерактив (ответы пользователям) и фон (рассылки, отчёты, обработка файлов). Рассылка не должна тормозить ответы.
  • Тяжёлые операции (генерация PDF, вызовы ИИ, обработка медиа) — в отдельный пул воркеров с таймаутами.
  • База данных часто становится узким местом раньше бота: пул соединений, индексы, реплики для чтения.

Лимиты Telegram на исходящие сообщения

Telegram ограничивает частоту отправки. Ориентиры из официального FAQ: не больше одного сообщения в секунду в один чат, не больше 20 сообщений в минуту в группу и порядка 30 сообщений в секунду при массовых рассылках. Лимиты могут меняться, поэтому сверяйтесь с документацией.

Что делать на практике:

  • общий rate limiter в Redis (token bucket) — локальный счётчик в каждом воркере не работает, когда воркеров много;
  • лимит на чат и глобальный одновременно;
  • при ответе 429 читайте retry_after и откладывайте задачу, а не повторяйте сразу;
  • рассылки ведите по очереди с прогрессом, чтобы после сбоя продолжить, а не начать заново;
  • для очень больших рассылок у Telegram есть платный режим allow_paid_broadcast — с оплатой в Stars.

Наблюдаемость

  • getWebhookInfo: pending_update_count и last_error_message — первое, что смотреть при задержках.
  • Метрики: длина очереди, время обработки, доля ошибок, число ответов 429.
  • Логи с update_id и chat_id для трассировки одного апдейта через всю систему.

Частые вопросы

Когда обработка апдейта занимает заметное время, появляются внешние вызовы (оплата, ИИ, CRM) или нужны рассылки. Если бот простой и отвечает мгновенно, достаточно webhook и нескольких процессов.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.