TOPIC 07

Databases Deep Dive

The database is where most systems actually bottleneck — and where the richest design decisions live. Replication, federation, sharding, and the great SQL vs NoSQL question.

Advanced 45 min read 14 concepts

At a Glance

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).

writes Primary all writes change log → Replica 1 reads only Replica 2 reads only

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:

The pain points

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.

SQL tuning before scaling

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

QuestionLeans SQLLeans NoSQL
Data shapeStructured, interrelated tablesFlexible / nested / evolving schema
QueriesAd-hoc, joins, aggregationsKnown access patterns by key
Correctness needsMulti-row transactions (money!)Per-item atomicity is enough
ScaleUp to a few TB / one big node + replicasHorizontal from day one, petabytes
Write volumeModerateExtreme (time-series, events, logs)
ExamplesPostgreSQL, MySQLDynamoDB, Cassandra, MongoDB, Neo4j
Real-world receipts

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.

Archie says

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

LevelWhat it guaranteesNotes
SERIALIZABLETransactions behave as if run sequentially. Prevents all anomalies.Slowest — locks or aborts conflicting transactions.
REPEATABLE READSame reads within a transaction always return the same data. Prevents dirty + non-repeatable reads.Phantoms still possible. MySQL InnoDB default.
READ COMMITTEDCan only read committed data. Prevents dirty reads.Non-repeatable reads possible. Postgres default.
READ UNCOMMITTEDCan 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

Range-based sharding

user_id 1–1M → shard 1, 1M–2M → shard 2, …

Directory-based sharding

A lookup table maps each key → its shard.

Consistent hashing, illustrated

S1 S2 S3 S4 S5 new ← only keys between S1 and S5 migrate (~1/N of total) 0 … 2³² hash space wraps around the ring; each key goes to the next server clockwise

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.

Instagram's Postgres Sharding Journey

Instagram
1
2012: one Postgres server

Instagram launched on a single Postgres instance. Three months later: 30M users. Time for horizontal sharding.

2
Shard by user_id

All of a user's data lives on the same shard → user timeline queries hit exactly one shard.

3
Shard-aware schema

All tables are prefixed with shard_id; lookups use (shard_id, local_id) instead of a global_id.

4
Self-describing IDs

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.

The lesson

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.

The trade-off

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

ModeConnection returned to pool…Notes
Session poolingWhen the client session endsOne real connection per client session — similar to no pooler.
Transaction poolingAfter each TRANSACTIONBest for stateless apps; the sweet spot for most web backends.
Statement poolingAfter each STATEMENTNo multi-statement transactions allowed. Fastest.

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.

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

Categories of Storage

Core Storage Properties

The CAP Theorem

In any distributed system, you can only fully guarantee two of the three properties simultaneously:

In practice, network partitions are unavoidable at scale, so real-world systems must choose between Consistency and Availability when a partition occurs:

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:

Real-World Storage Selection Patterns

Interview Prep

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?

Exactly. Massive sequential writes + known access pattern (partition key = device, clustering key = timestamp) is the textbook wide-column workload. This is why IoT and metrics systems live on Cassandra/Bigtable-style stores.
Try It

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.