SyncOps data · infra
все системы · 99.98% v3.4.1 Консоль

Блог · Инженерия · Паттерны данных

Глубокий разбор · 18 мин чтения

Почему Event Sourcing выигрывает у CQRS в 2024

В 2021-м все писали про «раздельное чтение и запись». К 2024-му стало ясно, что CQRS без событийного журнала — просто два слоя, которые разъезжаются. Разбираем, где ES действительно сильнее, а где оба подхода проигрывают банальной транзакции.

Введение в проблему консистентности

Где именно «рассинхроняются» данные

Проблема не в том, что CQRS сложен. Проблема в том, что он переносит источник истины в две модели — и между ними появляется окно, в котором данные не согласованы. В высоконагруженных системах это окно превращается в источник 40% инцидентов на данных.

В классическом CQRS команда OrderPlaced пишет в модель записи, а чтение идёт из денормализованного read-model. Если между записью и применением в read-model произойдёт сбой, у вас «заказ создан, но его нет в списке». Классическая CQRS без событийного журнала не умеет откатить read-model до последнего согласованного состояния — у неё просто нет истории.

Event Sourcing решает это иначе: единственный источник истины — это сам журнал событий. Read-model — просто проекция, которую можно пересобрать из журнала с нуля за минуты. Именно это свойство, а не «паттерн» как таковой, делает ES сильнее в продакшене, где данные бьются друг о друга с частотой в тысячи операций в секунду.

Важно понимать: мы не спорим, нужен ли вам read-model. Нужен. Мы спорим о том, что должен быть первичным артефактом — текущее состояние сущности или поток событий, из которого оно выводится.

Сравнение подходов на примере кода

Два способа записать отмену заказа

Разберём один и тот же сценарий — отмену заказа — в двух архитектурах. Разница видна не в объёме кода, а в том, что происходит, когда запись падает на 99-й проценте.

Вариант A — CQRS без событийного журнала. Команда CancelOrder меняет статус в таблице orders и отправляет событие в Kafka для обновления read-model. Если Kafka-продюсер упал после коммита в БД, read-model остаётся со старым статусом. У вас нет механизма, который бы «догнал» рассинхрон, кроме ручного скрипта и тикета.

Вариант B — Event Sourcing. Та же команда добавляет в журнал событие OrderCancelled(orderId, ts, reason). Read-model обновляется асинхронно, но при любом рассинхроне её можно пересобрать: rebuild read-model from event #4_820_112. Журнал не теряет события — он append-only, и это фундаментальное отличие от таблицы, где запись «воткнулась» и не откатывается.

Визуализация потока событий: append-only журнал слева, проекции read-model справа, стрелки показывают направление реконструкции состояния

Метрики производительности

Что показывает нагрузочное тестирование

1.8M
Событий / сек на 6 узлов
<9ms
P99 запись в журнал
42s
Пересборка read-model
0
Рассинхрон за 90 дней

Выводы для архитекторов

Когда ES — это не «переусложнение»

Событийный журнал окупается не тогда, когда вы «хотите DDD», а когда у вас конкретный набор требований, который невозможно закрыть CRUD-моделью.

01

Аудит и откат

Если регулятор требует показать состояние сущности на конкретный момент (а не «как оно было вчера по бэкапу»), только журнал даёт это без потери данных. CQRS с таблицей — нет.

use-case: finance · legal · medtech
02

Пересборка read-model

Добавили новое поле в read-model? В CQRS пишете миграцию и бэкуете данные. В ES — пересобираете проекцию из журнала за минуты, без риска «потерять» что-то по пути.

cost: 42s на 10M событий
03

Когда ES — это переусложнение

Если у вас CRUD-админка, 500 записей в день и нет требований к аудиту — берите обычную БД с транзакциями. ES здесь добавит сложность без пользы. Честно.

verdict: skip it · use ACID

Практический чек-лист

Три вопроса перед тем, как тянуть ES

1. Нужна ли вам история состояния, а не только текущее? Если да — журнал обязателен. Если нет — обычная БД дешевле и проще.

2. Готовы ли вы к тому, что read-model — это «кэш», а не источник? В ES вы сознательно принимаете, что read-model может отставать на миллисекунды и что её можно пересобрать. Если команда это не принимает — ES будет болеть.

3. Есть ли у вас метрики рассинхрона? Без мониторинга lag между журналом и read-model вы не узнаете о проблеме, пока её не заметит пользователь. SyncOps даёт этот мониторинг из коробки — lag, checkpoint-age, backlog — в OpenMetrics и трейсах каждого сообщения.

Подключить мониторинг

Следите за lag между журналом и read-model в реальном времени

SyncOps отслеживает checkpoint, backlog и рассинхрон между источником и проекцией. Алерт приходит до того, как пользователь заметит, что данные «не те». Подключите первый пайплайн за 30 минут.

Выбрать тариф Читать документацию Без карты · 14 дней · отмена в один клик