Потоковая обработка

CQRS

LinkedIn в 2011 году имел одну базу данных для всего: профили, связи, сообщения, поиск. Страница профиля требовала 50+ JOIN запросов. При пике нагрузки база падала под тяжестью чтений, хотя записей было в 20 раз меньше. Решение: разделить модели. Профиль писался в нормализованную реляционную БД. Читался из денормализованного документа в Voldemort (key-value store). CQRS в промышленном масштабе - это то, что позволило LinkedIn вырасти до 200 млн пользователей.

  • **Event Sourcing + CQRS:** Microsoft Azure, Netflix используют CQRS в связке с Event Sourcing; write side хранит только события, read side строит проекции
  • **Elasticsearch как read model:** многие системы пишут в PostgreSQL (write side) и индексируют в Elasticsearch (read side) для полнотекстового поиска - классический CQRS паттерн
  • **Axon Framework (Java):** dedicated CQRS/Event Sourcing фреймворк, используемый в banking и insurance системах где аудит и история критичны

Разделение чтения и записи

В большинстве приложений чтений в 10-100 раз больше, чем записей. При этом требования к модели данных для чтения (агрегация, денормализация, проекции) противоречат требованиям для записи (нормализация, консистентность, бизнес-правила). CQRS разрешает это противоречие радикально: две разные модели для двух разных операций. Command изменяет состояние. Query возвращает данные. Никогда вместе.

CQRS не требует отдельных баз данных - это спектр. Простейший уровень: разные методы в одном сервисе с разными моделями. Средний: отдельные read и write сервисы, одна БД. Полный: отдельные хранилища, асинхронная синхронизация. Greg Young (автор термина): начинать с простого, применять CQRS там, где есть реальная проблема асимметрии нагрузки.

Какова главная причина разделения моделей чтения и записи в CQRS?

Eventual Consistency в CQRS

Самое неудобное следствие CQRS с отдельными хранилищами - eventual consistency. Запись прошла в write-store, но read-store обновится через 50-500 миллисекунд. Пользователь создал заказ, нажал 'Мои заказы' - и не видит его. Это не баг - это архитектурное решение с ценой. Вопрос 'как долго стаял рассогласованным?' определяет приемлемость CQRS для конкретного домена.

Паттерны для управления eventual consistency: Read-your-writes (после команды читать из write-store некоторое время), Optimistic UI (показывать результат немедленно, откатить если синхронизация провалилась), версионирование (клиент передает версию, ждет пока read-store достигнет этой версии). Consistency window: обычно 100-500ms при Kafka, 10-50ms при Redis pub/sub.

Пользователь создал пост в социальной сети и сразу перешел на список постов - пост не появился. Через 2 секунды обновил - пост появился. Какой паттерн решил бы это UX-проблему?

Materialized Views как Read Model

Read-модель в CQRS - это, по сути, кеш, синхронизируемый событиями. Materialized view - физическое воплощение этой идеи в базе данных: предвычисленный результат запроса, хранящийся как таблица. Вместо JOIN по 5 таблицам при каждом запросе - единственный SELECT из денормализованной проекции. Обновление происходит через события или триггеры - не при каждом чтении.

PostgreSQL MATERIALIZED VIEW: CREATE MATERIALIZED VIEW ... AS SELECT ... WITH DATA; REFRESH MATERIALIZED VIEW CONCURRENTLY - без блокировки. Redis как read model: HSET order:summary:{id} status pending total 9999. Event-driven update: Kafka consumer слушает события и обновляет read store. Elasticsearch как read store: полнотекстовый поиск по денормализованным документам.

В CQRS с event-driven read model: что происходит если Kafka consumer падает на 5 минут и затем восстанавливается?

Синхронизация и согласованность

Самая сложная часть CQRS - не архитектура, а операционный вопрос: что делать когда read model рассинхронизировалась? Частичный сбой consumer, схема изменилась, событие пришло out-of-order. Ответ: идемпотентность и возможность воспроизведения. Если каждый projector идемпотентен (обработка одного события дважды = обработка один раз), то любую read model можно пересоздать заново из event log.

Snapshot pattern: периодическая материализация текущего состояния + события после snapshot, чтобы не replay с начала истории. Idempotency key: уникальный ключ события для предотвращения дублирования. At-least-once delivery + idempotent consumer = exactly-once semantics на уровне бизнес-результата. Dead Letter Queue (DLQ): события, которые не удалось обработать, отправляются в DLQ для ручного разбора.

CQRS нужно применять во всех проектах для масштабируемости

CQRS решает конкретную проблему асимметрии нагрузки и сложности модели; в большинстве CRUD-приложений это оверинжиниринг

CQRS добавляет eventual consistency, сложность синхронизации и операционную нагрузку. Greg Young, автор CQRS, явно предупреждал: применяйте только там, где есть реальная необходимость - высокая нагрузка на чтение, принципиально разные модели для read/write, или Event Sourcing как обязательное требование.

Projector получил одно и то же событие дважды из Kafka (at-least-once delivery). Как идемпотентность предотвращает проблему?

Ключевые идеи

  • **Разделение моделей** - write model нормализована для бизнес-правил, read model денормализована для производительности; это осознанный трейдоф
  • **Eventual consistency** - неизбежное следствие; Read-your-writes и Optimistic UI скрывают её от пользователя
  • **Идемпотентность projector** - ключ к надежности; любую read model можно пересоздать replay событий из Kafka
  • **Применять избирательно** - CQRS оправдан при реальной асимметрии нагрузки; для CRUD - это оверинжиниринг

Связанные темы

CQRS тесно связан с паттернами распределенных систем:

  • Saga Pattern — Saga управляет распределенными транзакциями; CQRS определяет как разделить команды и запросы внутри каждого шага
  • Event Streaming (Kafka) — Kafka - транспорт для синхронизации write и read моделей в CQRS

Вопросы для размышления

  • В каких доменах eventual consistency неприемлема (банковские транзакции, медицинские записи)? Как там применяется CQRS, если вообще применяется?
  • Если read model устарела и содержит неверные данные, как организовать горячий rebuild без простоя сервиса?
  • CQRS часто упоминают вместе с Event Sourcing. Можно ли применять их независимо друг от друга? В чём разница?

Связанные уроки

  • dist-12-consistency
CQRS

0

1

Войти