2025, Oct 22 12:32

Почему в Telegram Bot API нет истории и как избежать TimedOut из‑за вложенного polling

Почему в Telegram Bot API нельзя получить историю чата и как вложенный get_updates при polling истощает пул соединений — вызывая TimedOut — и как это исправить.

При создании Python‑бота для Telegram нередко требуется получить предыдущие сообщения чата. На первый взгляд задача простая, но Telegram Bot API не предоставляет доступ к истории. Попытки обойти это ограничение часто приводят к проблемам с производительностью — вплоть до исчерпания пула соединений и ошибок TimedOut во время polling.

Демонстрация проблемы

Ниже показана типичная структура обработчика, на которой проявляется сбой. Код принимает входящее обновление и изнутри обработчика снова вызывает get_updates, продолжая обращаться к серверам Telegram, хотя polling уже запущен.

class ChatBotRunner:
    def __init__(self) -> None:
        self.api_key = os.getenv("TELEGRAM_BOT_TOKEN")
        self.app = ApplicationBuilder().token(self.api_key).connection_pool_size(20).build()

    def start(self) -> None:
        self._wire_handlers()
        self.app.run_polling()
        log.info("BOT STARTED")

    def _wire_handlers(self):
        self.app.add_handler(MessageHandler(filters=filters.TEXT, callback=menu_handler))

async def menu_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = await load_user(update)
    log.error(user.phase)

    match user.phase:
        case Phase.intro:
            log.error(update.message.text)
            log.error(update.message)
            batch = await update._bot.get_updates()

            for item in batch:
                log.error(item)
                log.error(item.message)
                last_id = item.update_id

Что происходит на самом деле и почему всё ломается

У бота задан размер пула соединений 20. Пока run_polling уже получает обновления, обработчик делает дополнительный вызов get_updates. Этот параллельный polling продолжает отправлять запросы во время обработки предыдущих обновлений. При достаточном числе одновременных взаимодействий свободные соединения быстро заканчиваются, что приводит к ошибке TimedOut: все соединения в пуле заняты, и запрос не был отправлен.

Может ли бот получить историю чата?

Нет. Telegram Bot API не предоставляет способа получить исторические сообщения из чата. Если нужна история, придётся перейти на клиентский API на базе TDLib, например tdlib. Этот подход работает иначе, чем bot polling, и предназначен для полноценного клиентского доступа, включая историю чатов.

Как устранить таймаут пула соединений

Быстрое решение — избежать вложенного polling. В большинстве случаев не нужно вручную вызывать get_updates внутри обработчиков, потому что run_polling уже управляет потоком обновлений.

class ChatBotRunner:
    def __init__(self) -> None:
        self.api_key = os.getenv("TELEGRAM_BOT_TOKEN")
        self.app = ApplicationBuilder().token(self.api_key).connection_pool_size(20).build()

    def start(self) -> None:
        self._wire_handlers()
        self.app.run_polling()
        log.info("BOT STARTED")

    def _wire_handlers(self):
        self.app.add_handler(MessageHandler(filters=filters.TEXT, callback=menu_handler))

async def menu_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
    user = await load_user(update)
    log.error(user.phase)

    match user.phase:
        case Phase.intro:
            log.error(update.message.text)
            log.error(update.message)
            # Здесь нет вложенного вызова get_updates()
            # Обрабатываем только текущее обновление

Почему это важно

Понимание различий между Telegram Bot API и клиентским API экономит время и помогает избежать тупиков. Интерфейс ботов событийный и не раскрывает историю; ручной polling внутри обработчиков лишь увеличивает нагрузку и при конкуренции приводит к исчерпанию ресурсов. Чёткое разделение зон ответственности даёт предсказуемую производительность и меньше сюрпризов в эксплуатации.

Выводы

Если нужны прошлые сообщения, Bot API их не предоставит — используйте клиентский API, например TDLib. В ботах доверьте обработку обновлений run_polling и не вызывайте get_updates внутри обработчиков. Так вы не переполните пул соединений и сохраните отзывчивость бота под нагрузкой.

Статья основана на вопросе на StackOverflow от Konstantinos и ответе mint_tube.