Предел одного Telegram-бота: сколько сообщений в секунду до первых ошибок 429
Бот работал нормально, пока аудитория не выросла до нескольких тысяч подписчиков и не понадобилась массовая рассылка. Первый же прогон «отправить всем» кладёт цикл на землю: часть сообщений уходит, часть возвращается с ошибкой 429 Too Many Requests, часть теряется, если исключение не поймано. Проблема не в вашем коде как таковом и не в хостинге — Telegram Bot API просто ограничивает скорость приёма сообщений, и любой бот, который отправляет быстрее этого предела, рано или поздно в него упрётся. Разберём, где именно проходит граница, что означает ответ 429 и как построить очередь отправки, которая не роняет рассылку и не требует героических костылей в проде.
Содержание
Какие лимиты у Telegram Bot API на самом деле
Telegram не публикует эти цифры как формальный контракт API с гарантией на все времена — это скорее общеизвестные ориентиры, которые сообщество ботоводов подтверждает годами практики и которые встречаются в официальной документации как рекомендации. Держите в уме, что это именно ориентиры, а не жёстко задокументированная спецификация, и что на конкретном аккаунте бота цифры могут отличаться:
- около 30 сообщений в секунду — суммарный поток бота в разные чаты (broadcast-лимит);
- около 1 сообщения в секунду в один и тот же чат или группу — если долбить одного пользователя чаще, начнутся 429;
- для групп и супергрупп порог ощутимо ниже, чем для личных чатов — на практике безопаснее ориентироваться на единицы сообщений в минуту в одну группу, если это не критично важное уведомление;
- лимиты считаются как минимум на уровне одного бота (по токену), поэтому несколько независимых ботов с разными токенами имеют независимые бюджеты — это отдельная тема, разобранная в статье про архитектуру нескольких ботов на одном сервере.
Важный нюанс: лимит «в разные чаты» и лимит «в один чат» — это два разных счётчика, и превысить можно любой из них независимо. Бот, рассылающий 20 сообщений в секунду в 20 разных личных чатов, скорее всего не получит 429 по общему лимиту. Тот же бот, пытающийся отправить 20 сообщений в секунду в одну группу поддержки, получит 429 почти сразу — там сработает лимит на чат, а не общий.
Отдельно стоят медиа-группы (альбомы) и редактирование сообщений — у них исторически более строгие лимиты, чем у обычного sendMessage, единый бюджет на все типы запросов закладывать не стоит.
Что означает ошибка 429 и поле retry_after
Когда бот превышает лимит, Telegram Bot API не отклоняет запрос молча и не банит бота — он возвращает HTTP-ответ с кодом 429 Too Many Requests и телом вида:
{
"ok": false,
"error_code": 429,
"description": "Too Many Requests: retry after 3",
"parameters": {
"retry_after": 3
}
}
Поле retry_after — это число секунд, через которое сервер рекомендует повторить попытку. Это не оценка «примерно скоро», а конкретное значение, посчитанное сервером под конкретный чат или под конкретного бота в данный момент — и именно на него нужно ориентироваться, а не на собственный фиксированный таймаут.
Ключевая ошибка многих реализаций — воспринимать 429 как обычную сетевую ошибку и обрабатывать её общим try/except с логированием и пропуском сообщения. Это не так: 429 — не сбой доставки, а сигнал «подождите и повторите», и сообщение при правильной обработке всё равно должно быть доставлено, просто с задержкой. Если игнорировать retry_after и повторять запрос сразу — велик шанс получить следующий 429 с ещё большим значением задержки, потому что бот продолжает давить на тот же лимит.
Второй частый источник путаницы — смешивание 429 с другими кодами ошибок. 403 Forbidden (бот заблокирован пользователем или кикнут из группы) и 400 Bad Request (например, чат не найден) — это постоянные ошибки, повторять отправку для них бессмысленно, сообщение нужно просто списать и убрать получателя из базы. 429 — единственная из типичных ошибок API, где повтор не только оправдан, но и ожидается сервером.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверОчередь отправки и правильный backoff
Прямой цикл for user in users: bot.send_message(...) работает до первых сотен получателей и разваливается на тысячах. Правильная схема — очередь, через которую все исходящие сообщения проходят с контролируемым темпом, а не разлетаются параллельно из разных мест кода.
Минимальная реализация на Python с asyncio и токен-бакетом на глобальный лимит:
import asyncio
import time
class RateLimiter:
def __init__(self, rate: float, per: float = 1.0):
self.rate = rate # сколько токенов
self.per = per # за какой период, сек
self.tokens = rate
self.updated = time.monotonic()
self.lock = asyncio.Lock()
async def acquire(self):
async with self.lock:
now = time.monotonic()
elapsed = now - self.updated
self.tokens = min(self.rate, self.tokens + elapsed * (self.rate / self.per))
self.updated = now
if self.tokens < 1:
wait = (1 - self.tokens) * (self.per / self.rate)
await asyncio.sleep(wait)
self.tokens = 0
else:
self.tokens -= 1
global_limiter = RateLimiter(rate=25, per=1.0) # с запасом от ~30/сек
Лимит взят с запасом (25 вместо ориентировочных 30) — троттлинг на своей стороне стоит делать немного строже общего ориентира, потому что сетевые задержки и параллельные запросы всё равно создают всплески.
Отправка с обработкой 429 и уважением retry_after:
async def send_with_retry(bot, chat_id, text, max_retries=5):
for attempt in range(max_retries):
await global_limiter.acquire()
try:
return await bot.send_message(chat_id, text)
except TelegramRetryAfter as e:
# библиотека (aiogram, python-telegram-bot) обычно
# парсит retry_after сама и кладёт его в исключение
await asyncio.sleep(e.retry_after + 0.5)
except TelegramForbiddenError:
# пользователь заблокировал бота — не повторяем
mark_user_unreachable(chat_id)
return None
log.warning("send failed after retries: %s", chat_id)
return None
Задержка после 429 берётся из retry_after, а не из своей формулы экспоненты — сервер знает точнее, сколько ждать. К паузе добавлен небольшой запас (+0.5), потому что момент снятия ограничения на сервере и момент, когда до него долетит ваш запрос, не совпадают идеально.
Классический экспоненциальный backoff (без retry_after, если библиотека его не отдаёт или ошибка не 429, а сетевая — таймаут, разрыв соединения) выглядит иначе:
async def send_with_backoff(bot, chat_id, text, max_retries=5):
delay = 1.0
for attempt in range(max_retries):
try:
return await bot.send_message(chat_id, text)
except (TimeoutError, ConnectionError):
await asyncio.sleep(delay + random.uniform(0, delay * 0.3)) # джиттер
delay = min(delay * 2, 30)
return None
Джиттер (случайная добавка к паузе) важен, если у вас параллельно работает несколько воркеров: без него все воркеры после сбоя проснутся синхронно и создадут новый всплеск нагрузки — тот самый эффект thundering herd. Общий принцип троттлинга подробнее разобран в статье как работает rate limiting — там же объяснена разница между токен-бакетом и скользящим окном, если нужно выбрать алгоритм под конкретную задачу, а не только под Telegram.
Архитектура рассылки на большую аудиторию: батчинг и троттлинг
Когда аудитория растёт до десятков и сотен тысяч подписчиков, простого асинхронного цикла с лимитером уже недостаточно — рассылка должна быть отдельным управляемым процессом, а не побочным эффектом обработчика команды /broadcast. Практическая схема, которая устойчиво работает на одном VPS:
- Очередь задач — Redis (список или Stream) или простая таблица в PostgreSQL со статусами
pending / sent / failed. Список получателей формируется один раз при старте рассылки, а не собирается на лету. - Пул воркеров с общим лимитером — несколько асинхронных задач читают из очереди, но все проходят через один и тот же
RateLimiter(как в примере выше), а не имеют собственный независимый темп — иначе суммарная скорость легко превысит общий лимит бота, даже если каждый воркер по отдельности «медленный». - Батчинг по времени, а не по объёму — рассылка растягивается во времени, а не отправляется «пачкой» разом: при лимите около 30 сообщений в секунду 100 000 получателей — это около часа непрерывной отправки, и это нормально, лимит всё равно не даст ускориться.
- Разделение по типам получателей — если часть получателей это группы, а часть — личные чаты, стоит вести для них отдельные очереди с разным темпом, потому что лимит на чат для групп жёстче.
- Персистентность прогресса — при перезапуске бота (деплой, падение процесса) рассылка должна продолжаться с того места, где остановилась, а не с начала: статус
sentв базе — это дешёвая защита от повторной отправки одного и того же сообщения тысячам людей.
Ориентировочная таблица зависимости времени рассылки от размера аудитории при консервативном темпе около 25 сообщений в секунду:
| Получателей | Время рассылки (ориентировочно) |
|---|---|
| 1 000 | ~40 секунд |
| 10 000 | ~7 минут |
| 100 000 | ~1 час 7 минут |
| 1 000 000 | ~11 часов |
Это именно ориентир при равномерном темпе без сбоев и без повторов — на практике добавляются паузы на 429 у отдельных чатов, повторные попытки и обработка ошибок, так что закладывайте запас 15–25% сверху. Как устроена сама рассылка по подписчикам на уровне кода бота, включая хранение списка получателей и обработку отписок, подробно разобрано в статье про бота для рассылки по подписчикам.
Для по-настоящему больших аудиторий (от миллиона и выше) имеет смысл рассмотреть несколько ботов с разными токенами, каждый со своим бюджетом лимита, и распределять получателей между ними — но это усложняет инфраструктуру (нужно, чтобы пользователь взаимодействовал именно с «его» ботом) и оправдано не всегда: сначала стоит убедиться, что узкое место — действительно лимит API, а не, например, сеть или диск на сервере.
Мониторинг и деградация под нагрузкой
Рассылка на большую аудиторию — это часы непрерывной работы, и за это время что-то обязательно пойдёт не так: истечёт токен, оборвётся сеть, упадёт Redis. Без наблюдаемости узнать об этом получится только когда пользователи начнут жаловаться, что рассылка «зависла». Стоит логировать и по возможности выводить в дашборд:
- текущую скорость отправки — если она заметно ниже настроенного лимита, очередь пуста или воркеры застряли на повторах;
- количество 429 за последнюю минуту — рост метрики означает, что реальный лимит ниже заложенного, темп нужно снизить;
- количество
403 Forbidden— это не троттлинг, а отток аудитории (блокировки бота), стоит считать отдельно; - глубину очереди и оценку оставшегося времени — чтобы понимать, когда рассылка реально закончится.
Полезная практика — алерт в отдельный служебный чат при аномальном росте 429 или при полной остановке отправки дольше нескольких минут; как быстро поднять такие уведомления на сервере, описано в статье про алерты в Telegram на VPS. Заметьте: этот алерт должен идти через отдельного бота или отдельный высокоприоритетный канал очереди — если пускать его через тот же лимитированный поток, что и саму рассылку, уведомление о проблеме может застрять в той же очереди, что и проблема.
Частые ошибки при массовых рассылках
- Параллельные воркеры без общего лимитера. Каждый воркер сам по себе укладывается в 10 сообщений в секунду, но их пять — и суммарно бот отправляет 50 в секунду, вдвое больше ориентировочного предела. Лимитер должен быть один и общий на процесс (или общий через Redis, если воркеров несколько на разных серверах).
- Игнорирование retry_after в пользу своей формулы задержки. Собственный экспоненциальный backoff хорош для сетевых сбоев, но для 429 сервер уже посчитал нужную паузу — использовать вместо неё фиксированную секунду или произвольную экспоненту означает либо ждать дольше необходимого, либо снова упереться в лимит раньше времени.
- Блокирующие вызовы внутри асинхронного воркера. Синхронный HTTP-клиент или тяжёлая операция с базой без
awaitвнутри цикла отправки останавливает весь event loop — темп отправки скачет не из-за лимитов Telegram, а из-за собственной архитектуры бота. - Отсутствие различия между личными чатами и группами. Код, который одинаково гонит сообщения в группы и в личку с одним лимитом, будет ловить 429 в группах гораздо чаще — там порог ниже.
- Потеря прогресса при перезапуске. Без сохранения статуса
sent/pendingв базе рестарт бота во время рассылки означает либо повторную отправку части аудитории, либо потерю части получателей — оба варианта плохо смотрятся в проде. - Отправка без учёта отписок и блокировок. Если не обрабатывать
403 Forbiddenи не убирать таких пользователей из базы, каждая следующая рассылка тратит время и один и тот же слот лимита на заведомо недоставимые сообщения — на большой базе это ощутимо снижает эффективную скорость полезной рассылки. - Тестирование на маленькой аудитории и экстраполяция «в лоб». Рассылка на 50 человек пройдёт мгновенно и не покажет проблем с троттлингом — они проявляются только на объёмах, где лимит реально становится узким местом, поэтому нагрузочный прогон стоит делать хотя бы на нескольких тысячах тестовых chat_id или с намеренно заниженным лимитом для проверки логики повторов.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли обойти лимит, увеличив число серверов бота?
Нет — лимит привязан к токену бота на стороне Telegram, а не к серверу или IP-адресу, с которого идут запросы. Несколько серверов помогут распределить вычислительную нагрузку, но не увеличат бюджет сообщений в секунду для одного бота.
Что будет, если совсем игнорировать 429 и просто повторять запрос сразу?
С высокой вероятностью получите следующий 429 с ещё большим retry_after — сервер интерпретирует настойчивые повторы без паузы как продолжающееся превышение лимита, и задержка может расти, а не сокращаться.
Нужен ли Redis для очереди, если аудитория небольшая (пара тысяч подписчиков)?
Не обязательно — для такого объёма достаточно очереди в памяти процесса (asyncio.Queue) с одним общим лимитером, а прогресс можно сохранять прямо в таблицу базы данных, которая у бота и так есть. Redis имеет смысл, когда воркеров несколько или процесс бота может перезапускаться посреди рассылки.
Как понять, что узкое место — именно лимит Telegram, а не сеть или сервер?
Смотрите на код ответа: если это HTTP 429 именно от api.telegram.org с полем retry_after — это лимит API. Таймауты, разрывы соединения или медленный ответ без кода 429 — это, скорее всего, сетевые проблемы или нагрузка на сам сервер бота, и лечится это по-другому: проверкой пропускной способности сети и ресурсов VPS.
Стоит ли слать одно и то же сообщение через sendMediaGroup вместо нескольких sendMessage, чтобы сэкономить лимит?
Нет, это разные механизмы: sendMediaGroup считается отдельным методом со своими, обычно более строгими ограничениями, и не предназначен для экономии общего лимита сообщений — использовать его стоит только когда действительно нужен альбом из нескольких медиафайлов в одном сообщении.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →