9 min read
System Design Index
Start Here
Tier 1 -- Building Blocks
Scale Reads
Scale Writes
Database Selection
Traffic Control
Consistency & Coordination
Estimation
Tier 2 -- Core Systems
Tier 3 -- Location & Real-Time
Tier 4 -- Infrastructure & Data
Tier 5 -- Finance & Commerce
Tier 6 -- Advanced & Collaborative
System Design Index
Start Here
Tier 1 -- Building Blocks
Scale Reads
Scale Writes
Database Selection
Traffic Control
Consistency & Coordination
Estimation
Tier 2 -- Core Systems
Tier 3 -- Location & Real-Time
Tier 4 -- Infrastructure & Data
Tier 5 -- Finance & Commerce
Tier 6 -- Advanced & Collaborative
SQL Databases (PostgreSQL, MySQL) — ACID, Joins, Strong Consistency
Tier 1 — Building Block
1. What Is It?
A relational database stores data in tables with predefined schemas, enforces relationships between tables (foreign keys), and provides SQL as a query language for flexible data retrieval including multi-table JOINs, aggregates, and subqueries. The defining property is ACID: Atomicity, Consistency, Isolation, — a guarantee that transactions execute as all-or-nothing, leave the database in a valid state, are isolated from concurrent transactions, and survive crashes.
SQL databases are the default starting point for most application data. They handle reads, writes, complex queries, and transactional consistency on a single node with battle-tested . The question is never "should I use a relational database?" — it's "when do I need to move beyond one?"
A payment processing service is transferring $500 from a user's checking account to their savings account. Midway through the operation, the database server crashes. When it recovers, the $500 has been deducted from checking but never credited to savings. Which ACID property was violated?
2. How It Works
ACID Properties
| Property | What It Means | Mechanism |
|---|---|---|
| Atomicity | All operations in a transaction succeed, or none do | Write-Ahead Log (WAL) — on crash, incomplete transactions are rolled back by replaying WAL |
| Consistency | Database moves from one valid state to another (constraints, foreign keys respected) | Constraint checking at commit time; referential integrity enforcement |
| Isolation | Concurrent transactions don't see each other's incomplete work | MVCC (Multi-Version Concurrency Control) — each transaction sees a snapshot of data |
| Durability | Committed transactions survive crashes | WAL flushed to disk (fsync) before commit acknowledgment |
MVCC (Multi-Version Concurrency Control)
Both PostgreSQL and MySQL/InnoDB use MVCC to allow concurrent reads and writes without locking.
MVCC = readers don't block writers; writers don't block readers. The tradeoff: old versions accumulate and must be periodically cleaned up (VACUUM in PostgreSQL, InnoDB purge in MySQL).
Query Execution: Index Lookup
PostgreSQL vs. MySQL Key Differences
| Feature | PostgreSQL | MySQL (InnoDB) |
|---|---|---|
| Data types | Richer: JSONB, arrays, ranges, enums, custom types | Standard SQL types; JSON (less optimized than PG) |
| JSONB | First-class JSONB with GIN indexes — powerful hybrid | JSON column type, limited indexing |
| Full-text search | Built-in FTS with tsvector/tsquery, GIN indexes | Full-text indexes, less flexible than PG |
| Replication | Streaming WAL replication (physical) + logical replication | Binlog-based; Group Replication; InnoDB Cluster |
| VACUUM | Explicit periodic vacuum for dead tuple cleanup | Background purge thread (more automatic) |
| Concurrent DDL | Limited — many DDL operations lock the table | More online DDL support (ALTER TABLE non-blocking for some ops) |
| Performance | Generally faster for complex queries; better query planner | Generally faster for simple read/write; better at high-connection-count workloads |
| Community vs. ecosystem | Open-source; owned by community | Open-source core; Oracle-backed (MySQL); MariaDB fork |
| Best for | Complex queries, JSONB, analytics, geospatial (PostGIS) | High-connection OLTP, read replicas at massive scale, web apps |
Transaction T1 begins and takes a snapshot of the database at timestamp 100. While T1 is still running, Transaction T2 updates a row and commits at timestamp 101. When T1 subsequently reads that same row, what value does it see and why?
3. Variants & Comparisons
Isolation Levels
SQL standard defines 4 isolation levels. Higher isolation = fewer anomalies, more locking/blocking.
| Level | Dirty Read | Non-Repeatable Read | Phantom Read | Common Use |
|---|---|---|---|---|
| Read Uncommitted | Possible | Possible | Possible | Almost never used |
| Read Committed | Not possible | Possible | Possible | PostgreSQL default |
| Repeatable Read | Not possible | Not possible | Possible | MySQL InnoDB default (prevents phantom reads too via gap locks) |
| Serializable | Not possible | Not possible | Not possible | Financial transactions; correctness-critical operations |
PostgreSQL implements MVCC-based Serializable Snapshot Isolation (SSI) — full serializability without pessimistic locking.
When SQL Scales (with standard techniques)
| Challenge | Solution | Scale Ceiling |
|---|---|---|
| Read throughput | Read replicas | 10–15× read scale |
| Write throughput | Vertical scale (bigger instance) | ~100K writes/sec on largest instances |
| Dataset size | Vertical scale + tablespace | ~10TB practical per node |
| Complex queries | Query optimization, partial indexes, materialized views | Depends on query complexity |
| Connection overload | PgBouncer/ProxySQL connection pooling (sharing a pool of database connections across many app threads, instead of each opening its own) | Handles thousands of app-level connections |
When SQL Doesn't Scale
| Constraint | Symptom | Solution |
|---|---|---|
| Write throughput ceiling | Primary CPU > 90% on writes at peak | Sharding (functional decomposition first; hash/range sharding if needed) |
| Dataset size > node capacity | Disk I/O bottleneck; B-tree depth increases | Sharding, archival, tiered storage |
| Schema flexibility needed | Frequent ALTER TABLE migrations as data evolves | Document store (MongoDB) or JSONB columns |
| Massive horizontal write scale | 500K+ writes/sec | Wide-column store (Cassandra) or NewSQL |
A fintech application processes bank transfers and requires that concurrent transactions produce results identical to running them one at a time — no dirty reads, no non-repeatable reads, and no phantom reads. However, the team wants to avoid the performance bottleneck of traditional pessimistic locking. Which isolation level and implementation approach satisfies both requirements?
4. When to Use It (and When NOT To)
Use SQL When:
- Relational data with JOINs: Users have orders; orders have items; items have categories — relational structure maps naturally to SQL tables.
- ACID transactions required: Transfer money between accounts; place an order that atomically reduces inventory and creates a payment — multi-table atomicity is a SQL strength.
- Complex ad-hoc queries: Analytics, reporting, "give me all users who bought product X in the last 30 days who haven't bought product Y" — SQL is expressive and flexible.
- required: Financial balances, booking reservations, inventory counts — you cannot tolerate stale reads.
- Most web applications: CRUD operations, user records, content management — SQL handles 95% of web application data needs.
Decision triggers:
- "Default to PostgreSQL unless you have a specific reason to choose otherwise"
- "If you need ACID transactions that span multiple tables → SQL"
- "If your data is relational (entities with relationships) and fits within one node → SQL"
Do NOT Use SQL When:
- Massive write (>100K writes/sec sustained): A single SQL node can't keep up — use Cassandra, , or shard.
- Very large unstructured/semi-structured data: 10+ columns that are frequently null or vary per record — use a document store or add JSONB columns (PostgreSQL handles this better than most).
- Pure key-value lookups with sub-ms requirement: Use Redis; SQL can't match in-memory .
- Hierarchical or graph-heavy relationships: 6+ levels of self-referential joins kill SQL performance. Use a graph database (Neo4j).
Anti-patterns:
- N+1 queries: Loading N parent records and then making N separate queries for their children. Fix: JOIN in one query or use
WHERE id IN (...)batch query. - SELECT * in production: Fetches all columns including large ones (BLOB, TEXT) you don't need. Always SELECT only needed columns.
- No indexes on foreign keys and common WHERE columns: Table scans kill performance at scale. EXPLAIN ANALYZE every slow query.
- Large transactions holding locks: A transaction that holds a row lock for seconds while doing business logic blocks all concurrent writers. Keep transactions short.
- Using SQL for queue/polling pattern:
SELECT ... FOR UPDATE SKIP LOCKEDworks but is inefficient at scale compared to a real queue (Redis, SQS). Don't build a job queue in PostgreSQL unless it's truly low-volume.
Your team is building an e-commerce backend. When a customer places an order, the system must simultaneously reduce inventory counts, create an order record, and record a payment — all as a single atomic operation. Which database choice is most appropriate for this requirement?
5. Real-World Usage
Instagram (PostgreSQL at scale): Instagram ran PostgreSQL as their primary database for years, by user_id at the application layer. They used PostgreSQL for user profiles, photos metadata, follower graphs (early), and direct messages. When they hit the single-node write ceiling, they sharded horizontally — but kept PostgreSQL as each shard's engine. As of Meta's ownership, Instagram's core relational data still runs on PostgreSQL.
GitHub (MySQL at massive scale with Vitess): GitHub uses MySQL as their primary database, scaled with read replicas and eventually Vitess for horizontal . GitHub's "gh-ost" tool (GitHub's online schema change tool for MySQL) was open-sourced because large-scale MySQL table migrations are a genuine operational challenge. GitHub serves ~100M developers from a MySQL + Vitess core.
Airbnb (MySQL + PostgreSQL): Airbnb uses MySQL for its core transactional data (bookings, payments, users) and PostgreSQL for specific use cases needing JSONB, advanced indexes, or PostGIS (geospatial). Their booking system requires strict ACID transactions — a failed booking must atomically return inventory and void the payment. SQL's guarantees are non-negotiable here.
Airbnb's booking system requires that when a booking fails, inventory is returned and the payment is voided together — either both happen or neither does. Which database property makes a relational database the appropriate choice for this requirement?
6. Interview Cheat Sheet
Key sentences to demonstrate depth:
- "PostgreSQL is my default. ACID guarantees, mature query planner, JSONB for semi-structured data, -based streaming , and decades of production hardening. I only deviate when a specific constraint (write , schema flexibility, graph queries) pushes me elsewhere."
- "MVCC is how PostgreSQL achieves high concurrency: each transaction sees a snapshot of the database at its start time. Readers don't block writers; writers don't block readers. The cost is VACUUM — periodic cleanup of dead tuple versions."
- "For ACID transactions spanning multiple tables: BEGIN / your operations / COMMIT. If anything fails, ROLLBACK. This is the correct tool for 'charge the credit card AND reduce inventory AND create the order' — all or nothing."
- "The SQL scaling ladder: (1) Add indexes, (2) Connection pool (PgBouncer), (3) Read replicas for read scale, (4) Vertical scale, (5) Functional decomposition (split by service), (6) Horizontal . Exhaust each step before moving to the next."
- "The N+1 query problem is the most common SQL performance issue in practice: a loop in application code issues one query per item instead of batching. Fix with JOINs or WHERE IN (ids) batching."
Common follow-up questions:
| Question | Concise Answer |
|---|---|
| "What is VACUUM in PostgreSQL?" | PostgreSQL's MVCC leaves behind dead tuple versions (old rows after UPDATE/DELETE). VACUUM reclaims disk space and prevents transaction ID wraparound (a critical maintenance operation). autovacuum handles this automatically but can fall behind on write-heavy tables. |
| "PostgreSQL vs. MySQL — which do you prefer?" | PostgreSQL for complex queries, JSONB, analytics workloads. MySQL for high-connection OLTP, teams with existing MySQL expertise, or ecosystems tightly coupled to MySQL (WordPress, many ORMs). Both are excellent; team knowledge often drives the choice. |
| "What is an index and why does it help?" | A B-tree index is a sorted copy of a column (or columns) that enables O(log N) binary search instead of O(N) table scan. The cost: ~10–20% write overhead to maintain the index on every INSERT/UPDATE. |
| "What is connection pooling and why is it needed?" | Each PostgreSQL process uses ~5–10MB RAM. 10,000 connections = ~100GB RAM just for connections. PgBouncer sits in front and multiplexes many application connections through a smaller pool of actual database connections. |
| "When would you choose NoSQL over PostgreSQL?" | When write throughput exceeds ~100K/sec (Cassandra), when you need sub-ms latency (Redis), when your data model is highly variable schema or document-oriented (MongoDB), or when you need global distributed ACID (Spanner/CockroachDB). |
Connections to other building blocks:
- Write-Ahead Log: PostgreSQL and MySQL use for both crash recovery (ACID ) and (streaming WAL to replicas). WAL is the foundation of SQL database .
- Read Replicas: SQL databases scale reads via streaming replication to read replicas. PostgreSQL streaming replication is WAL-based; MySQL uses binlog replication.
- : SQL databases can be sharded (Instagram, GitHub), but it must be done at the application layer since most SQL databases don't shard natively. NewSQL (Spanner, CockroachDB) adds automatic sharding.
- Caching: Redis in front of PostgreSQL is the most common scaling pattern — absorb hot reads in Redis, keep the DB for writes and complex queries.
- CAP Theorem: SQL databases are CP (Consistency + Partition Tolerance). They sacrifice : if the primary is down, writes fail (unless you have automatic failover).
Glossary History
Click dotted jargon to save explanations here.
Glossary History
Click dotted jargon to save explanations here.