Архитектурные решения, грабли и RabbitMQ
Привет!
Я Python-инженер. Последние несколько лет я в одиночку строил довольно сложную бэкенд‑систему, и за это время набил немало шишек и нашел, как мне кажется, несколько интересных решений. В этой статье я хочу поделиться не «историей успеха», а конкретными архитектурными проблемами и их решениями при построении высокопроизводительного сервиса на асинхронном Python.
Статья будет полезна тем, кто работает с FastAPI, микросервисами и думает о надежности и масштабируемости своих систем.
1. Проблема: Хаос из 20+ AI-моделей
Все началось с простой бизнес-задачи: предоставить унифицированный доступ к десяткам различных AI-моделей (от OpenAI и Gemini до Sora и Kling). Проблема в том, что каждое API - это свой мир:
Разные форматы аутентификации (API-ключи, JWT-токены).
Абсолютно разная структура запросов и ответов (
image_urlvsreference_image).Разное поведение: одни отвечают сразу, другие требуют асинхронного опроса статуса.
Просто "подключать" их по очереди в каждом новом клиенте (например, Telegram-боте) - это прямой путь к неподдерживаемому спагетти-коду. Стало очевидно, что нужно централизованное решение - API-шлюз.
2. Архитектурное решение №1: Декаплинг с FastAPI и RabbitMQ
Для API-шлюза я выбрал FastAPI. Его асинхронная природа идеально подходила для I/O-bound задачи - принять запрос и быстро "перекинуть" его дальше.
Но главная проблема это долгие задачи. Некоторые AI-модели генерируют видео по 3-5 минут. Заставлять клиента ждать столько времени просто безумие.
Мой путь к очередям:
Наивное решение: Сначала я реализовал очередь на
MongoDB. API создавал в коллекции документ со статусомpending, а воркеры в бесконечном цикле опрашивали (poll) эту коллекцию. Это работало, но создавало лишнюю нагрузку на БД и было ненадежно.Правильное решение: Я понял, что изобретаю велосипед, и перешел на RabbitMQ. Это было ключевым решением. API-сервер (Producer) теперь просто публикует сообщение-задачу в очередь и мгновенно отвечает клиенту
202 Accepted. А отдельные воркеры (Consumers) разбирают эту очередь.-
Обеспечение надежности: Чтобы задачи не терялись, если воркер падает, я внедрил два стандартных механизма:
Acknowledgements (
ack/nack): Воркер подтверждает получение сообщения только после успешной обработки. Если он "умирает", сообщение возвращается в очередь.Dead Letter Queue (DLQ): "Сломанные" сообщения, которые вызывают ошибку несколько раз подряд, автоматически отправляются в отдельную очередь для ручного разбора, не блокируя основной поток.
3. Архитектурное решение №2: Правильная база для правильной задачи
Я использовал гибридный подход к хранению данных, и он себя полностью оправдал:
MySQL (PostgreSQL): Для всех структурированных, транзакционных данных, где важна целостность: профили пользователей, API-ключи, биллинг, тарифы.
MongoDB: Для оперативных, часто меняющихся данных: хранение статусов задач, их параметров и JSON-результатов. Гибкая схема и скорость чтения Mongo здесь подходят идеально.
4. Грабли: Как моя MongoDB раздулась до 8GB и чему меня это научило
А теперь о главной ошибке. На старте, для простоты, я передавал в очередь и сохранял в Mongo изображения в формате Base64. Это работало.
Проблема проявилась через несколько месяцев, когда я увидел, что коллекция с задачами раздулась до 8GB при всего 180,000 записей. Аналитика стала тормозить, бэкапы стали огромными. Я понял, что система скоро "встанет".
Решение: Я провел рефакторинг, внедрив паттерн "Pass by Reference". Теперь API-сервер не передает Base64 в очередь, а:
Сначала загружает файл на S3-совместимое хранилище.
Кладет в очередь и в Mongo только легковесную ссылку (URL) на этот файл.
Это простое изменение сократило размер базы на 90% и в разы ускорило все операции. Урок: никогда не храните тяжелые бинарные данные в оперативной базе данных.
5. Мой подход к разработке: "Дирижер и Оркестр"
Многие спросят, как я справился с этим в одиночку. Я активно использую AI‑ассистентов, но не как «автопилот», а как «оркестр». Моя роль это быть архитектором и дирижером:
Я определяю проблему («база раздувается»).
Я проектирую решение («нужно вынести файлы на S3»).
Я ставлю конкретную техническую задачу («напиши мне сервис для загрузки на S3»).
Этот подход позволяет мне фокусироваться на архитектуре и качестве, делегируя рутинную реализацию.
Заключение
Строить high-load систему в одиночку - это вызов. Ключевые уроки, которые я вынес:
Не бойся использовать промышленные инструменты (как RabbitMQ) с самого начала. Они сэкономят тебе месяцы в будущем.
Выбирай правильное хранилище для правильного типа данных.
Проактивно ищи и исправляй «бутылочные горлышки», а не жди, пока все сломается.
P.S. Да, мне 18 лет, и я понимаю, что мой путь не совсем стандартный. Именно поэтому я и решил поделиться своим практическим опытом — надеюсь, он будет кому‑то полезен, и буду очень благодарен за конструктивный фидбек от более опытных коллег.
Комментарии (43)

