Skip to main content

Databases

One-line summary: Choosing the right database — and modeling data correctly — is the single biggest lever on a system's scalability, consistency, and cost.


🧩 Core Concepts

A database stores, organizes, and retrieves data reliably. The two broad families are relational (SQL) and non-relational (NoSQL). Picking between them (and the right sub-type) is a foundational system-design decision that drives your consistency model, query flexibility, and how you shard and replicate later.

flowchart TD
DB[Databases] --> SQL[Relational / SQL]
DB --> NoSQL[Non-Relational / NoSQL]
SQL --> S1[PostgreSQL, MySQL,<br/>SQL Server, Oracle]
NoSQL --> N1[Document<br/>MongoDB, Couchbase]
NoSQL --> N2[Key-Value<br/>Redis, DynamoDB]
NoSQL --> N3[Column-Family<br/>Cassandra, HBase]
NoSQL --> N4[Graph<br/>Neo4j, Neptune]

🗃️ SQL vs NoSQL

Relational (SQL)

Data lives in tables (rows + columns) with a fixed schema and enforced relationships (foreign keys). Queried with SQL, and typically ACID-compliant.

Strengths: structured data, complex joins, strong consistency, mature tooling. Weaknesses: rigid schema, harder to scale writes horizontally.

Non-Relational (NoSQL)

A family of stores optimized for flexible schemas, horizontal scale, and specific access patterns. Usually favor availability + partition tolerance (see CAP Theorem) with eventual consistency (see Consistency Models).

TypeData ModelBest ForExamples
DocumentJSON/BSON documentsSemi-structured data, evolving schemas, catalogsMongoDB, Couchbase
Key-Valuekey → value mapCaching, sessions, high-throughput lookupsRedis, DynamoDB
Column-FamilySparse wide rows by columnTime-series, write-heavy, huge datasetsCassandra, HBase
GraphNodes + edgesHighly connected data, relationshipsNeo4j, Neptune

Head-to-Head

DimensionSQLNoSQL
SchemaFixed, enforcedFlexible / schema-less
ScalingPrimarily vertical (harder horizontal)Built for horizontal (sharding)
ConsistencyStrong (ACID)Tunable, often eventual (BASE)
JoinsFirst-class, powerfulLimited / denormalized
Query languageStandard SQLVaries per engine
Best fitTransactions, reporting, relationsScale, flexible data, specific access patterns

🎯 When to Use Each

Choose SQL when:

  • You need transactions and strong consistency (payments, inventory, banking).
  • Data is highly relational with complex queries and joins.
  • The schema is well-understood and stable.

Choose NoSQL when:

  • You need massive horizontal scale and high write throughput.
  • The schema is flexible or rapidly evolving.
  • Access patterns are known and simple (key lookups, document fetches).
  • Eventual consistency is acceptable.

💡 Polyglot persistence: real systems often mix both — e.g., PostgreSQL for orders, Redis for sessions, Cassandra for event logs.


🔍 Indexing

An index is an auxiliary data structure that speeds up reads at the cost of extra storage and slower writes (the index must be updated on every write).

flowchart LR
Q[Query: WHERE email = ?] --> I{Index on email?}
I -- Yes --> F[O log n lookup]
I -- No --> S[O n full table scan]
Index TypeStructureGreat ForWeak For
B-TreeBalanced sorted treeRange queries, sorting, >, <, BETWEEN, prefix LIKENothing major — the default
HashHash tableExact-match equality (=)Range queries (not supported)
  • B-Tree is the default in most engines: O(log n) lookups and supports ordered scans.
  • Hash indexes give O(1) average equality lookups but cannot serve ranges.
  • Composite indexes cover multi-column queries (order matters: leftmost-prefix rule).
  • Over-indexing hurts write performance and wastes storage — index deliberately.

🧱 Normalization vs Denormalization

ApproachIdeaProsCons
NormalizationSplit data into related tables, no redundancy (3NF)No duplication, consistent updates, less storageMore joins, slower reads
DenormalizationDuplicate data to avoid joinsFast reads, fewer joinsRedundancy, harder/costly writes, risk of drift
  • Normalize for write-heavy, consistency-critical OLTP systems.
  • Denormalize for read-heavy systems and NoSQL, trading storage & write cost for read speed. Pairs well with caching.

⚖️ ACID vs BASE

flowchart LR
subgraph ACID [ACID - SQL]
A1[Atomicity]
A2[Consistency]
A3[Isolation]
A4[Durability]
end
subgraph BASE [BASE - NoSQL]
B1[Basically Available]
B2[Soft state]
B3[Eventually consistent]
end

ACID — guarantees for reliable transactions:

  • Atomicity — all-or-nothing; a transaction fully completes or fully rolls back.
  • Consistency — moves the DB from one valid state to another (constraints hold).
  • Isolation — concurrent transactions don't interfere (via isolation levels).
  • Durability — committed data survives crashes.

BASE — the relaxed philosophy for scale (see CAP Theorem):

  • Basically Available — the system always responds, even if degraded.
  • Soft state — state may change over time without input.
  • Eventually consistent — replicas converge given enough time.

🔄 Transactions & Isolation Levels

A transaction groups multiple operations into one atomic unit. Isolation levels trade consistency for concurrency:

Isolation LevelDirty ReadNon-Repeatable ReadPhantom Read
Read Uncommitted✅ possible✅ possible✅ possible
Read Committed✅ possible✅ possible
Repeatable Read✅ possible
Serializable

Higher isolation = more correctness but more locking/contention and lower throughput.


🔌 Connection Pooling

Opening a DB connection is expensive (TCP + auth handshake). A connection pool keeps a set of reusable open connections that application threads borrow and return.

flowchart LR
App1[App Thread 1] --> Pool[(Connection Pool)]
App2[App Thread 2] --> Pool
App3[App Thread 3] --> Pool
Pool --> DB[(Database)]
  • Reduces latency — no per-request handshake.
  • Caps concurrency — protects the DB from connection exhaustion.
  • Tune pool size to match DB limits and workload (e.g., HikariCP, PgBouncer).

🧠 Trade-offs / When to Use

  • SQL → correctness, relations, transactions; scale vertically or via read replicas.
  • NoSQL → scale and flexibility; accept eventual consistency and denormalization.
  • Indexing → speeds reads, slows writes — index for your real query patterns.
  • Normalization → write integrity; denormalization → read speed.
  • When one DB isn't enough, combine sharding (scale writes) + replication (scale reads/availability) + caching (reduce load).

Interview Questions

  • Given a mixed workload (writes for orders, reads for analytics), how would you design the datastore layer?
  • When would you denormalize data and what trade-offs would you accept?
  • How do you pick indexing strategies for a new table with evolving query patterns?

Production Checklist

  • Baseline query performance and index usage; remove unused indexes to speed writes
  • Monitor slow queries, lock contention, and connection pool saturation
  • Establish backup/restore and run regular recovery drills
  • Enforce schema migrations with versioning and rollout strategies (blue/green migrations)

Testing & Monitoring

  • Run realistic load tests that include OLTP and analytical workloads where applicable
  • Test failover of primary instances and validate replica promotions
  • Validate connection pool tuning under concurrency and chaos tests that drop DB nodes
  • Sharding — partition data across nodes to scale writes
  • Replication — copy data for availability and read scaling
  • Caching — reduce database load with fast in-memory reads
  • CAP Theorem — consistency vs availability under partitions
  • Consistency Models — strong vs eventual consistency
  • Scalability — vertical vs horizontal scaling

← Back to System Design · © sparshjaswal