← Назад к вопросам

Откуда берется распределенный монолит

3.0 Senior🔥 31 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Распределённый монолит: происхождение и природа

Распределённый монолит (Distributed Monolith) — это архитектурный антипаттерн, где система выглядит как микросервисы, но функционирует как тесно связанный монолит. Это наихудшее из обоих миров: вся сложность распределённых систем без её преимуществ.

Как он возникает

1. Неправильное разделение на микросервисы

Обычно история развития выглядит так:

┌─────────────────────────────────────────────────┐
│  Этап 1: Монолитная архитектура                │
│  - Один процесс, одна БД                        │
│  - Просто масштабировать (горизонтально)       │
│  - Быстрое разработка                           │
└─────────────────────────────────────────────────┘
                       ↓ рост проекта
┌─────────────────────────────────────────────────┐
│  Этап 2: Попытка перейти на микросервисы       │
│  - Команда слышала о масштабируемости           │
│  - Начали делить монолит "по слоям" (User,    │
│    Order, Payment сервисы)                      │
│  - Забыли про слабую связанность                │
└─────────────────────────────────────────────────┘
                       ↓ результат
┌─────────────────────────────────────────────────┐
│  Распределённый монолит                         │
│  - Много сервисов, много RPC вызовов           │
│  - Синхронные зависимости повсюду              │
│  - Сложность отладки, низкая надёжность        │
└─────────────────────────────────────────────────┘

2. Синхронные зависимости между сервисами

Вот типичный случай:

# UserService
@app.post("/users")
def create_user(user_data):
    user = User.create(**user_data)
    
    # 🔴 СИНХРОННЫЙ вызов к другому сервису!
    response = requests.post(
        "http://payment-service/setup-account",
        json={"user_id": user.id}
    )
    
    # Если payment-service упал, упадёт и этот сервис
    if response.status_code != 200:
        raise Exception("Payment setup failed")
    
    return user

Это создаёт цепь зависимостей, которая превращает всю систему в монолит:

  • UserService зависит от PaymentService
  • PaymentService зависит от NotificationService
  • NotificationService зависит от EmailService
  • И так далее...

Результат: если упадёт любой сервис в цепи, упадёт вся система.

3. Недостаточная обработка отказов

# ❌ Плохо: нет обработки timeout/retry
def get_user_with_orders(user_id):
    user = db.get_user(user_id)
    
    # Если OrderService медленный, зависнет весь запрос
    orders = requests.get(f"http://order-service/{user_id}").json()
    
    return {"user": user, "orders": orders}

# ✅ Хорошо: timeout, retry, fallback
from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(3), wait=wait_exponential())
def get_orders_with_retry(user_id, timeout=2):
    try:
        return requests.get(
            f"http://order-service/{user_id}",
            timeout=timeout
        ).json()
    except requests.Timeout:
        # Fallback: вернуть пустой список
        return []

4. Общая база данных

Много "микросервисов" оказываются в одной БД:

-- Одна БД для всех!
CREATE TABLE users (...);
CREATE TABLE orders (...);
CREATE TABLE payments (...);
CREATE TABLE notifications (...);

Это полностью уничтожает независимость сервисов:

  • Изменение схемы в одной таблице может сломать другой сервис
  • Блокировки в БД влияют на всех
  • Невозможно использовать разные БД (PostgreSQL, MongoDB)
  • Невозможно масштабировать отдельные сервисы

Признаки распределённого монолита

  • 🔴 Синхронные REST/RPC вызовы между сервисами
  • 🔴 Одна общая БД для всех микросервисов
  • 🔴 Общие библиотеки, которые обновляются вместе
  • 🔴 Если упадёт один сервис, система деградирует
  • 🔴 Логирование и трейсинг разбросаны по сервисам
  • 🔴 Версионирование API не продумано
  • 🔴 Невозможно развёртывать сервисы независимо

Как правильно делать микросервисы

1. Слабая связанность через async

# ✅ Асинхронная коммуникация
from celery import shared_task

@shared_task
def setup_payment_account(user_id):
    """Это выполнится в фоне, независимо от создания user"""
    payment_service.setup_account(user_id)

# В UserService:
@app.post("/users")
def create_user(user_data):
    user = User.create(**user_data)
    
    # Просто отправляем задачу, не ждём результата
    setup_payment_account.delay(user.id)
    
    return user

2. Event-driven архитектура

# Используем message queue (RabbitMQ, Kafka)
from kombu import Connection, Exchange, Queue

# UserService публикует событие
user_created_event = {
    "event_type": "user.created",
    "user_id": user.id,
    "email": user.email,
    "timestamp": datetime.now().isoformat()
}

with Connection("amqp://localhost") as conn:
    exchange = Exchange("users", type="topic")
    exchange.publish(user_created_event, routing_key="user.created")

# PaymentService и NotificationService слушают независимо
# Если одного нет — user все равно создан

3. Database per service

UserService
    └─ PostgreSQL (users table)

OrderService
    └─ PostgreSQL (orders table)

PaymentService
    └─ MongoDB (payments, transactions)

NotificationService
    └─ Redis (message queue)

Каждый сервис полностью независим в плане хранилища.

4. Обработка отказов

# Circuit Breaker паттерн
from circuitbreaker import circuit

@circuit(failure_threshold=5, recovery_timeout=60)
def call_payment_service(user_id):
    return requests.post("http://payment-service/setup", json={"user_id": user_id})

try:
    call_payment_service(user_id)
except CircuitBreakerListener:
    # Payment service недоступен, но мы продолжаем работу
    log.warning(f"Payment service unavailable, using fallback")
    # Используем retry/fallback стратегию

Почему распределённый монолит появляется

  1. Неправильное разделение ответственности: делят по слоям (User, Order), а не по bounded contexts
  2. Отсутствие async стека: нет Celery, RabbitMQ, Kafka
  3. Коммуникация по умолчанию синхронная: привычка из монолита (прямой вызов функций)
  4. Недоумение о независимости: не понимают разницы между монолитом и микросервисами
  5. Спешка: "быстро разделим и готово" — без планирования

Выводы

Распределённый монолит — результат копирования архитектурного паттерна без понимания его суути. Правильные микросервисы требуют:

  • Независимой разработки: каждый сервис развёртывается отдельно
  • Асинхронной коммуникации: через события, не RPC
  • Слабой связанности: максимум повторения кода, минус координация
  • Отказоустойчивости: circuit breakers, timeouts, fallbacks
  • Dedicated databases: каждый сервис хранит свои данные

Если этого нет — это не микросервисы, а распределённый монолит.

Откуда берется распределенный монолит | PrepBro