plFlok
18.10.2025 20:15Аналитика стала тормозить
Мне даже интересно, как лежащие и никого не беспокоящие блобы одной коллекции могли аффектить работу других коллекций
использовать монгу для аналитики - мощно. Лучше сразу уходить в аналитические бд для этого. Всё равно с ростом проекта к этому придёте.
и вообще хочется блобы в какой-нибудь s3 любого облака закинуть, даже если сам сервис крутится на vps-ке за 500 рублей. Ну реально, 8 гигов у яндекса на хранении стоят 16 рублей. первые 100к чтений бесплатно. следующие 100к - 4 рубля.
Иногда лучше откупиться от задачи деньгами, чем тратить на неё время. Вспомните, сколько час программиста стоит, и сколько часов он будет тратить на поддержку решения в будущем. Точно облака не будут дешевле?
p.s: 8 гб - это вообще можно всю базу в RAM держать. Очень удивлён, что оно вызвало тормоза.

maxp
18.10.2025 20:15Про "200к запросов в сутки" тут уже все высказались :)
В конечном счете хранить бинари в S3 - это правильное решение.
А вот разные дб лучше не тянуть в проект без веских оснований. Одного Постгреса вам тут хватило бы по уши. Но уж коль есть желание общаться с MongoDB, то на надо забывать, что там GridFS есть "из коробки".
rdo
18.10.2025 20:15Раньше это называлось пет-проектом, такое делали безо всяких ЛММ-ассастов и не писали про это такие пафосные статьи. Уровень крепкого джуна.

tagabenz
18.10.2025 20:15Молодец конечно, но пуху ты на себя накинул , 18 лет инженер, последние несколько лет)

d3d11
18.10.2025 20:15высокопроизводительного сервиса на асинхронном Python
Разве Python может быть в одном предложении с высокой производительностью?

panzerfaust
18.10.2025 20:15Что меня каждый раз удивляет: человек на хайпе пилит очередную свистоперделку про ИИ, но не догадывается спросить у этого самого ИИ, как это правильно делать.
Keeper22
≈ 2.3 запроса в секунду
shai_hulud
Разве для Python это не считается "Высоконагруженной системой"?
mentin
Даже для Ruby это не много
IlyaEdrets
Хайлоадом обычно считают систему, которая не справляется с нагрузкой на одном сервере.
Обычно тогда приходят к горизонтальному масштабированию и здесь начинаются все сложности хайлоада: согласованность данных, доступность и отклик системы, распределенные транзакции и т.д.
motoronik
2.3 rps это мука для любого языка
metheoryt
Увидел заголовок и пришел негодовать именно по этой причине