System Design ยท 1 of 5
Why "just pick Postgres" isn't always the answer, how B-Trees and LSM-Trees actually change your write performance, and what breaks when you shard a database without thinking about your query patterns first.
You're designing the data layer for a social app. User profiles: name, email, bio, a handful of fields that rarely change and need to stay consistent โ a user should never see their own profile half-updated. Activity feed: millions of "user X liked post Y" events per hour, written constantly, read in bulk, and honestly fine if a write shows up a second late.
If you reach for the same database for both, you'll eventually regret it. This is the actual decision behind "SQL vs. NoSQL" โ it's not a religious debate, it's a question of what your data looks like and how you need to read and write it.
| Dimension | SQL (e.g., PostgreSQL, MySQL) | NoSQL (e.g., MongoDB, Cassandra) |
|---|---|---|
| Data Model | Tabular (rows & columns) with static schemas. | Document, Key-Value, Columnar, or Graph. |
| Transactions | Strong ACID (Atomicity, Consistency, Isolation, Durability). | BASE (Basically Available, Soft state, Eventual consistency). |
| Scaling | Vertical (scale up CPU/RAM). Horizontal replication is read-heavy. | Horizontal (sharding out of the box) for high write/read volumes. |
| Joins | Native support for multi-table relationships. | Denormalization or manual application-level queries. |
For that user profile table, Postgres is the obvious choice โ you want ACID guarantees and the data is genuinely relational. For the activity feed, a columnar or key-value store built for write throughput is a better fit, because you're never going to run a complex JOIN against "who liked what" โ you're going to append and scan.
Once you've picked a database, the next thing that actually determines your performance under load is the indexing structure underneath it โ and this is where a lot of "why is Postgres slow on writes but Cassandra isn't" confusion comes from.
Relational databases like Postgres and MySQL use B-Trees (specifically B+ Trees).
NoSQL stores like Cassandra and RocksDB use Log-Structured Merge Trees.
This is exactly why the activity feed example above fits an LSM-Tree-backed store better: it's almost pure appends, which is the one thing LSM-Trees are built to do fast.
When a database becomes too slow or runs out of disk capacity, replication and sharding solve two different problems โ and mixing them up is a common planning mistake.
Copies database state across multiple nodes. This scales reads and adds resilience โ it does nothing for write capacity or storage capacity.
Splits data by row across multiple servers based on a Shard Key. This is what actually scales writes and total storage, at the cost of a lot of new complexity.
[ App Server ]
|
+---> Shard Key: ID % 2 == 0 ---> [ Database Node A (IDs 0, 2, 4) ]
|
+---> Shard Key: ID % 2 == 1 ---> [ Database Node B (IDs 1, 3, 5) ]
Scatter-Gather Queries: If your query doesn't include the shard key, the application has to query every shard and merge the results in memory. I've seen teams shard a database for scale, then keep writing "get all orders in the last 7 days across all customers" reports โ which turns every one of those reports into a full fan-out across every shard. Sharding only pays off if your actual query patterns respect the shard key.
hash(UserID) % NumberOfShards). Even distribution, but adding a shard means the modulo changes and almost all your data needs to be rehashed and moved.Sharding and replication both depend on routing requests to the right place reliably โ which is exactly what a load balancer does at the traffic layer. That's next.
Get an email when I publish the next chapter. No spam โ just new technical deep-dives.
Comments
Share feedback or questions about this blog post.
No comments yet. Be the first to share your thoughts.