At a Glance
- RDBMS = ACID guarantees, SQL joins, structured schema. Postgres is the right default choice.
- Replication: master writes, replicas read. Lag is the cost; read scalability is the gain.
- Sharding distributes rows across servers by a shard key. Consistent hashing minimizes resharding.
- Denormalization deliberately duplicates data to eliminate joins at read time.
- NoSQL: key-value (Redis), document (MongoDB), wide-column (Cassandra), graph (Neo4j).
- Start with SQL + read replicas + caching. Add NoSQL only when a specific workload demands it.
Relational Databases & ACID
An RDBMS (PostgreSQL, MySQL, SQL Server…) organizes data into tables of rows and columns, related by keys and queried with SQL — including joins that combine tables on the fly. Their superpower is the transaction, guaranteed by ACID:
Atomic
All steps of a transaction happen, or none do. A money transfer never debits without crediting.
Consistent
Every transaction moves the DB from one valid state to another — constraints, foreign keys, and invariants always hold.
Isolated
Concurrent transactions behave as if they ran one at a time. No reading anyone's half-finished work.
Durable
Once committed, it survives crashes — written to a log on disk before the "OK" is returned.
Reach for an RDBMS when: data is structured and interrelated, you need complex queries and ad-hoc joins, or correctness is money — orders, payments, inventory, anything an accountant might audit.
Replication: Copies for Safety and Speed
Master-slave (primary-replica)
The primary accepts all writes and streams its change log to read-only replicas. You scale reads by adding replicas, and a replica can be promoted if the primary dies (manually, or via automated election — which takes seconds to minutes).
The catch is replication lag: a user updates their avatar (write → primary), refreshes (read → replica), and sees the old avatar. Mitigations: read-your-own-writes routing (send a user's reads to the primary briefly after they write), or synchronous replication (safe but slow).
One writer, many readers: the change log flows continuously from primary to replicas.
Master-master
Multiple primaries, each accepting writes and syncing with the others. No failover dance and writes survive a node loss — but concurrent writes to the same row on different masters conflict. You'll need last-writer-wins (lossy), conflict-free data types (CRDTs), or app-level merge logic. Loosely-coupled multi-region systems use this; most OLTP apps avoid it.
Federation: Split by Function
Federation (functional partitioning) splits one monolithic database into several by domain: a users DB, an orders DB, a products DB. Each is smaller, with its own cache-friendly working set and independent write capacity.
Costs: cross-database joins are gone — you join in application code or duplicate reference data. Transactions spanning two databases require distributed-transaction machinery (or, more realistically, the saga pattern from Topic 10). This split also maps neatly onto microservice boundaries.
Sharding: Split by Rows
When even a single-purpose table outgrows one machine, sharding distributes its rows across servers by a shard key:
- Hash-based:
shard = hash(user_id) % N. Even distribution, but range queries scatter across all shards, and changing N is painful. - Range-based: users A–H on shard 1, I–P on shard 2… Great for range scans; risky for hotspots (everyone signing up today lands on the "newest IDs" shard).
- Directory-based: a lookup service maps each key to its shard. Maximum flexibility (move tenants individually), plus one more critical service to keep alive.
The pain points
- Hotspots: a bad key concentrates traffic. The celebrity problem: Rihanna's row gets a million times the reads of yours — shard-by-user puts her whole load on one box.
- Resharding: with naive
hash % N, going from 4 to 5 shards remaps ~80% of all keys. Migration nightmare. - Cross-shard operations: joins, transactions, and unique constraints across shards all require application gymnastics.
Consistent hashing
The standard fix for resharding: place shards and keys on a conceptual ring; each key belongs to the next shard clockwise. Adding a shard moves only ~1/N of keys instead of nearly all of them. Virtual nodes (each server appears many times on the ring) smooth the distribution. This is the heart of DynamoDB and Cassandra — and of how CDNs and distributed caches assign keys.
Denormalization: Buy Reads with Writes
Normalization removes duplicate data; denormalization deliberately adds it back to kill expensive joins. Store author_name directly on each post, keep a precomputed follower_count, maintain a separate read-optimized table per view.
Reads become single-row lookups. The price: every write must now update multiple copies, and copies can drift out of sync. Once data is federated or sharded (joins across servers ≈ misery), denormalization stops being optional and becomes the norm.
Before sharding anything, do the cheap stuff: add indexes matching your query patterns (and covering indexes so queries never touch the table), run EXPLAIN on slow queries, kill N+1 query loops (fetch 100 posts, then 100 author queries — use one join or batch fetch), avoid SELECT *, and batch your inserts. A week of tuning routinely buys a year of growth.
NoSQL: The Four Families
"NoSQL" is a marketing umbrella for databases that trade relational guarantees for scale, flexibility, or specialized shapes. Four families matter:
Key-Value
Redis, DynamoDB, Riak. A giant hash map: get/put by key, O(1), no schema. Perfect for sessions, carts, feature flags, caching. Useless for "find all users where…".
Document
MongoDB, CouchDB, Firestore. Values are structured JSON-ish documents you can query into. Flexible per-document schema. Great for user profiles, product catalogs, CMS content.
Wide-Column
Cassandra, HBase, Bigtable. Rows with millions of sparse columns, partitioned and sorted for sequential scans. Built for massive write volumes: time-series, IoT telemetry, message history, analytics.
Graph
Neo4j, Amazon Neptune. Nodes and edges as first-class citizens; traversals ("friends of friends who like jazz") that would be brutal multi-joins in SQL run naturally. Social graphs, fraud rings, recommendations.
BASE: NoSQL's looser promise
Where ACID promises correctness, BASE promises resilience: Basically Available (the system answers, even during failures), Soft state (data may be in flux as replicas sync), Eventually consistent (replicas converge, given time). It's the AP corner of CAP wearing a database costume.
SQL vs NoSQL: The Decision Guide
| Question | Leans SQL | Leans NoSQL |
|---|---|---|
| Data shape | Structured, interrelated tables | Flexible / nested / evolving schema |
| Queries | Ad-hoc, joins, aggregations | Known access patterns by key |
| Correctness needs | Multi-row transactions (money!) | Per-item atomicity is enough |
| Scale | Up to a few TB / one big node + replicas | Horizontal from day one, petabytes |
| Write volume | Moderate | Extreme (time-series, events, logs) |
| Examples | PostgreSQL, MySQL | DynamoDB, Cassandra, MongoDB, Neo4j |
Stripe and most banks live on PostgreSQL/MySQL — ledgers demand ACID. Discord moved trillions of messages from MongoDB to Cassandra, then to ScyllaDB — wide-column for write-heavy history. Netflix pairs Cassandra (viewing data) with caches; Amazon's retail cart is the original DynamoDB story. Notice the theme: polyglot persistence — one company, many databases, each chosen per workload.
The safest interview default: "Start with PostgreSQL plus read replicas and caching. Shard or add a specialized NoSQL store only when a specific workload demands it." Boring databases are a feature, not a cop-out — just show you know where the cliff edges are.
Transaction Isolation Levels — The Full Picture
ACID's "I" hides a dial, not a switch. Without proper isolation, four anomalies can occur:
Dirty Read
A transaction reads uncommitted data from another concurrent transaction — data that may be rolled back and never have existed.
Non-Repeatable Read
Read the same row twice in one transaction, get different values — another transaction committed in between.
Phantom Read
Re-execute a range query and get more rows — another transaction inserted rows between your reads.
Lost Update
Two transactions read-modify-write the same row; one silently overwrites the other's change.
The four levels, strictest to most permissive
| Level | What it guarantees | Notes |
|---|---|---|
| SERIALIZABLE | Transactions behave as if run sequentially. Prevents all anomalies. | Slowest — locks or aborts conflicting transactions. |
| REPEATABLE READ | Same reads within a transaction always return the same data. Prevents dirty + non-repeatable reads. | Phantoms still possible. MySQL InnoDB default. |
| READ COMMITTED | Can only read committed data. Prevents dirty reads. | Non-repeatable reads possible. Postgres default. |
| READ UNCOMMITTED | Can read uncommitted ("dirty") data. | All anomalies possible. Fastest, rarely used. |
Why READ COMMITTED still bites: the bank transfer
-- Two concurrent withdrawals, READ COMMITTED (Postgres default)
BEGIN;
SELECT balance FROM accounts WHERE id = 1; -- both transactions read 100
-- both check 100 - 80 >= 0 in app code … both proceed!
UPDATE accounts SET balance = balance - 80 WHERE id = 1;
COMMIT; -- account ends at -60: a classic lost-update / double-spend
-- Fix: lock the row at read time
BEGIN;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE; -- 2nd tx blocks here
UPDATE accounts SET balance = balance - 80 WHERE id = 1;
COMMIT;
Indexes — How They Really Work
B-Tree index
A balanced tree sorted by key. Perfect for =, <, >, BETWEEN, ORDER BY, and range queries. The default almost everywhere.
Hash index
Only exact equality (=). Faster than a B-Tree for single lookups, but no range queries at all.
Composite index
CREATE INDEX ON orders(user_id, created_at). Leftmost prefix rule: works for (user_id) and (user_id, created_at), but NOT for (created_at) alone.
Covering index
The index includes every column the query needs — the query never touches the table (index-only scan). Extremely fast.
Partial index
Index only rows matching a condition: CREATE INDEX ON orders(user_id) WHERE status = 'pending'. Smaller and faster for filtered queries.
EXPLAIN ANALYZE
Always run it on slow queries. Look for: Seq Scan (full table scan — may need an index), nested loop vs hash join, and row-count estimates that are wildly off.
The N+1 problem
-- Bad: 1 query for posts + N queries for authors (N+1)
SELECT * FROM posts;
-- for each post:
SELECT * FROM users WHERE id = ?; -- N times
-- Good: 1 JOIN
SELECT posts.*, users.name FROM posts
JOIN users ON posts.user_id = users.id;
Sharding Strategies Deep Dive
The earlier section introduced the three families; here's the full trade-off analysis, plus the consistent hashing mechanics.
Hash-based sharding
shard = hash(user_id) % N
- Pro: even distribution for random IDs.
- Con: range queries hit all shards; resharding remaps ~(N−1)/N of all keys.
- Con: the "celebrity problem" — Rihanna's user row lives on shard 2, but data for her 100M followers' interactions piles onto shard 2 too → hot shard.
Range-based sharding
user_id 1–1M → shard 1, 1M–2M → shard 2, …
- Pro: range queries hit exactly one shard.
- Con: new users all land on the last shard — monotonic IDs create a hotspot.
- Fix: use UUIDs or ULIDs (time-sortable UUIDs) to spread inserts.
Directory-based sharding
A lookup table maps each key → its shard.
- Pro: move any key to any shard individually. Ideal for multi-tenant SaaS — move the whale customer to a dedicated shard.
- Con: an extra hop to the lookup table; the lookup table itself is a SPOF unless replicated.
Consistent hashing, illustrated
Keys and servers live on a hash ring (0 to 2³²). Adding S5 migrates only the keys between S5 and its predecessor — ~1/N of the total.
- Rule: each key belongs to the next server clockwise on the ring.
- Adding a server: only keys between the new server and its predecessor migrate (~1/N of total keys).
- Virtual nodes: each physical server takes K virtual positions on the ring (K=150 is typical) for a much smoother distribution.
- Used by: DynamoDB, Cassandra, Riak, and the Chord DHT.
Instagram's Postgres Sharding Journey
Instagram launched on a single Postgres instance. Three months later: 30M users. Time for horizontal sharding.
All of a user's data lives on the same shard → user timeline queries hit exactly one shard.
All tables are prefixed with shard_id; lookups use (shard_id, local_id) instead of a global_id.
Custom 64-bit IDs encoding (shard_id, 13 bits) + (timestamp, 41 bits) + (sequence, 10 bits). IDs are globally unique AND encode the shard directly — no lookup table needed.
Design your ID scheme before you need to shard. Retrofitting sharding onto auto-increment IDs is painful.
NewSQL — Distributed ACID
NoSQL gave up ACID for scale. NewSQL asks: can we have both?
CockroachDB
Distributed, Postgres-compatible. Automatic sharding, Raft consensus per range, serializable isolation. Follows Google Spanner's model.
TiDB
Distributed, MySQL-compatible. Separate storage (TiKV) and compute (TiDB) layers. Used by Shopee and Square.
Google Spanner
Multi-region ACID with TrueTime (GPS + atomic clocks bound clock uncertainty). Runs Google Ads, banking workloads, Gmail contacts.
NewSQL adds roughly 2–5ms of latency per write (consensus overhead) versus ~0.5ms on local Postgres. Worth it for global multi-region systems that genuinely need ACID — overkill for a single-region app.
Database Connection Pooling
The problem: opening a DB connection takes ~10–20ms (TCP + auth + SSL). 1,000 concurrent web requests × 20ms = 20 seconds of pure connection overhead. Worse, PostgreSQL only handles ~100–300 concurrent connections before performance degrades — each connection is a whole OS process.
A connection pool maintains N open connections, hands them to request handlers, and takes them back when done.
PgBouncer's three modes
| Mode | Connection returned to pool… | Notes |
|---|---|---|
| Session pooling | When the client session ends | One real connection per client session — similar to no pooler. |
| Transaction pooling | After each TRANSACTION | Best for stateless apps; the sweet spot for most web backends. |
| Statement pooling | After each STATEMENT | No multi-statement transactions allowed. Fastest. |
- PgBouncer sits between app and DB: 10,000 app connections → 20 real DB connections.
- HikariCP (Java): in-process pool, near-zero overhead, the fastest JVM option.
- Sizing rule of thumb: pool_size = (2 × num_cores) + num_disks. An 8-core machine → a pool of ~18 connections.
Full-Text Search with Elasticsearch
An RDBMS LIKE '%foo%' is a full table scan — O(N), terrible for search. Elasticsearch flips the data structure: an inverted index stores, for each word, the list of documents containing it. Lookup is O(log V), where V is the vocabulary size.
- Analyzers tokenize and normalize text: "Running" → "run" (stemming), "the" → removed (stop word), synonyms ("car" matches "automobile").
- Search features: relevance scoring (BM25), fuzzy matching (Levenshtein distance), geo-distance queries, aggregations, autocomplete (edge n-grams).
- Architecture: index → shards → segments. Near-real-time: new documents become searchable in ~1 second (configurable).
- When to use: full-text search — e-commerce, logs, docs. Don't use it as a primary DB (no ACID). Pair it with Postgres: write to Postgres → async sync to Elasticsearch.
Interview Q&A
Three database questions interviewers actually ask — click to reveal strong answers.
Step 1: EXPLAIN ANALYZE the slow query. Full table scan? Add an index on the WHERE clause column. Index scan but still slow? Check index selectivity — low-cardinality columns (status='active' where 80% are active) make bad indexes. Step 2: Cover your query — add all SELECT columns to the index (covering index). Step 3: Check for N+1 — are you making individual queries in a loop? Step 4: Add a read replica and route reads there. Step 5: Partition the table by range (by date, by user_id range). Step 6: Do you actually need all 500M rows live, or can you archive old data? Most "database scaling" problems are actually index and query design problems.
Denormalize when: (1) A join is on the hot path (thousands of queries per second) and it's consistently between the same tables. (2) The data is write-once or infrequently updated (profile name, product title). (3) You're sharding — cross-shard joins are not viable. (4) You're building a read replica specifically for analytics with different query patterns. Don't denormalize if the data changes frequently (you'll have sync bugs) or if the join is rare (premature optimization). Always check first: is this join actually slow? Does an index or covering index solve it?
Cassandra wins clearly. (1) Cassandra is built for write-heavy workloads — writes hit a sequential commit log + an in-memory memtable, both extremely fast; Postgres writes go through WAL + buffer pool with far more overhead. (2) Cassandra auto-shards across nodes; getting 5M writes/sec from Postgres requires a complex manual sharding setup. (3) The time-series access pattern fits wide-column perfectly: partition by device_id, cluster by timestamp → "all readings for device X between t1 and t2" is a single-partition range scan. (4) Cassandra TTL automatically deletes old readings after N days, built in.
Anti-Patterns to Avoid
OFFSET-based pagination at scale
LIMIT 10 OFFSET 10000 scans 10,010 rows to return 10. Page 1000 of results: 10M rows scanned. Use cursor-based pagination instead: WHERE id > :last_id LIMIT 10.
SELECT * in production
Retrieves every column, including large BLOB/TEXT fields you don't need. Wastes I/O and network bandwidth. Always specify columns.
Storing JSON blobs in SQL without indexing
You lose all SQL benefits — no queries, no constraints. Either use a proper document store, or use Postgres JSONB with GIN indexes for queryable JSON.
Storage in System Design
Every system generates and consumes data — choosing the right storage layer is one of the most consequential decisions in system design. Storage impacts performance, reliability, cost, and scalability. The goal is to match your data's shape and access patterns to the right storage primitive.
Structured vs. Unstructured Data
- Structured data — Organized in a predefined schema (rows and columns). Examples: customer records, transaction logs, inventory tables. SQL databases excel here.
- Unstructured data — No fixed schema; stored as raw files or blobs. Examples: images, videos, audio, documents, social media posts. Object storage excels here.
Categories of Storage
- Database storage (SQL/NoSQL) — For structured and semi-structured data. Supports queries, transactions, and relationships. Examples: PostgreSQL, MySQL, MongoDB, Cassandra.
- Object storage — Flat address space (buckets), accessed via APIs, optimized for large unstructured data. Infinite horizontal scale. Examples: Amazon S3, Google Cloud Storage, Azure Blob Storage.
- File storage — Hierarchical directory structure. Ideal for shared drives and POSIX-compatible workloads. Examples: NFS, SMB, Amazon EFS.
- Block storage — Raw storage volumes, fast random I/O, no inherent metadata. Used by databases and virtual machines that manage their own file systems. Examples: Amazon EBS, iSCSI, SAN.
Core Storage Properties
- Durability — Data persists even after hardware failures, crashes, or power loss. Achieved through replication, checksums, and redundant storage (e.g., S3's 11 nines durability via cross-AZ replication).
- Availability — The system continues to respond to read and write requests even during partial failures. Traded against consistency under the CAP theorem.
- Consistency — Every read returns the most recent committed write. Strong consistency is critical for financial systems; eventual consistency is acceptable for social feeds or CDN-cached content.
- Atomicity — Operations are all-or-nothing. Crucial in transactional storage where partial updates (e.g., a half-completed money transfer) would corrupt data integrity.
The CAP Theorem
In any distributed system, you can only fully guarantee two of the three properties simultaneously:
- Consistency (C) — Every read receives the latest write across all nodes.
- Availability (A) — Every request receives a (non-error) response, even if it may return stale data.
- Partition Tolerance (P) — The system continues operating despite network failures splitting nodes apart.
In practice, network partitions are unavoidable at scale, so real-world systems must choose between Consistency and Availability when a partition occurs:
- CP systems — Reject requests during a partition to avoid returning stale data. Data is correct when available. Example: HBase — won't serve a read if it can't confirm the latest write across replicas. Use case: banking, financial ledgers, inventory.
- AP systems — Remain available during a partition, potentially returning stale data. Example: DynamoDB (default eventual consistency), Cassandra. Use case: social media feeds, product catalogs, content delivery.
- CA systems — Only achievable on a single node or tightly coupled cluster where partitions never occur. Example: standalone PostgreSQL. Not realistic for distributed systems.
The Fundamental Trade-offs: Scalability vs. Reliability vs. Performance
No storage system maximizes all three simultaneously. Understanding the trade-off triangle is essential for every design interview:
- Scale vs. Reliability — Distributing data across many nodes increases scale but introduces more failure points; replication adds cost and consistency complexity.
- Scale vs. Performance — More nodes mean more network hops. Object storage scales infinitely but has higher latency than local block storage.
- Reliability vs. Performance — Synchronous replication ensures durability but adds write latency. Async replication is faster but risks data loss on leader failure.
Real-World Storage Selection Patterns
- E-commerce — Product catalog (structured SQL or NoSQL document store), product images (object storage), inventory counts (SQL with strong consistency).
- Streaming services — Media files (object storage + CDN), user watch history (NoSQL columnar), billing (SQL with ACID).
- Log aggregation — Raw logs (object storage or HDFS), metrics (time-series DB like InfluxDB or Druid), search (Elasticsearch).
- Photo-sharing app — Photos in object storage (S3), user metadata and social graph in relational or document DB, activity feeds in NoSQL.
Storage & Database Interview Questions
Key questions covering storage systems, database performance, and advanced database topics — drawn from course Q&A material across all storage-related modules.
Introduction to Storage Questions
Storage is essential because it ensures data persistence across sessions and failures. All applications — from simple blogs to enterprise systems — need a reliable mechanism to store, retrieve, and update data. The choice of storage impacts scalability, performance, availability, and cost of the overall system.
Structured data is highly organized and stored in a predefined schema (e.g., tables in SQL databases). Examples: customer records, transaction logs.
Unstructured data lacks a fixed schema and is usually stored as raw files or blobs. Examples: images, videos, documents, social media posts.
Databases (SQL/NoSQL): For structured data, supporting queries and transactions.
Object Storage: For unstructured data like media and backups (e.g., Amazon S3).
File Storage: Hierarchical storage, like shared drives (e.g., NFS, SMB).
Block Storage: Raw storage volumes for high-performance apps (e.g., databases, VM disks).
Durability: Data remains intact and recoverable after crashes or failures.
Availability: System continues to respond to requests even during failures.
Consistency: Clients always see the latest committed data after a write.
Atomicity ensures that operations are all-or-nothing — they either complete fully or have no effect. It's crucial in transactional systems like databases where partial updates can lead to corruption (e.g., transferring money between accounts where only the debit completes).
Not always — the CAP theorem states that in the presence of a network partition, a distributed system must choose between consistency and availability. A system cannot guarantee both during failures. Trade-offs are necessary based on business requirements: financial systems demand consistency; social apps can tolerate availability with eventual consistency.
The CAP theorem states that a distributed system can only guarantee two out of three properties: Consistency, Availability, and Partition Tolerance.
It's important because it guides architects in choosing trade-offs when designing distributed storage — especially under failure conditions. Since network partitions are unavoidable at scale, systems must choose between consistency and availability when a partition occurs.
CP (Consistency + Partition Tolerance): Prioritizes data accuracy over uptime. During a partition the system may reject requests to avoid inconsistent reads. Example: HBase — won't serve a read without confirming the latest write.
AP (Availability + Partition Tolerance): Prioritizes uptime, even if data is stale. Example: DynamoDB, Couchbase — will serve requests during a partition but may return eventually consistent data.
Because network partitions are inevitable in real-world distributed systems. Without partition tolerance a system can't remain fault-tolerant. CA systems only work in centralized or tightly coupled environments (e.g., single-node PostgreSQL). As soon as you distribute, partition tolerance becomes non-negotiable.
It depends on the use case:
For financial systems, consistency is non-negotiable — partial or stale data causes real monetary harm.
For social apps or media streaming, availability is preferred — users can tolerate slight delays or stale views (e.g., a like count being off by a few for a moment).
Photos: Store in object storage (e.g., S3), optimized for large unstructured files. Use a CDN in front for fast global delivery. Store multiple resolutions for adaptive loading.
Metadata (likes, comments, users): Store in a NoSQL or relational database depending on query patterns and consistency needs. User relationships might use a graph DB; counts could use Redis for low-latency increments.
Use append-optimized storage with horizontal scalability:
Object storage for raw logs (e.g., S3) — cheap, durable, scalable.
Columnar or time-series DBs for metrics (e.g., Apache Druid, InfluxDB) — fast range scans over time.
Distributed file systems like HDFS for batch processing workloads.
Object storage: Flat namespace, API-based access, virtually unlimited scale, optimized for large sequential reads. Best for media, backups, data lakes.
File storage: Hierarchical directories, POSIX-compliant, good for shared drives across multiple compute nodes.
Block storage: Raw I/O blocks, very fast random reads/writes, no inherent metadata. Best for databases and OS-level operations where the application manages its own filesystem.
Database Performance Optimization Questions
Database replication involves creating copies of a database across multiple servers. This ensures high availability and fault tolerance. Replication improves performance by distributing read queries across multiple replicas, reducing load on a single server — particularly useful in read-heavy applications like content delivery or reporting. However, replication adds overhead for write operations because data must be synchronized across replicas.
Sharding: Distributes data across multiple databases or servers (shards), often based on a specific key (e.g., user ID). Sharding scales horizontally — each shard operates independently, potentially on separate machines. Cross-shard queries are expensive.
Partitioning: Divides a database table into smaller pieces within a single database instance, based on a range or list of values (e.g., by date). Improves query performance by narrowing the search scope without distributing across machines.
The CAP theorem forces a performance trade-off: in systems requiring high availability, you may accept eventual consistency (e.g., NoSQL databases like Cassandra deliver high throughput by relaxing consistency guarantees). In systems needing strong consistency (e.g., banking), availability may be sacrificed during network partitions — which can degrade perceived performance under failures.
B-Tree Indexes: The most common type, ideal for equality and range queries (WHERE age > 30). Use for general-purpose indexing on high-cardinality columns.
Hash Indexes: Optimized for exact-match equality queries (WHERE id = 123). Faster for point lookups but do not support range queries.
Full-Text Indexes: Used for keyword and phrase searches in text-heavy fields. Ideal for content search applications (e.g., Elasticsearch, Postgres tsvector).
Bitmap Indexes: Suitable for low-cardinality columns (e.g., gender, boolean flags). Improve analytics query performance.
Normalization: Organizes a database into multiple tables to reduce redundancy and ensure data integrity. Minimizes anomalies but increases query complexity (more joins) and can reduce read performance.
Denormalization: Combines tables or adds redundant data to improve read performance by reducing joins. Useful in reporting or read-heavy systems, but risks data inconsistency and increases storage requirements.
Connection pooling reuses database connections from a pre-established pool rather than creating and tearing down a connection per request. Establishing a new DB connection is expensive (TCP handshake, authentication, session setup). Pooling improves throughput by allowing many application requests to share a smaller set of persistent connections, dramatically reducing latency under high concurrency.
Query optimization is improving SQL execution time and resource usage. Approach:
1. Analyze the execution plan (EXPLAIN ANALYZE) — look for full table scans or inefficient joins.
2. Add indexes on columns used in WHERE, JOIN, and ORDER BY clauses.
3. Avoid SELECT * — select only necessary columns to reduce I/O.
4. Use appropriate joins — prefer INNER JOIN over OUTER JOIN when possible.
5. Paginate results using LIMIT / cursor-based pagination instead of fetching millions of rows.
6. Denormalize for read-heavy systems where join cost is consistently high.
A materialized view is a precomputed query result stored as a physical table. Instead of re-executing an expensive query on every request, results are retrieved from the cached view and refreshed periodically. Particularly useful in reporting and data warehousing where slightly stale data is acceptable and query complexity is high (e.g., aggregations over billions of rows).
Data Partitioning and Sharding: Split data across multiple storage systems or partitions for horizontal scalability.
Pagination: Return data in pages using cursor-based pagination rather than loading everything at once.
Caching: Use Redis or Memcached to store frequently accessed data and reduce database reads.
Batch Processing: Use batch jobs for large data transformations to avoid overloading the database with many small concurrent operations.
The trade-off stems from the CAP theorem. Prioritizing consistency (e.g., MySQL with synchronous replication) may sacrifice availability or performance during network partitions. Prioritizing performance and availability leads to eventual consistency — data may not be immediately synchronized across all nodes, but allows faster reads and higher throughput (e.g., Cassandra). The right choice depends on whether your business can tolerate stale reads.
Advanced Database Questions
SQL (Relational): Fixed predefined schema, tables with rows and columns, SQL query language, strong ACID compliance, vertical scaling, best for banking/ERP/inventory.
NoSQL (Non-Relational): Flexible or dynamic schema, documents/key-value/wide-column/graph structures, varies by type, often BASE/eventual consistency, horizontal scaling, best for IoT/analytics/real-time apps.
ACID (SQL databases): Atomicity — all operations in a transaction complete or none do. Consistency — data stays valid and follows rules. Isolation — simultaneous transactions don't interfere. Durability — committed data survives crashes.
BASE (NoSQL databases): Basically Available — system always responds. Soft state — data may not be immediately consistent. Eventually consistent — data consistency achieved over time.
Document (e.g., MongoDB): JSON-like documents. Use for content management, user profiles, flexible schemas.
Key-Value (e.g., Redis, DynamoDB): Keyed access, extremely fast. Use for caching, session storage, config stores.
Columnar (e.g., Cassandra, HBase): Stores by columns. Use for time-series data, analytics, logs.
Graph (e.g., Neo4j): Nodes and relationships. Use for social graphs, fraud detection, recommendation systems.
Prefer MongoDB when: the data structure is flexible or evolving (e.g., user profiles with varying fields); you're storing nested JSON documents; you need fast development cycles with dynamic schemas; or you're okay with eventual consistency or per-operation consistency tuning. Prefer PostgreSQL when ACID guarantees, complex joins, or strong schema enforcement are required.
CP: No availability during partitions. Example: HBase. AP: Allows stale reads to stay available. Example: DynamoDB, Cassandra. CA: Only achievable without partitions (rare in distributed systems).
SQL (PostgreSQL, MySQL): Typically CP in distributed mode — prioritize consistency. NoSQL: DynamoDB/Couchbase are AP; MongoDB is tunable between CP and AP; Cassandra leans AP but is tunable; Neo4j is CP (graph integrity is critical).
Financial ledger: SQL (PostgreSQL or MySQL) — requires strong consistency and transactional integrity (ACID compliance).
Product catalog: NoSQL Document DB (MongoDB) — schema flexibility for varying product attributes, frequent updates, nested structures.
Real-time chat app: NoSQL Key-Value or Document DB (Redis or DynamoDB) — low latency, high throughput, can tolerate eventual consistency for message ordering.
Poor horizontal scalability — sharding is complex and often manual. Rigid schemas — not ideal for evolving data structures or rapid iteration. Expensive distributed joins across shards. Lower throughput for high-velocity, high-volume workloads. Potential single point of failure if not properly clustered with failover.
Polyglot persistence is the use of multiple types of databases in a single system, each chosen to best fit the needs of a specific component. For example: SQL for transactional data, Redis for caching, Elasticsearch for full-text search, Cassandra for write-heavy time-series. It lets you optimize each data access pattern independently, though it adds operational complexity.
SQL: Highly normalized across multiple related tables. Predefined schemas. Focus on reducing data duplication. Design is driven by the data shape.
NoSQL: Denormalized or nested structures. Schema-less or dynamic schema. Data is modeled around access patterns — how you read and write the data — rather than minimizing duplication.
Vertical Scaling (Scale-Up): Increase CPU, RAM, or SSD on a single server. Simpler to implement; works well with traditional SQL databases. Has a hard ceiling.
Horizontal Scaling (Scale-Out): Add more servers or nodes. Common with NoSQL databases that support distributed architecture. No theoretical ceiling but introduces coordination complexity.
Use vertical scaling when simplicity matters and your workload fits on one machine. Use horizontal scaling for large-scale systems where traffic or data exceeds a single server's capacity.
All writes go to a leader node, which then replicates data to one or more followers. Reads can be served from followers to reduce leader load.
Consistency impact: Asynchronous replication can leave followers slightly behind (eventual consistency). Synchronous replication provides strong consistency but adds write latency.
Availability impact: If the leader fails, a new leader must be elected, potentially causing brief downtime during the failover.
Pros: Improves read scalability by offloading traffic from the primary. Increases fault tolerance — if the primary goes down, some reads can continue from replicas.
Cons: Data on replicas may be eventually consistent due to replication lag. Adds routing complexity (which queries go where). Does not improve write scalability — all writes still hit the primary.
Range-Based Sharding: Data split by key range (e.g., user IDs 1–1000 on shard 1). Pros: easy range queries and predictable key placement. Cons: can create hot spots if one range receives disproportionate traffic.
Hash-Based Sharding: A hash function on the shard key determines the shard. Pros: better load distribution, avoids hot spots. Cons: makes range queries difficult and complicates data locality debugging.
In standard hashing (key % N), adding or removing a node remaps the majority of keys — causing massive data movement. Consistent hashing arranges nodes on a virtual ring so that only a small fraction of keys need to be remapped when nodes join or leave. This dramatically improves elasticity and availability. Used in Cassandra, DynamoDB, and Redis clusters for partitioning and rebalancing.
Netflix: Cassandra for write-heavy workloads and geo-distribution; MySQL for billing and transactional data; Elasticsearch for real-time search; DynamoDB for metadata and resilience.
Uber: PostgreSQL and MySQL for core business data; Redis for geolocation and session caching; BigQuery and Hadoop for analytics.
Both use polyglot persistence — each database type is chosen to best fit the specific data needs of a component, optimizing for performance, scalability, and structure.
File Systems & Distributed Storage Questions
A traditional file system (e.g., ext4, NTFS) manages files on a single physical disk or volume, limited by one machine's storage and processing capacity.
A distributed file system (DFS) — like HDFS or CephFS — spreads data across multiple machines (nodes), providing scalability, fault tolerance, and high availability. Multiple clients can access files as if they were local, even though data is physically distributed across nodes.
HDFS ensures fault tolerance through block replication: each file is split into blocks (default 128MB or 256MB) and each block is replicated across different DataNodes (default: 3 copies). If a node fails, HDFS retrieves the block from another replica. The NameNode tracks all block locations but does not store actual data — DataNodes periodically send heartbeats to confirm liveness.
NameNode: The central metadata server. Stores file hierarchy, block locations, and replication metadata. Does not store actual file data. It is the coordination brain of the cluster.
DataNodes: Store the actual data blocks. Periodically send heartbeat signals and block reports to the NameNode to confirm they are alive and what data they hold.
The NameNode is the coordinator; DataNodes are the storage workhorses. NameNode is also a single point of failure unless configured with HA (standby NameNode).
Latency — time to retrieve or write a piece of data. In distributed systems, latency is higher due to network hops and coordination overhead (e.g., NameNode communication, quorum consensus).
Throughput — total data processed over time. Distributed systems excel at high throughput through parallel reads and writes across many nodes.
Trade-off: Optimizing for high throughput (batch analytics) typically increases latency for small, frequent reads (OLTP). Choose systems based on whether your workload is latency-sensitive or throughput-oriented.
CephFS when you need POSIX-compliant file system access, fine-grained metadata control, or unified object/block/file storage through a single Ceph cluster.
GlusterFS for simpler setup on commodity hardware, scale-out file storage with minimal configuration, or shared volumes in container environments (e.g., serving media files).
HDFS when you're primarily running batch analytics workloads (Hadoop, Spark) with a write-once-read-many access pattern and tight integration with the Hadoop ecosystem.
Use a distributed file system like HDFS or CephFS. Partition and replicate data to distribute load and prevent bottlenecks. Scale horizontally by adding new DataNodes as needed. Implement data locality awareness — run compute jobs close to where data resides (avoid cross-node data shuffling). Use compression and efficient columnar file formats (Parquet, ORC) to reduce I/O. Monitor access patterns and employ auto-scaling where supported.
Object Storage Questions
Object storage stores data as discrete units (objects), each with a unique identifier and rich metadata, in a flat address space (buckets). Ideal for scalability, metadata-driven organization, and unstructured data.
File storage uses a hierarchical folder/directory structure — good for shared file systems requiring POSIX semantics.
Block storage breaks data into fixed-sized blocks without inherent metadata — used in databases and virtual machines where the application manages the filesystem for fast random I/O.
Choose object storage when: dealing with large amounts of unstructured data (media, backups, logs); scalability is critical across regions; you need to serve static assets over the internet via CDN integration; or cost efficiency over long-term storage matters more than real-time access latency.
An object consists of: the data payload (e.g., an image file); a unique ID/key used to retrieve it; and metadata — user-defined or system-generated attributes (file type, permissions, timestamps, tags, custom labels). Metadata enables powerful organization, filtering, lifecycle policies (auto-tiering, expiration), and search capabilities without requiring a separate metadata database.
Store uploaded videos in object storage (S3). Organize by user ID or content type using bucket prefixes or tags. Use presigned URLs for secure, time-limited upload/download. Integrate with a CDN (CloudFront, Akamai) for fast global delivery. Store different video resolutions as separate objects to support adaptive bitrate streaming (HLS, DASH).
Use S3 lifecycle rules to automatically transition logs through storage tiers: S3 Standard → S3 Infrequent Access → Glacier → Glacier Deep Archive. Enable versioning and Object Lock for immutability and compliance. Use batch processing (AWS Batch or Lambda) to compress and archive logs before upload. Monitor access patterns with S3 Storage Class Analysis to optimize tier assignments.
Upload files to object storage and generate presigned URLs for limited-time access — no credentials need to be shared between services. Apply IAM policies for fine-grained per-service access control. Encrypt objects using SSE-S3 or SSE-KMS. Track access via CloudTrail or S3 access logs for audit trails.
Object storage has higher latency than block storage, especially for small, random reads/writes. Some regions exhibit eventual consistency for overwrites or deletes. It is not suitable for real-time transactional systems. Better suited for warm or cold data — static assets, media, backups — where latency of tens of milliseconds per request is acceptable.
S3 Standard: High availability and low latency — for frequently accessed content.
S3 IA / One Zone IA: Cheaper but with retrieval fees — for infrequently accessed backups.
S3 Glacier / Deep Archive: Very cheap, but high retrieval latency (hours) — for compliance archives and long-term retention.
The key trade-off is cost vs. access time. Good lifecycle management automatically transitions data to cheaper tiers as access frequency drops.
Use bucket policies or IAM roles scoped to individual users or applications. Use object-level permissions or presigned URLs for user-specific upload/download without exposing credentials. Enable logging and monitoring (CloudTrail, S3 access logs) to detect unauthorized access. Encrypt objects and manage keys using KMS or customer-managed encryption keys (CMKs) for data protection at rest.
Big Data Questions
The 5 V's define the challenges and opportunities in Big Data systems:
Volume — Massive amounts of data (e.g., Facebook: 4+ PB/day). Systems must scale horizontally.
Velocity — Speed at which data flows in (IoT sensors, trading platforms). Requires real-time tools like Kafka and Flink.
Variety — Structured, semi-structured, and unstructured data (JSON, images, logs). Systems must ingest diverse formats.
Veracity — Data quality and accuracy. Requires validation, cleaning, and data lineage.
Value — Business insights derived from data. Justifies investment in big data infrastructure.
These characteristics shape how systems are architected — from storage to processing to analytics.
Traditional RDBMSs are built for structured data and vertical scaling. They struggle with: Scalability — can't easily scale horizontally across commodity hardware. Flexibility — rigid schemas don't support semi/unstructured data. Performance — bottlenecks under high write/read loads and complex joins at scale. Cost — proprietary solutions become expensive to scale. Big Data systems (Hadoop, NoSQL, Spark) are designed to overcome these limitations through distributed architectures.
HDFS: Tightly coupled with the Hadoop ecosystem; optimized for high-throughput batch workloads; on-premises or private cloud; requires manual scaling and management.
S3: Cloud-native, fully managed, 11-nines durability; supports decoupled compute (Athena, EMR, Redshift Spectrum); auto-scales and is cost-effective for elastic workloads; serverless-friendly.
Choose HDFS for controlled, on-prem clusters with traditional Hadoop jobs. Choose S3 for scalable cloud-native pipelines needing flexibility, reliability, and lower operational overhead.
Workloads involving high Volume (terabytes to petabytes — application logs, CCTV footage), high Velocity (continuous streams — sensor data, financial transactions), or high Variety (heterogeneous sources — web logs, social media, audio). Examples: clickstream analysis, fraud detection, predictive maintenance with IoT data, training ML models on large datasets, real-time ad bidding. When traditional systems break down due to size, speed, or complexity — it's a Big Data problem.
Batch Processing: Processes data in chunks (e.g., nightly reports). High throughput, higher latency. Examples: Hadoop MapReduce, Spark batch. Use for model training, historical analysis, ETL.
Stream Processing: Processes events as they arrive with low latency. Examples: Apache Flink, Kafka Streams. Use for real-time alerts, monitoring, dashboards.
For fraud detection: Stream processing is ideal — immediate detection is critical. Kafka ingests transactions → Flink applies fraud detection logic → alerts raised in real time. Batch processing is still used to train and update fraud detection models on historical data.
Delta Lake is an open-source storage layer built on top of data lakes (S3, HDFS) that adds: ACID transactions — reliable concurrent reads/writes; Schema enforcement — prevents corrupt or invalid data; Time travel — rollback and historical data access for debugging and auditing; Performance optimizations — caching, indexing, and file compaction. Traditional data lakes lack consistency and governance. Delta Lake bridges that gap, making data lakes production-ready for analytics and ML pipelines.
A scalable, fault-tolerant pipeline:
Ingestion: Kafka or AWS Kinesis to ingest logs in real time at high throughput.
Storage: Raw logs in S3 or Delta Lake for cost-effective, durable, scalable storage.
Processing: Spark (batch) for ETL and aggregation; Flink (stream) for real-time alerting or filtering.
Querying: Presto, Athena, or Redshift Spectrum for ad-hoc SQL analytics on the stored data.
Visualization: Grafana, Superset, or Tableau for dashboards and insights. The design ensures horizontal scalability, data durability, and supports both real-time and batch workflows.
Storage: S3 or Delta Lake — scalable, low-cost, supports schema enforcement and time travel. HDFS for on-prem tightly coupled Hadoop workloads. NoSQL (Cassandra, MongoDB) for high-velocity semi-structured data with low-latency reads.
Processing: Apache Spark for large-scale batch ETL and machine learning. Apache Flink or Kafka Streams for real-time low-latency processing. Presto/Trino for federated interactive SQL queries across large datasets.
Tool choice depends on latency requirements, data structure, scale, and whether the workload is batch, stream, or hybrid.
Further Reading
Quick Check
You're storing sensor readings: 2 million writes per minute, queried only as "give me device X's readings between t1 and t2." Best fit?
Interactive: Database Replication Visualizer
Switch between async, sync, and semi-sync modes to see how writes propagate to replicas. Adjust the network lag slider and click "Write to Primary" to watch replication delay in action. Use "Simulate Failover" to see what happens when the primary goes down.