Допустим, операционная база должна выдерживать частые изменения, а сложные аналитические запросы замедляют рабочие операции. Как разделить эти нагрузки на уровне модели взаимодействия с системой?
Используйте CQRS — разделение моделей и путей обработки команд и запросов. Команды изменяют операционную модель, а запросы читают специализированную модель, оптимизированную для аналитики.
Такой подход уменьшает конкуренцию за ресурсы основной базы, но обычно приводит к временной согласованности данных между моделями. CQRS оправдан, когда модели чтения и записи имеют существенно разные требования, а не просто ради формального разделения интерфейсов.
Подход появился как ответ на ситуацию, когда одна модель данных одновременно должна была поддерживать транзакционные изменения и многочисленные сложные сценарии чтения. Эти задачи предъявляют разные требования к структуре данных, индексам, масштабированию и скорости обработки.
Идея отделяет операции, намеренно изменяющие состояние, от операций, только читающих состояние. В простых системах это разделение может оставаться логическим, но при росте нагрузки оно часто приводит к отдельным моделям или хранилищам.
Операционная модель обычно нормализована и защищает бизнес-инварианты. Аналитическим запросам, напротив, могут требоваться объединение большого числа сущностей, предварительно рассчитанные показатели и чтение по другим ключам.
Если выполнять такие запросы непосредственно на рабочей базе, аналитическая нагрузка может конкурировать с командами за процессор, память, соединения и дисковый ввод-вывод. Если бездумно дублировать данные, возникают расхождения, неясный источник истины и сложность восстановления модели чтения.
В CQRS выделяют два типа операций:
Операционная модель остаётся источником истины для изменяемых данных. Модель чтения строится из изменений операционной модели: синхронно в рамках одной операции либо асинхронно через события, журнал изменений или другой надёжный механизм доставки.
При асинхронном обновлении между записью и появлением данных в модели чтения существует задержка. Поэтому требования должны явно описывать допустимую свежесть данных: например, пользовательский экран может принять небольшую задержку, а проверка доступного остатка перед оформлением заказа — нет.
Нужно определить, как обнаруживаются и устраняются сбои обновления. Практические меры включают повторную обработку, идемпотентность построения проекции, контроль версии события, мониторинг отставания и возможность полного пересоздания модели чтения.
CQRS не требует автоматически применять микросервисную архитектуру, брокер сообщений или Event Sourcing. Он также не означает, что для каждого экрана обязательно создаётся отдельная база. Разделение следует вводить только там, где различие требований приносит пользу, иначе стоимость синхронизации превысит выигрыш.
Главный компромисс — обмен простой строгой согласованности на независимое масштабирование и удобство специализированного чтения. Для операций, где решение должно основываться на только что записанном состоянии, нужно либо читать операционную модель, либо обеспечить явную проверку актуальности проекции.
В системе управления доставкой рабочие операции изменяли заказы и статусы перевозок, а аналитический экран строил отчёты по маршрутам, регионам и временным интервалам. Выполнение отчётов на той же нормализованной базе увеличивало задержки оформления изменений.
Рассматривались три варианта. Оптимизация запросов и индексов была наименее сложной, но не устраняла конкуренцию нагрузок. Полная реплика базы для чтения снижала давление на основной экземпляр, однако не давала удобной структуры для сложных отчётов. Полное дублирование бизнес-логики в аналитическом контуре ускоряло чтение, но создавало риск расхождения правил.
Выбрали CQRS: команды продолжили работать с операционной моделью, а отдельная проекция хранила агрегированные данные для отчётов. Для экранов аналитики явно указали допустимую задержку обновления, а для критических проверок использовали операционную модель. В результате чтение и запись масштабировались независимо, а границы ответственности моделей стали явными.
Нет. CQRS требует разделить ответственность за команды и запросы, но физическое разделение может отсутствовать. На раннем этапе достаточно разных сервисных компонентов или представлений в одной базе, если это не нарушает требования к нагрузке и согласованности.
Разные хранилища полезны, когда модели требуют разных способов индексации, масштабирования или форматов хранения. Однако они увеличивают стоимость эксплуатации, резервного копирования, мониторинга и восстановления.
Обновление проекции должно быть идемпотентным либо защищённым от повторного применения. Потребитель может хранить обработанные идентификаторы изменений, проверять версии сущности или применять операции так, чтобы повтор не менял итоговое состояние.
Одного контроля дубликатов недостаточно, если изменения могут приходить не по порядку. Тогда нужна проверка последовательности или версии, а пропущенные изменения должны быть дозапрошены либо проекция должна быть пересобрана из надёжного источника.
Нет. Медленный запрос сначала следует проверить на ошибки модели, плана выполнения, индексов, объёма данных и формулировки требования. CQRS добавляет второй жизненный цикл данных и проблему задержки согласования, поэтому он не заменяет обычную оптимизацию.
Подход оправдан, когда чтение и запись действительно имеют разные модели нагрузки или структуры, а выгода от независимой проекции устойчиво превышает стоимость её поддержки. Если проблема решается индексом или изменением запроса, CQRS будет избыточным.