Select by workload evidence and disqualifying constraints before preferences or popularity.
Build a Database Selection Matrix from Queries, Invariants, Latency, Scale, Operations, and Skills
Turn AtlasMart workload evidence into a selection matrix that uses hard constraints before weighted preferences, so a high score cannot hide a fatal invariant, recovery, geography, or operations mismatch.
Derive selection criteria from concrete queries, commands, invariants, latency, scale, geography, recovery, security, operations, skills, and cost instead of product categories alone.
Separate hard disqualifiers from weighted preferences so a fatal transaction, residency, or restore mismatch cannot be averaged away by attractive secondary features.
Attach evidence to each candidate score and keep unknowns visible as experiments rather than optimistic assumptions.
Produce a repeatable decision record that can be revisited when workload, team, regulation, or provider economics change.
1. Start with AtlasMart decisions, not database brands
AtlasMart now has twenty-four chapters of mechanisms: transaction boundaries, replication, quorums, clocks, partitioning, storage engines, data models, search, consensus, conflict resolution, caching, change streams, security, recovery, performance and managed-service economics. The capstone begins by converting those mechanisms into a decision record. A selection matrix is not a beauty contest between products. It is a structured statement of which workload must be served, which facts must remain correct, what latency and recovery evidence is required, and what the team can operate.
For each command or query, record keys/predicates, cardinality, result size, ordering, frequency, p95/p99 target, consistency need and business invariant. Add data volume/growth, geography/residency, availability objective, Recovery Point Objective (RPO), Recovery Time Objective (RTO), security boundary, backup/restore needs, skill availability and cost range. Only then map candidates to evidence.
2. Hard constraints before weighted preferences
A disqualifying constraint is a requirement whose failure makes a candidate unacceptable even if it is excellent elsewhere. Examples include an order/payment invariant that needs an atomic boundary the proposed design does not supply, a regulated tenant that cannot be placed in the proposed region, or a restore path that cannot meet the tested RTO. Weighted scores are useful only among candidates that survive those gates.
| Criterion | Evidence | Hard constraint? | Example question |
|---|---|---|---|
| Invariant/transaction | failure history + transaction test | Often | Can checkout prevent double allocation under retries? |
| Query/access path | representative trace | Sometimes | Can 99% of reads stay partition-local? |
| Latency | p50/p95/p99 distribution | Sometimes | Does p99 meet SLO during node loss? |
| Recovery | restore/game-day evidence | Often | Can an isolated restore meet RPO/RTO? |
| Operations/skills | runbook + staffing evidence | Sometimes | Can the team repair, upgrade and observe it? |
| Cost | low/base/high TCO | Budget-dependent | What happens at 2× and 5× traffic? |
Score the requirement you tested, not the product reputation. “Fast,” “scalable,” “serverless,” “strongly consistent,” and “schema-less” are not measurements or complete guarantees.
3. Deliberately wrong approach: let a high total score hide a fatal mismatch
Suppose a low-latency key-value option wins a weighted matrix for the checkout ledger because it scores highly on latency, cost and operations. If the design then decomposes inventory, order and payment facts into independent keys without a correctness strategy for the required invariant, the weighted winner is invalid. The failure is in the decision method: a preference score was allowed to average away a correctness constraint.
The repair is to make the constraint explicit, eliminate or redesign candidates that cannot satisfy it, and preserve unresolved questions as experiments. A candidate can re-enter only when a concrete design changes the guarantee—for example, by moving the invariant into one atomic aggregate or selecting a transaction-capable boundary.
4. AtlasMart lab: constraint-first selection
Python 3.13+ standard library only. Candidate names are database classes, not product recommendations; scores are synthetic teaching inputs.
from dataclasses import dataclass
@dataclass(frozen=True)
class Candidate:
name: str
scores: dict
guarantees: set
criteria = {
"latency": 5,
"operational_fit": 4,
"query_fit": 5,
"recovery": 4,
"team_skill": 3,
"cost": 2,
}
candidates = [
Candidate("relational", {"latency":4,"operational_fit":5,"query_fit":5,"recovery":5,"team_skill":5,"cost":4}, {"multi_row_tx","joins","unique_constraints"}),
Candidate("document", {"latency":5,"operational_fit":4,"query_fit":4,"recovery":4,"team_skill":4,"cost":4}, {"single_document_atomic"}),
Candidate("key_value", {"latency":5,"operational_fit":5,"query_fit":2,"recovery":3,"team_skill":4,"cost":5}, {"single_key_atomic"}),
Candidate("wide_column",{"latency":5,"operational_fit":3,"query_fit":3,"recovery":3,"team_skill":2,"cost":4}, {"partition_local_atomic"}),
]
def weighted(c):
return sum(criteria[k] * c.scores[k] for k in criteria)
workloads = {
"checkout_ledger": {
"required": {"multi_row_tx","unique_constraints"},
"bonus": {"relational":12,"document":0,"key_value":0,"wide_column":0},
"notes": "inventory reservation + payment/order state must not split silently",
},
"catalog_page": {
"required": set(),
"bonus": {"relational":0,"document":18,"key_value":2,"wide_column":0},
"notes": "aggregate-oriented reads; eventual search projection acceptable",
},
}
print("NAIVE WEIGHTED RANKING")
for c in sorted(candidates, key=weighted, reverse=True):
print(c.name, weighted(c))
print("\nCONSTRAINT-FIRST SELECTION")
for workload, req in workloads.items():
print("workload=", workload, "--", req["notes"])
survivors=[]
for c in candidates:
missing=req["required"] - c.guarantees
effective=weighted(c)+req["bonus"].get(c.name,0)
print(f" {c.name:12} base={weighted(c):3} workload_bonus={req["bonus"].get(c.name,0):2} effective={effective:3} missing={sorted(missing)}")
if not missing:
survivors.append((effective,c))
winner=max(survivors, key=lambda x:x[0])[1] if survivors else None
print(" winner after disqualifiers:", winner.name if winner else "NONE")
print("\nUNKNOWN EVIDENCE REGISTER")
unknowns=[
("catalog_page","document","p99 under 10x catalog growth"),
("checkout_ledger","relational","restore test under regional failover"),
]
for row in unknowns:
print("experiment required:", row)
The program first prints a naive weighted ranking, then applies required guarantees per workload. For checkout, candidates lacking the stated multi-row transaction/unique-constraint requirements are visibly disqualified. It also leaves p99 and restore behavior as experiments instead of silently scoring unknowns.
5. Production judgment
Keep one matrix per bounded workload, not one giant score for the company. An orders ledger, search projection, fraud graph and telemetry feed have different invariants and query shapes. The answer may be one database for several workloads or several databases with explicit ownership; both are acceptable if the evidence supports them.
Version the matrix alongside architecture decisions. Re-run critical experiments after major engine upgrades, topology changes, new regions, regulatory changes, traffic shifts or team turnover. The next lesson applies the same discipline to a common failure of judgment: adopting NoSQL where a relational database already gives the simplest invariant and query boundary.
Check your understanding
- Why should a hard constraint be evaluated before a weighted score?
- What belongs in the evidence column of a selection matrix?
- Why is “supports transactions” too vague for selection?
- When should a candidate remain “unknown” rather than receive a hopeful score?
- What makes the matrix maintainable?
Review the answers
1. Because a high aggregate score cannot compensate for a fatal mismatch such as an invariant the candidate cannot preserve, forbidden data placement, or an unachievable recovery objective.
2. Workload traces, benchmark distributions, restore tests, failure drills, documentation for exact semantics, operational skill evidence, cost ranges, and unresolved experiments.
3. Transaction scope, isolation, cross-partition behavior, latency, failure semantics, and limits differ; the invariant must be mapped to the exact guarantee.
4. When the team lacks reproducible evidence for a material requirement such as tail latency, restore time, global-write semantics, or operational ownership.
5. Explicit assumptions, dated evidence, disqualifiers, weights, experiment links, and a trigger for reassessment when workload or constraints change.
References
Foundational and current implementation references used for this lesson:
- PostgreSQL 18 — Transactions — Current official transaction semantics used as one relational implementation anchor.
- PostgreSQL 18 — Logical Replication — Current official snapshot-plus-change replication model relevant to migration and synchronization.
- Debezium 3.6 release series — Latest stable 3.6 line; 3.6.1.Final was released 2026-08-04. Optional CDC implementation anchor only.
- Apache Cassandra downloads — Current official release page listing Cassandra 5.0.9 as latest GA on 2026-08-07.
- Google Bigtable paper — Primary research showing a system optimized around a specific distributed structured-data model and workload target.
- Dynamo: Amazon’s Highly Available Key-value Store — Primary research illustrating explicit availability, partitioning, replication and conflict tradeoffs rather than universal database guarantees.