# When should I apply CQRS, and how do I sync the read and write models?

> Move reporting to a read replica and adopt CQRS only when query shapes outgrow one schema, feeding the read model from outbox events, not dual-writes.

- Asked: 2026-06-06
- Answered: 2026-06-09
- Asked by: Rüzgar
- Tags: veritabani, mimari, elasticsearch
- Source: https://www.muhammetsafak.com.tr/en/just-ask/when-to-apply-cqrs-and-syncing-read-and-write-models/
- Language: en-US
- Author: Muhammet Şafak

---
**Question:** In our app the write operations are very few (~5,000 records/day) but reads and complex reporting are very heavy (~2,000 queries/sec). The relational DB locks up because of the reporting queries. We want to fully separate the write model (PostgreSQL) and the read model (Elasticsearch or an optimized read-replica SQL) — CQRS.

How do I keep the two models in sync, event-driven, with no lag and reliably?


Short answer: 2k reads/sec against 5k writes/day — that asymmetry is a real CQRS case. But start with the cheapest version; don't jump straight to full CQRS.

The problem is clear: heavy reporting queries lock the write path too. Most of the time you don't need a full architectural split to fix that.

1. **Add a read replica first.** Put one or more read replicas on PostgreSQL and route all reporting queries there. Writes stay on the primary, reads go to the replica; the locking usually ends with just this. Don't take on CQRS complexity before trying it.
2. **Understand when full CQRS earns its keep.** A separate read model (Elasticsearch or denormalized SQL) is only worth it when query shapes diverge so much that one schema can't serve both well. Needs like full-text search or heavy aggregation — where even a replica isn't enough — are what justify splitting out the read model.
3. **Sync the models event-driven.** Have the write side emit an event on every change; a projector consumes those events to update the read model. For reliability, use the **outbox pattern**: write the event to an outbox table in the *same transaction* as the business data, and have a separate process publish it — so you never get "the DB committed but the event was lost."
4. **Accept eventual consistency, don't dual-write.** The read model lags the write by ms–seconds; design the UI for it (e.g. "saved" instantly, visible in the list a moment later). Never write to both stores from the app (dual-write) — they inevitably drift. Always drive the read model from the write side's events.

**Bottom line:** replicas first, move reporting there. Adopt full CQRS only when query needs genuinely diverge, with outbox-driven projections — and treat the added complexity as a cost. The answer to "is PostgreSQL enough for me?" is still yes under most loads; push it to its limit before reaching for CQRS.

## Related Reading

- [Is PostgreSQL Enough for Everything?](https://sade.dev/en/journal/is-postgresql-enough-for-everything) — sade.dev
- [Should I choose NoSQL or PostgreSQL for flexible product attributes?](https://www.muhammetsafak.com.tr/en/just-ask/nosql-vs-relational-for-flexible-product-attributes/) — Just Ask
- [How do I make search-as-you-type fast with edge n-grams in Elasticsearch?](https://www.muhammetsafak.com.tr/en/just-ask/search-as-you-type-performance-with-edge-ngram-in-elasticsearch/) — Just Ask
- [Multi-tenant SaaS database isolation: separate DB, schema, or tenant_id?](https://www.muhammetsafak.com.tr/en/just-ask/multi-tenant-saas-database-isolation-for-10k-tenants/) — Just Ask
