CQRS (Command Query Responsibility Segregation)
Practical example
An order service accepts commands against a normalized Postgres schema with an outbox table; a Debezium connector streams committed order events into Kafka, which a projector consumes to maintain a denormalized 'order search' index in Elasticsearch used by customer support and a separate rolled-up 'account summary' table in Redis used by the mobile app dashboard.
CQRS partitions a service’s data-access surface into two distinct code paths: a command side that validates business invariants and mutates authoritative state, and a query side that serves reads from one or more denormalized projections. The command side typically writes to a normalized store or an event log (frequently paired with Event Sourcing, though the two are not required to co-occur). The query side subscribes to the resulting change stream — via CDC, an outbox relay, or domain events — and materializes purpose-built read models: a search index, a graph store, a flattened SQL table, or an in-memory cache keyed exactly to a UI’s access pattern.
The critical architectural consequence is that the read models are eventually consistent with the write model. There is a propagation delay between a command committing and every projection reflecting it, and this delay is variable under load, replication lag, or consumer backpressure. Systems must explicitly decide how to handle the read-after-write gap: sticky routing to the primary for the originating session, client-side version tokens compared against projection watermarks, or simply accepting stale reads with a documented SLA. Ignoring this gap is the single most common production incident vector — engineers assume the pattern behaves like a synchronous ORM and build UX or downstream logic that silently breaks under lag.
- Command side design: commands are intent-bearing, validated against invariants, and typically produce one canonical write plus zero or more domain events; concurrency control (OCC or a version column) prevents lost updates.
- Query side fan-out: a single command can drive N independent projections at different consistency lags — a search index might update in seconds, a data-warehouse rollup in hours. Each projection needs independent replay/rebuild tooling since schema changes require reprocessing the event history.
- Failure isolation: a broken projector should never block the command path; the outbox/queue between them must be durable and independently scalable, or the pattern degenerates into a distributed monolith with two synchronized failure domains instead of one.
- Idempotency: projectors consume from an at-least-once delivery substrate, so projection updates must be idempotent (upserts keyed by event ID/offset, not blind appends).
CQRS is frequently conflated with simple read-replica offloading. A read replica keeps the identical schema and identical query capability as the primary, just on separate hardware — it solves throughput, not shape mismatch. CQRS restructures the data model itself: the read side can be a different storage technology entirely (e.g., Elasticsearch for the query model backed by Postgres for the command model), which is what unlocks query patterns (full-text, graph traversal, multi-dimensional aggregation) that the write-optimized schema was never designed to serve.
The pattern’s cost is organizational and operational complexity: two schemas to version, a rebuild/replay pipeline for every projection, monitoring for consumer lag as a first-class SLO, and developer discipline to never let query-side code leak back into command-side invariant enforcement. It is justified only when read and write access patterns have genuinely diverged enough that a single model can no longer serve both without compromising one of them — applying it prophylactically on a CRUD service with symmetric read/write shapes adds two moving parts to maintain zero additional capability.