
Stop choosing databases by brand. Choose them by invariants, access patterns, and what “correct” means when the network is on fire.
Axel Domingues
In September 2021 I stopped treating “the database” as a tech choice and started treating it as a correctness boundary.
Because almost every production incident with data has the same root cause:
We scaled the system… but we didn’t scale the meaning of truth.
This post is a practical mental model for architects and tech leads:
It’s a framework for choosing data stores in a way you can defend in a design review — and operate when reality gets loud.
What you’ll be able to do after this
Pick a data store by invariants + access patterns, not vibes.
The key lens
“Consistency” is not ideology. It’s a contract about what can never be wrong.
The common failure
Teams scale reads/writes, but forget to scale correctness and operability.
The output
A short checklist you can apply to any feature: payments, carts, feeds, search, analytics.
Every system has a few sacred invariants — things the business expects to be true even on the worst day.
Examples:
Those invariants determine your data boundary more than:
If you can’t say what must never be wrong, you’re not choosing a database — you’re gambling.
A data store is not “where we put data.”
It is a contract about:
Your job as an architect is to align that contract with your product’s invariants.
“SQL vs NoSQL” is a bad debate.
A better framing is:
SQL is great when…
You need transactions, constraints, joins, and evolving queries.
NoSQL is great when…
You need scale-out reads/writes on predictable access patterns.
SQL hurts when…
You push it into “infinite scale” without designing partitioning and query discipline.
NoSQL hurts when…
You need cross-entity invariants and you pretend eventual consistency is “free.”
Here’s the non-negotiable truth:
Most data models fail because we model the world (“User”, “Order”, “Product”)…
…instead of modeling how the system is actually used.
Do this first:
Examples:
Examples:
Examples:
Now the database is a tool to satisfy the contract you just defined.
Relational databases have been battle-tested for decades because they’re very good at the hard part:
maintaining truth under concurrency.
If you can express an invariant as a constraint, do it.
Examples:
Why? Because the database enforces it even when your app is buggy.
NoSQL is a category, not a product.
Different NoSQL systems make different tradeoffs, but the common theme is:
When NoSQL shines:
But when your queries evolve and you need ad-hoc joins and constraints, you’ll either bolt on more systems… or move back toward relational semantics.
Consistency is not a checkbox.
It’s a question about what can be observed after a write, across:
Most good architectures are hybrid.
A highly reliable architecture often looks like this:
Examples:
Write path
Small, strict, transactional.
Enforces invariants.
Read path
Fast, denormalized, cached.
May be stale — by design.
This reduces the “God database” temptation while keeping truth centralized.
Denormalization is not wrong. It’s a performance tool.
But it creates a new job:
If you can’t answer those, your read model is a time bomb.
A highly available system that occasionally sells the same seat twice is not “high availability.” It’s a lawsuit generator.
Retries are correctness events.
If you don’t plan for retries:
Many teams say “we use SQL” and assume that means correctness is handled.
But isolation levels matter.
You don’t need to memorize these. You need to understand what races exist in your write paths.
Keep it short. Keep it transactional. Keep it auditable.
A data store choice is also an ops choice.
Backup + restore
How fast can we restore to a point in time?
Have we practiced it?
Migrations
Can we evolve schema without downtime?
What’s our rollback story?
Replication + failover
What happens during a node failure?
What does “primary” mean?
Observability
Can we see slow queries, lock contention, replication lag?
You have a temporary cache with a very expensive API.
Print this. Put it in your PR template. Use it in planning.
Designing Data-Intensive Applications (Kleppmann)
The best mental model book for replication, consistency, and distributed tradeoffs.
Jepsen Analyses
Reality checks for distributed systems guarantees. Read when you’re tempted to assume.
If your product has invariants (money, inventory, uniqueness, quotas), yes — default to SQL for the source of truth.
You can still use NoSQL for read models, caching, search, and projections. The pattern is “strong core + fast edges”.
No. “NoSQL” is a category.
Some systems offer strong consistency under certain configurations, and many offer tunable consistency. But the key is: you must understand the contract you’re buying — and what it does under partitions and failover.
When your data is mostly read/written as a single aggregate and you have predictable access patterns.
Documents shine for “get by ID and render”, multi-tenant partitioning, and cases where joins are rare. They hurt when invariants span multiple aggregates and queries evolve frequently.
Keep a transactional write core and scale reads with:
Make staleness explicit in UX and observability.
This month was about picking a data store with adult supervision:
Next month I’m zooming into the performance layer most teams misuse: Caching
Because once your data model is honest… your cache needs to be honest too.
Caching Without Folklore: Redis, CDNs, and the Two Hard Things
Caching is not “make it faster.” It’s a contract: what can be stale, for how long, for whom, and how you recover when it lies. This month is a practical architecture guide to caching layers that scale without corrupting truth.
RESTful Design That Survives: Resources, Boundaries, and Versioning
REST isn’t “JSON over HTTP.” It’s a set of constraints that make interfaces boring, predictable, and resilient under change. This month is about designing resource boundaries and contracts that survive growth — and using versioning only when you’ve earned it.