G-FORCE
0.0G
разгон
← все разборы

Одна синхронная строка остановила весь Telegram-бот


! Проблема

Бот для учёта расходов на aiogram. Обычные команды отрабатывали мгновенно, но при запросе большого отчёта бот замолкал для всех пользователей на несколько секунд. Асинхронный код, который ведёт себя как однопоточный блокирующий.

Решение

Нашёл в коде обращение к базе через синхронный драйвер: одна строка внутри async-функции. Перевёл весь слой данных на асинхронный SQLAlchemy, тяжёлое построение отчёта вынес в отдельную задачу, а расписание напоминаний — на APScheduler. Всё завернул в docker-compose вместе с базой, чтобы окружение было одинаковым везде.

Как я к этому пришёл

Слово async перед функцией не делает код асинхронным — оно лишь разрешает внутри await. Если внутри вызвать блокирующую операцию, цикл событий встанет целиком: пока эта строка выполняется, ни один другой пользователь не будет обслужен.

Именно это у меня и происходило. Я написал сотню асинхронных обработчиков и один синхронный запрос к базе в функции построения отчёта. На маленькой выборке разница была незаметна, на годовой — секунды полной тишины.

Диагностика оказалась поучительной: я добавил замер времени вокруг каждого await и увидел, что цикл событий простаивает ровно там, где ждать не должен.

Отдельный урок — деплой. Пока бот жил только у меня, «работает на моей машине» устраивало. Как только понадобилось поднять его на сервере, выяснилось, что версия Python другая, а зависимости конфликтуют. docker-compose снял вопрос: одна команда — и окружение идентично.

Бот работает каждый день, отчёт за месяц строится меньше чем за секунду, а развёртывание на новом сервере занимает одну команду.

Вывод

Асинхронность ломается тихо. Одна блокирующая строка среди сотни асинхронных останавливает всё приложение — и никакой ошибки в логах.

Другие разборы