Ошибка 429 Too Many Requests в Telegram-боте и её решение с помощью Redis
Автор: Javohir Abdullayev · · Telegram-боты

Введение: Неожиданное препятствие в реальном проекте
Недавно в e-commerce Telegram-боте нашего клиента с аудиторией более 20 000 активных пользователей возникла серьёзная проблема. В момент запуска новой акции тысячи покупателей одновременно зашли в бот и начали непрерывно нажимать на кнопки. В результате бот перестал отвечать, а в логах сервера появилась известная ошибка: TelegramRetryAfter: Flood control exceeded. Retry in 18 seconds (HTTP 429).
В этом кейсе мы разберём, как работают лимиты запросов (Rate Limit) в Telegram-ботах, почему возникает ошибка 429 и как устранить её с помощью Aiogram 3 и Redis.
Почему Telegram возвращает ошибку 429?
Серверы Telegram Bot API имеют строгие ограничения для защиты своей инфраструктуры от спама и чрезмерных нагрузок (DDoS):
- Лимит на отправку в один чат: не более 1 сообщения в секунду (при кратковременных всплесках допускается до 30, но не на постоянной основе).
- Общий лимит сообщений: суммарно во все чаты не более 30 сообщений в секунду.
- Отправка сообщений в группы: не более 20 сообщений в минуту.
Если ваш бот превышает эти лимиты, Telegram API возвращает ошибку 429 Too Many Requests с указанием количества секунд, которое необходимо подождать (retry_after). Если не управлять этим процессом должным образом, бот зависнет для всех пользователей.
Анализ проблемы: В чём заключалась ошибка?
При анализе исходного кода проекта были выявлены два основных недостатка:
- Пользовательский спам: когда некоторые пользователи нажимали одну и ту же кнопку 5–10 раз за секунду, бот отправлял отдельный API-запрос на каждое нажатие.
- Хранение состояния в памяти (In-Memory State): проверка rate limit сохранялась в оперативной памяти Python (в словаре), из-за чего при перезапуске бота или работе нескольких воркеров синхронизация терялась.
Решение: Создание Throttling Middleware с помощью Redis
Для решения проблемы мы разработали специальный middleware, контролирующий частоту запросов каждого пользователя через Redis. Redis работает чрезвычайно быстро и позволяет задавать ключам время автоматического удаления (TTL).
1. Код Throttling Middleware для работы с Redis
Наш класс для Aiogram 3 на основе BaseMiddleware выглядит следующим образом:
from typing import Any, Awaitable, Callable, Dict
from aiogram import BaseMiddleware
from aiogram.types import TelegramObject, Message, CallbackQuery
import redis.asyncio as aioredis
class ThrottlingMiddleware(BaseMiddleware):
def __init__(self, redis: aioredis.Redis, limit_seconds: int = 1):
self.redis = redis
self.limit_seconds = limit_seconds
async def __call__(
self,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: Dict[str, Any]
) -> Any:
user = data.get("event_from_user")
if not user:
return await handler(event, data)
user_id = user.id
key = f"throttle:{user_id}"
# Проверяем наличие ключа в Redis
is_throttled = await self.redis.get(key)
if is_throttled:
# Если пользователь нажал кнопку быстрее положенного времени
if isinstance(event, CallbackQuery):
await event.answer("Пожалуйста, не спешите!", show_alert=False)
elif isinstance(event, Message):
# Игнорируем лишние сообщения или предупреждаем
pass
return
# Сохраняем ключ на указанный срок (TTL)
await self.redis.set(key, "1", ex=self.limit_seconds)
return await handler(event, data)2. Подключение Middleware к боту
В основном файле main.py создаём подключение к Redis и регистрируем middleware:
import asyncio
from aiogram import Bot, Dispatcher
import redis.asyncio as aioredis
from config import BOT_TOKEN, REDIS_URL
from middlewares import ThrottlingMiddleware
async def main():
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()
redis = aioredis.from_url(REDIS_URL, decode_responses=True)
# Включаем throttling для сообщений и Callback-запросов
dp.message.middleware(ThrottlingMiddleware(redis=redis, limit_seconds=1))
dp.callback_query.middleware(ThrottlingMiddleware(redis=redis, limit_seconds=1))
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())Проблема массовых рассылок (Broadcast)
Чтобы избежать ошибки 429 при массовой рассылке сообщений пользователям, следует отказаться от простого цикла for. Вместо этого необходимо добавить микропаузы между сообщениями (asyncio.sleep(0.05)) и реализовать логику автоматического ожидания при возникновении ошибки TelegramRetryAfter:
from aiogram.exceptions import TelegramRetryAfter, TelegramForbiddenError
import asyncio
import logging
async def send_broadcast_message(bot: Bot, user_id: int, text: str):
try:
await bot.send_message(chat_id=user_id, text=text)
await asyncio.sleep(0.05) # Безопасно отправлять до 20 сообщений в секунду
except TelegramRetryAfter as e:
logging.warning(f"Необходимо подождать {e.retry_after} сек...")
await asyncio.sleep(e.retry_after)
return await send_broadcast_message(bot, user_id, text)
except TelegramForbiddenError:
logging.info(f"Пользователь заблокировал бота: {user_id}")
except Exception as e:
logging.error(f"Непредвиденная ошибка: {e}")Результаты и заключение
После внесения данных изменений показатели бота значительно улучшились:
- Ошибки 429 сократились на 99%: при повторных нажатиях пользователей запросы блокируются на уровне Redis, не доходя до Telegram API.
- Сэкономлены ресурсы сервера: устранены лишние, не подлежащие обработке запросы к БД.
- Улучшился пользовательский опыт (UX): бот стал работать плавно и стабильно для всех пользователей.
Если вы разрабатываете высоконагруженных Telegram-ботов, обязательно с самого начала внедряйте систему Throttling и Rate Limiting на базе Redis.
Теги: #Telegram bot #Aiogram 3 #Redis #Python #Rate limiting