Зачем нужны паттерны
Паттерны системного дизайна -- это проверенные рецепты для типовых задач распределённых систем. Они не магия, а кристаллизованный опыт: кто-то уже наступил на грабли, и теперь у нас есть готовое решение.
В отличие от "банды четырёх" (GoF), которая описывает паттерны внутри одного процесса, системные паттерны работают на уровне сервисов, баз данных, очередей и сетей. Здесь нет shared memory и транзакций -- только сеть, которая иногда лежит.
Четыре категории паттернов
Условная классификация по цели применения:
┌─────────────────────────────────────────────────────────────┐
│ Паттерны системного дизайна │
├──────────────┬──────────────┬──────────────┬────────────────┤
│ Data │ Reliability │ Scaling │ Coordination │
│ │ │ │ │
│ Saga │ Circuit │ Fan-out │ Distributed │
│ Outbox │ breaker │ Rate limit │ lock │
│ CDC │ Retry + │ Unique ID │ Leader │
│ Materialized │ backoff │ Sharding │ election │
│ view │ Bulkhead │ CDN │ Consensus │
│ Event │ DLQ │ Load │ Service │
│ sourcing │ Timeout │ balancing │ discovery │
│ │ Hedging │ │ │
└──────────────┴──────────────┴──────────────┴────────────────┘
Data patterns -- управление данными
Главная проблема: как гарантировать консистентность данных между разными сервисами и хранилищами без распределённых транзакций.
- Saga -- длинные бизнес-транзакции через последовательность локальных + компенсации
- Transactional Outbox -- атомарность записи в БД и отправки в очередь
- CDC (Change Data Capture) -- стриминг изменений БД через лог репликации
- Materialized view -- предвычисленный результат агрегации
- Event Sourcing -- хранить события, а не состояние (покрыто в
17.arch-patterns/4.cqrs-es)
Reliability patterns -- надёжность при сбоях
Сеть ненадёжна, сервисы падают. Эти паттерны ограничивают blast radius.
- Circuit breaker -- отключение вызовов к упавшему сервису
- Retry + exponential backoff + jitter -- умное повторение
- Bulkhead -- изоляция ресурсов (как отсеки корабля)
- DLQ (Dead Letter Queue) -- куда складывать отравленные сообщения
- Timeout propagation -- контекст с дедлайном через всю цепочку вызовов
- Hedged requests -- дублирование запроса к медленным реплика
Scaling patterns -- горизонтальное масштабирование
Проблема: одна машина не тянет нагрузку. Эти паттерны распределяют работу.
- Fan-out (write vs read) -- предвычисление лент/фидов
- Rate limiter -- защита от перегрузки (token/leaky bucket, sliding window)
- Unique ID generators -- Snowflake, UUID v7, ULID для распределённой генерации
- Two-stage processing -- быстрый ack + асинхронная обработка
Coordination patterns -- координация узлов
Когда N узлов должны договориться о чём-то (кто лидер, кто держит лок).
- Distributed lock -- Redis/etcd/ZooKeeper для критических секций
- Leader election -- выбор одного активного узла из кластера
- Consensus -- Raft, Paxos для реплицированного состояния
- Fencing tokens -- защита от stale lock holders
Когда применять: cheat sheet
| Проблема | Паттерн | Пример применения |
|---|---|---|
| Транзакция через N сервисов | Saga | Оформление заказа: платёж + резерв + доставка |
| Надо отправить событие после commit | Outbox | После создания user -- push в Kafka |
| Миграция БД без downtime | CDC (Debezium) | PostgreSQL -> Elasticsearch sync |
| Лента пользователя с 1000 подписок | Fan-out on write (push) | Twitter timeline |
| Лента знаменитости с 100M фолловеров | Fan-out on read (pull) | Celebrity feed |
| Дашборд с агрегацией 1B строк | Materialized view | Analytics realtime |
| API внешнего партнёра иногда лежит | Circuit breaker | Payment gateway |
| Транзиентные сетевые ошибки | Retry + jitter | HTTP call to microservice |
| Один медленный endpoint не должен валить всё | Bulkhead | Separate thread pool per downstream |
| Poison message не даёт обрабатывать очередь | DLQ | Сообщение с невалидным JSON |
| 1M RPS на endpoint | Rate limiter | Public API |
| Распределённая генерация ID | Snowflake / UUID v7 | Order IDs across shards |
| Только один worker должен обработать задачу | Distributed lock | Cron job в кластере |
| Кто лидер в HA кластере | Leader election | Active-passive failover |
Anti-patterns и подводные камни
Retry без idempotency -- вы только что списали деньги дважды. Retry работает только с идемпотентными операциями (см. 02.design-approaches/9.idempotency.md).
Circuit breaker без fallback -- закрытый цепной выключатель без плана B = ошибка пользователю. Подумайте: cached response, graceful degradation, queue for later.
Saga без компенсаций -- если шаг 4 упал, а компенсаций нет, данные неконсистентны навсегда.
Distributed lock без fencing -- GC pause в JVM может удержать лок дольше TTL, два процесса получат "эксклюзивный" доступ одновременно.
Dual-write в БД и очередь без outbox -- сеть мигнула между коммитом и publish, событие потерялось.
Fan-out on write для всех -- у Лионеля Месси 500M подписчиков. Push каждого твита в 500M inbox -- это 500M записей. Убьёт БД.
Rate limit только на балансере -- легитимный пользователь с одного IP получает 429, а атакующий с ботнета проходит.
Композиция паттернов
Реальные системы комбинируют паттерны:
Order placement flow:
Client --> API Gateway [rate limit, auth]
│
v
[Order Service]
│
├── INSERT order + INSERT outbox (1 transaction)
│
v
[Outbox Publisher] -- worker
│
v
[Kafka: orders.created]
│
├──> [Payment Service] ---> Saga step 1
│ [circuit breaker on Stripe]
│ [retry with jitter]
│
├──> [Inventory Service] --> Saga step 2
│
└──> [Notification Service]
[DLQ for failed emails]
Один сценарий использует: rate limit, outbox, saga, circuit breaker, retry, DLQ. Это нормально.
Trade-offs каждого паттерна
Каждый паттерн добавляет сложности. Перед применением спросите:
- Нужна ли вообще эта проблема? Saga нужна, только если операция реально распределённая. Для монолитной БД хватит ACID транзакции.
- Какая цена? Outbox = +1 таблица, +worker, +мониторинг. Оправдано при важности событий.
- Где ломается? Rate limiter с Redis -- Redis single point of failure. Нужен кластер.
- Что мониторить? Circuit breaker state, outbox lag, saga timeout rate, DLQ depth.
Что дальше
В этом разделе мы подробно разберём implementation каждого значимого паттерна с кодом на PHP и Go: saga (оркестровка и хореография), outbox + publisher worker, fan-out стратегии, materialized views, два-стадийная обработка, алгоритмы rate limiter, генераторы ID, распределённые локи с fencing, resilience-паттерны, DLQ, и решения dual-write проблемы.
Выводы
- Паттерны -- не догма, а набор инструментов под конкретные проблемы
- Четыре категории: data, reliability, scaling, coordination
- Большинство реальных систем комбинируют 5-10 паттернов одновременно
- Каждый паттерн имеет цену: сложность, латентность, новые точки отказа
- Применяйте, когда проблема доказана метриками, а не "на всякий случай"