DB Choice
Choosing the right database starts with access patterns
Don't pick a database and then figure out if it fits. Start with what the system actually does to data — the reads, the writes, the queries — and let that drive the choice.
The access patterns¶
There are two core access patterns for message storage:
Write pattern — send a message:
INSERT message INTO conversation C1
sender = A, content = "hey", timestamp = T
Read pattern — load chat history:
FOR conversation C1
GIVE ME last 20 messages
BEFORE timestamp T
SORTED chronologically
These two patterns tell you everything you need to know about what the database must be good at: - Fast sequential writes (appends) - Fast range reads within a single partition key
Write volume — why single-instance relational DBs fail¶
Peak write QPS → 20k writes/sec
Single Postgres instance → 5k–10k WPS (with indexes, constraints, WAL overhead)
A single Postgres instance cannot sustain 20k WPS. You'd need to shard it manually — partition the data across multiple instances, build a routing layer, manage cross-shard consistency, handle rebalancing when nodes are added. Postgres was not designed for this. It becomes an operational nightmare at this scale.
Why Postgres is rejected¶
Postgres is a general-purpose relational database. Its strengths are: - Complex multi-table joins - Flexible ad-hoc queries - ACID transactions across multiple rows and tables - Rich indexing (B-tree, GIN, partial indexes)
None of these strengths are needed here. The access patterns are simple and uniform — always by conversation_id, always a range scan on timestamp. No joins. No aggregations. No flexible queries. Paying the overhead of a relational DB (WAL, MVCC, constraint checking) for a workload that doesn't need any of it makes no sense.
MongoDB is rejected for the same reason — it adds document model flexibility (nested JSON, dynamic schema) that chat messages simply don't need.
Why Cassandra and DynamoDB win¶
Both are wide-column stores with the same fundamental data model:
Partition Key → identifies which node holds the data (conversation_id)
Sort Key → orders data within the partition (timestamp)
This maps perfectly to the access patterns:
- Write: append a new row under conversation_id partition — O(1), sequential write to LSM tree
- Read: range scan within conversation_id partition — contiguous on disk, fast sequential read
Both use LSM trees (Log-Structured Merge trees) under the hood. LSM trees are optimised for write-heavy workloads — writes go to an in-memory buffer first (MemTable), then flush to disk as sorted files (SSTables). No random disk seeks on writes. Perfect for append-heavy chat data.
Write throughput:
Postgres (single instance) → 5k–10k WPS
Cassandra / DynamoDB → 100k+ WPS
20k peak WPS is well within range. No manual sharding needed for throughput.
Cassandra vs DynamoDB¶
Both are technically correct choices. The difference is operational:
Cassandra:
→ Self-managed (or via DataStax)
→ Full control over compaction, replication factor, consistency level tuning
→ More operational overhead — you run the cluster, you handle failures
→ Better for teams with Cassandra expertise
DynamoDB:
→ Fully managed by AWS
→ No cluster to tune, no compaction to worry about, no node failures to handle
→ Pay-per-request or provisioned capacity
→ Less control, more convenience
Choice: DynamoDB. The data model fits identically. At this scale, not managing a Cassandra cluster is a significant operational advantage. The engineering team can focus on the application, not the database infrastructure.
Interview framing
"The access patterns are simple — append writes and range reads within a conversation. That rules out Postgres and Mongo — too much overhead for queries we don't need. Cassandra and DynamoDB both fit perfectly: partition key on conversation_id, sort key on timestamp, LSM trees for fast sequential writes. I'd pick DynamoDB over Cassandra to avoid managing the cluster — same data model, less operational burden."