Архитектурные решения, грабли и 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 лет, и я понимаю, что мой путь не совсем стандартный. Именно поэтому я и решил поделиться своим практическим опытом — надеюсь, он будет кому‑то полезен, и буду очень благодарен за конструктивный фидбек от более опытных коллег.
