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

Автор: Javohir Abdullayev · · Telegram-боты

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

Введение: Неожиданное препятствие в реальном проекте

Недавно в 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). Если не управлять этим процессом должным образом, бот зависнет для всех пользователей.

Анализ проблемы: В чём заключалась ошибка?

При анализе исходного кода проекта были выявлены два основных недостатка:

  1. Пользовательский спам: когда некоторые пользователи нажимали одну и ту же кнопку 5–10 раз за секунду, бот отправлял отдельный API-запрос на каждое нажатие.
  2. Хранение состояния в памяти (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