Skip to main content
KBY Technologies Logo
cd ../lexicon
sys/docs/lexicon/cqrs-command-query-responsibility-segregation.md
Lexicon Entry

CQRS (Command Query Responsibility Segregation)

An architectural pattern that separates the model used to mutate state (commands) from the model used to read state (queries), allowing each to be optimized, scaled, and evolved independently. It matters because it decouples write-path invariants from read-path performance characteristics, which is otherwise impossible in a single normalized relational model serving both concerns.

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.