Как масштабировать Telegram-бота под высокую нагрузку
Продвинутое3 минРедакция Innovialabs
Коротко
Принимайте апдейты через webhook и сразу кладите их в очередь, обрабатывайте stateless-воркерами с состоянием в Redis, защищайтесь от повторов по update_id, а исходящие сообщения пропускайте через общий rate limiter.
Архитектура в одном абзаце
Бот под нагрузкой — это не один процесс, а конвейер:
- Webhook-приёмник получает апдейт, проверяет секрет и кладёт его в очередь. Ответ Telegram — сразу.
- Очередь задач (Redis Streams, RabbitMQ, Kafka, Celery, arq и т. п.) буферизует пики.
- Воркеры без собственного состояния обрабатывают апдейты. Их можно запускать столько, сколько нужно.
- Redis хранит FSM-состояния, кеш, блокировки и счётчики лимитов.
- Отправщик выпускает исходящие сообщения с учётом лимитов 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для трассировки одного апдейта через всю систему.