Chapter 11: NoSQL Database Taxonomy

Document, KV, Column-family, Graph — When to Use Each

11.1 Why NoSQL Exists

The Relational Database Wall

By the mid-2000s, web-scale companies hit fundamental limits with traditional RDBMS:

Limitation Symptom at Scale Root Cause
Rigid Schema ALTER TABLE locks on billions of rows; schema migrations take weeks ACID guarantees require fixed structure; changes must be atomic
Join Performance Multi-way joins across sharded tables require cross-network operations Normalization assumes co-located data; sharding breaks this assumption
Write Scalability Single-master bottleneck; vertical scaling only (add RAM/CPU) Strong consistency requires coordination; 2PC/Paxos slow down writes
Impedance Mismatch Object-Relational Mapping (ORM) complexity; N+1 query problems Application objects don't map cleanly to normalized tables
Cost Enterprise licenses scale with CPU cores; hardware gets expensive Commercial databases charge per-core; commodity hardware not optimized
Interview Tip: When asked "Why NoSQL?", don't just say "scale". Be specific: "Relational databases struggle with horizontal write scalability because strong consistency requires coordination across nodes (2PC), which increases latency. NoSQL systems trade consistency for partition tolerance (CAP theorem), enabling independent writes across nodes."

The CAP Theorem Trade-off

graph TD CAP[CAP Theorem: Pick 2 of 3] C[Consistency
All nodes see same data] A[Availability
System always responds] P[Partition Tolerance
Works during network splits] CAP --> C CAP --> A CAP --> P C -.->|CP Systems| CP[Traditional RDBMS
MongoDB strong
HBase
Redis Cluster] A -.->|AP Systems| AP[Cassandra
DynamoDB
Riak
CouchDB] P -.->|CA Impossible| CA[Network partitions
always happen in
distributed systems] style CA fill:#f66,stroke:#f00

Reality: Network partitions are inevitable in distributed systems (fiber cuts, switch failures, datacenter splits). So the real choice is CP vs AP.

Choice Behavior During Partition Example Systems Use When
CP (Consistency + Partition Tolerance) Minority partition nodes return errors or block reads/writes until partition heals MongoDB (strong consistency), HBase, etcd, Redis Cluster Financial transactions, inventory, strong correctness requirements
AP (Availability + Partition Tolerance) All nodes accept reads/writes; conflicts resolved later (last-write-wins, vector clocks, CRDTs) Cassandra, DynamoDB, Riak, CouchDB Shopping carts, social feeds, analytics, high uptime > perfect consistency

Polyglot Persistence

Modern architectures use multiple database types within the same application, each optimized for specific access patterns:

graph LR App[Application Layer] App -->|User Sessions| Redis[Redis
Key-Value] App -->|Product Catalog| Mongo[MongoDB
Document] App -->|Order History| Postgres[PostgreSQL
Relational] App -->|Activity Feed| Cassandra[Cassandra
Column-Family] App -->|Recommendations| Neo4j[Neo4j
Graph] App -->|Search| ES[Elasticsearch
Document + Inverted Index] style Redis fill:#e74c3c style Mongo fill:#47a248 style Postgres fill:#336791 style Cassandra fill:#1287b1 style Neo4j fill:#008cc1 style ES fill:#005571
Trade-off Alert: Polyglot persistence increases operational complexity (multiple technologies to monitor, backup, secure, upgrade) and data consistency challenges (distributed transactions across systems). Only adopt when the benefits clearly outweigh the costs.

11.2 Key-Value Stores

Conceptual Model

The simplest NoSQL data model: a distributed hash map. Every item has a unique key, and the value is an opaque blob (or typed data structure in advanced stores).

graph TD subgraph "Key-Value Store" KV1["Key: user:1001
Value: {name: 'Alice', age: 30}"] KV2["Key: session:abc123
Value: {userId: 1001, expires: 1620000000}"] KV3["Key: counter:page_views
Value: 452891"] end Client[Client] -->|GET user:1001| KV1 Client -->|SET session:abc123| KV2 Client -->|INCR counter:page_views| KV3
Characteristic Implication
No Schema Values can be anything (JSON, binary, string); no validation at storage layer
No Joins Related data accessed via multiple GET operations; application-side joins
No Secondary Indexes Can only query by primary key; filtering requires scanning all keys or external indexing
Horizontal Scaling Partition by key hash; consistent hashing enables adding/removing nodes
Performance O(1) reads/writes; microsecond latency for in-memory stores

Redis

The Swiss Army Knife of KV stores. Single-threaded (no lock contention), in-memory with optional persistence, rich data structures beyond simple key-value.

Architecture

graph TB subgraph "Redis Server (Single-threaded Event Loop)" EventLoop[Event Loop
epoll/kqueue] Commands[Command Processor] DataStructures[Data Structures
Strings, Hashes, Lists,
Sets, Sorted Sets, Streams] Persistence[Persistence Layer] end subgraph "Persistence Options" RDB[RDB Snapshots
Fork + Copy-on-Write
Periodic full dump] AOF[AOF Log
Append-only file
fsync every write/second/never] end subgraph "Scaling" Sentinel[Redis Sentinel
Auto-failover + Monitoring] Cluster[Redis Cluster
16384 hash slots
Automatic sharding] end Client[Client] --> EventLoop EventLoop --> Commands Commands --> DataStructures DataStructures --> Persistence Persistence --> RDB Persistence --> AOF DataStructures -.-> Sentinel DataStructures -.-> Cluster
Feature Description Use Case
Strings Binary-safe strings up to 512MB; GET/SET/INCR/DECR Caching, counters, rate limiting
Hashes Field-value maps; HGET/HSET/HGETALL User profiles (key=user:1001, fields=name/email/age)
Lists Linked lists; LPUSH/RPUSH/LPOP/RPOP/LRANGE Job queues, activity logs, real-time feeds
Sets Unique unordered strings; SADD/SREM/SINTER/SUNION Tags, unique visitors, online users
Sorted Sets Sets with score; ZADD/ZRANGE/ZRANGEBYSCORE Leaderboards, priority queues, time-series indexes
Streams Append-only log with consumer groups (like Kafka Lite) Event sourcing, messaging, activity tracking
Bitmaps Bit-level operations on strings; SETBIT/GETBIT/BITCOUNT Feature flags, user activity tracking (daily logins)
HyperLogLog Probabilistic cardinality estimation (0.81% error) Unique visitor counts with O(1) space

Persistence Strategies

Method Mechanism Durability Performance Impact Recovery
RDB (Snapshotting) Fork child process, writes copy-on-write snapshot to disk periodically (e.g., every 5 min) Can lose up to snapshot interval of data Low (background process); brief pause during fork on large datasets Fast (load snapshot file)
AOF (Append-Only File) Log every write command to file; replay on restart Better (fsync every second or every write) Higher (disk I/O on writes); AOF rewrite compacts file Slower (replay all commands)
RDB + AOF Hybrid: RDB for full snapshots + AOF for recent changes Best (RDB base + AOF delta) Medium (both mechanisms active) Fast RDB load + short AOF replay
None Pure in-memory cache (no persistence) None (data lost on restart) Zero (no disk I/O) N/A (start empty)

Redis Cluster Partitioning

graph TB Client[Client] Master1["Master 1 Slots 0-5460"] Master2["Master 2 Slots 5461-10922"] Master3["Master 3 Slots 10923-16383"] Replica1["Replica 1 Async replication"] Replica2["Replica 2 Async replication"] Replica3["Replica 3 Async replication"] Master1 -.-> Replica1 Master2 -.-> Replica2 Master3 -.-> Replica3 Client --> Master2 Client --> Master3

Key Routing:

slot = CRC16(key) % 16384
# Client contacts any node; if wrong slot, server returns MOVED redirect
# Smart clients cache slot-to-node mapping for direct routing
Interview Question: "Why is Redis single-threaded but still so fast?"
Answer: (1) All operations are in-memory (no disk I/O blocking), (2) No lock contention (single thread = no mutex overhead), (3) Event loop with non-blocking I/O (epoll/kqueue) handles thousands of concurrent connections, (4) Simple data structures optimized for cache locality. Bottleneck is usually network, not CPU. Redis 6+ added I/O threading for socket reads/writes, but command execution remains single-threaded.

Memcached

Pure LRU cache. Simpler than Redis (no persistence, fewer data structures), multi-threaded (better for massive read-heavy workloads).

Feature Memcached Redis
Threading Multi-threaded (per-core) Single-threaded (I/O threads in 6+)
Data Structures Strings only Strings, hashes, lists, sets, sorted sets, streams, etc.
Persistence None RDB snapshots + AOF log
Replication None (client-side sharding) Master-replica + Sentinel/Cluster
Eviction Strict LRU Multiple policies (LRU, LFU, TTL, volatile/allkeys)
Use Case Pure cache layer (sessions, page fragments) Cache + data structures + pub/sub + messaging

DynamoDB

AWS managed key-value + document store. Fully managed (no servers), single-digit millisecond latency, unlimited scale, pay-per-request or provisioned capacity.

Architecture

graph TB Client[Client Application] subgraph "DynamoDB Service" Router[Request Router] subgraph "Partitions (Auto-sharding)" P1[Partition 1
Hash key range A-F] P2[Partition 2
Hash key range G-M] P3[Partition 3
Hash key range N-Z] end subgraph "Replication (3 AZs)" R1[Replica AZ-1] R2[Replica AZ-2] R3[Replica AZ-3] end Storage[Storage Layer
SSD-backed] end Client --> Router Router -->|Consistent hash| P1 Router --> P2 Router --> P3 P1 --> R1 P1 --> R2 P1 --> R3 R1 --> Storage R2 --> Storage R3 --> Storage

Data Model

Each item (row) must have a partition key (hash key). Optionally, a sort key (range key) for ordering items within a partition.

// Table: Users
{
  "userId": "user123",        // Partition key (PK)
  "name": "Alice",
  "email": "alice@example.com",
  "createdAt": 1620000000
}

// Table: OrderItems (composite key)
{
  "orderId": "order456",      // Partition key (PK)
  "itemId": "item789",        // Sort key (SK)
  "quantity": 2,
  "price": 29.99
}

// Query: Get all items for order456, sorted by itemId
GET orderId = "order456"

Secondary Indexes

Index Type Scope Key Requirements Consistency Use Case
Local Secondary Index (LSI) Same partition key, different sort key Must define at table creation Strong or eventual Query same partition by different attribute (e.g., orderId + timestamp instead of itemId)
Global Secondary Index (GSI) Different partition key and/or sort key Can add/remove after table creation Eventual only Query by non-primary key (e.g., email lookup in Users table)

Consistency Models

Mode Behavior Latency Cost
Eventual Consistency Read from any replica; may see stale data (< 1 second lag) Lower 0.5x RCU (read capacity units)
Strong Consistency Read from leader replica; always latest data Higher 1x RCU

Adaptive Capacity

DynamoDB automatically redistributes capacity to hot partitions. If one partition gets 10x traffic, DynamoDB boosts its throughput temporarily (borrowing from unused capacity elsewhere).

Pitfall: Hot key problem. If 80% of requests hit the same partition key (e.g., celebrity user), that single partition maxes out (1000 WCU/3000 RCU per partition). Solution: Add randomness to partition key (e.g., userId#<0-9> suffix) or use DynamoDB Accelerator (DAX) for caching.

etcd

Distributed KV store for configuration and coordination. Built on Raft consensus algorithm; used by Kubernetes for cluster state.

graph TB Leader["Leader Node Handles all writes"] Follower1["Follower 1 Replicates log"] Follower2["Follower 2 Replicates log"] Leader -->|Append entries| Follower1 Leader -->|Append entries| Follower2 Follower1 -.->|Heartbeat| Leader Follower2 -.->|Heartbeat| Leader Client[Client] -->|Write| Leader Client -->|Read| Leader Leader -->|Commit when majority ack| Storage["Persistent Storage Write-ahead log"]
Feature Description Use Case
Raft Consensus Leader election + log replication; strong consistency; quorum writes (majority) Ensures all nodes agree on state even during failures
Watch API Clients subscribe to key changes; get notified in real-time Service discovery (watch /services/api-server for IP changes)
TTL Keys Automatic expiration (leases) Leader election (holder renews lease; if dies, lease expires and new leader elected)
Transactions Compare-and-swap (CAS) operations; multi-key ACID txns Distributed locks, atomic configuration updates
Prefix Range Queries List all keys with prefix (e.g., /config/db/*) Hierarchical configuration
Interview Distinction: "etcd vs Redis for coordination?"
etcd: Strong consistency (Raft), designed for critical metadata (Kubernetes state, service discovery). Slower writes (quorum required) but guaranteed linearizability.
Redis: Eventual consistency (async replication), designed for caching and high-throughput data structures. Faster but can lose data or see stale reads during failover.

When to Use Key-Value Stores

Scenario Why KV Fits Recommended Store
Session Management Fast reads/writes by session ID; TTL for expiration Redis (persistence for sticky sessions), Memcached (pure cache)
Caching High read throughput; LRU eviction; tolerate data loss Memcached (simple), Redis (richer features)
Rate Limiting Atomic counters (INCR); sliding window with sorted sets Redis
Real-time Analytics Counters High-write throughput; eventual consistency OK DynamoDB (managed scale), Redis (sorted sets for leaderboards)
Configuration Store Strong consistency; watch for changes; small dataset etcd
Feature Flags Fast lookups; infrequent updates; broadcast changes Redis (pub/sub for updates), etcd (watch API)
Shopping Cart User-keyed data; occasional writes; merge conflicts tolerable DynamoDB (managed, AP), Redis (fast, CP)

11.3 Document Databases

Conceptual Model

Store documents (JSON, BSON, XML) as first-class entities. Unlike KV stores, documents have internal structure that the database can index and query.

graph TD Doc1["Document: User { _id: 'user123', name: 'Alice', email: 'alice@example.com', address: { city: 'NYC', zip: '10001' }, orders: ['order1', 'order2'] }"] Doc2["Document: Product { _id: 'prod456', title: 'Laptop', price: 999.99, tags: ['electronics', 'computers'], specs: { ram: '16GB', cpu: 'i7' } }"]
Characteristic Implication
Flexible Schema Documents in same collection can have different fields; schema-on-read
Nested Structures Embed related data (denormalization); reduces joins
Rich Queries Query by any field; secondary indexes; aggregation pipelines
Horizontal Scaling Shard by document ID or chosen shard key

MongoDB

The dominant document database. BSON format (binary JSON), rich query language, aggregation framework, sharding and replication built-in.

Architecture

graph TB Client[Application] subgraph "MongoDB Replica Set" Primary[Primary Node
All writes] Secondary1[Secondary 1
Async replication] Secondary2[Secondary 2
Async replication] Primary -->|Oplog replication| Secondary1 Primary -->|Oplog replication| Secondary2 Secondary1 -.->|Heartbeat| Primary Secondary2 -.->|Heartbeat| Primary end subgraph "Sharded Cluster (optional)" Mongos[mongos
Query Router] ConfigServers[Config Servers
Cluster metadata] Shard1[Shard 1
Replica Set
Chunk A-M] Shard2[Shard 2
Replica Set
Chunk N-Z] Mongos --> ConfigServers Mongos --> Shard1 Mongos --> Shard2 end Client -->|Single Replica Set| Primary Client -->|Read preference:
primary/secondary| Secondary1 Client -.->|Sharded Setup| Mongos

Data Model

// Collection: users
{
  "_id": ObjectId("507f1f77bcf86cd799439011"),  // Auto-generated 12-byte ID
  "name": "Alice",
  "email": "alice@example.com",
  "age": 30,
  "address": {
    "street": "123 Main St",
    "city": "NYC",
    "zip": "10001"
  },
  "interests": ["hiking", "photography"],
  "createdAt": ISODate("2023-01-15T10:30:00Z")
}

// Collection: orders (reference to user)
{
  "_id": ObjectId("507f1f77bcf86cd799439012"),
  "userId": ObjectId("507f1f77bcf86cd799439011"),  // Reference
  "items": [
    { "productId": "prod123", "quantity": 2, "price": 29.99 },
    { "productId": "prod456", "quantity": 1, "price": 49.99 }
  ],
  "total": 109.97,
  "status": "shipped"
}

Embedding vs Referencing

Pattern When to Use Pros Cons
Embedding
(denormalize)
1:few relationships; data accessed together; bounded array size Single read to get all data; atomic updates Document size limit (16MB); duplication if data shared across docs
Referencing
(normalize)
1:many or many:many; data accessed independently; unbounded growth No duplication; flexible access patterns Multiple queries (application-side join); no ACID across documents

Example Decision:

  • Embed user's address (1:1, always accessed together)
  • Reference user's orders (1:many, unbounded, orders queried separately)

Indexing

// Single-field index
db.users.createIndex({ email: 1 })  // 1 = ascending, -1 = descending

// Compound index
db.orders.createIndex({ userId: 1, createdAt: -1 })

// Multikey index (array fields)
db.users.createIndex({ interests: 1 })  // Indexes each array element

// Text index (full-text search)
db.products.createIndex({ description: "text" })

// Geospatial index
db.stores.createIndex({ location: "2dsphere" })

// Unique index
db.users.createIndex({ email: 1 }, { unique: true })

Aggregation Pipeline

Multi-stage data transformation (like Unix pipes):

db.orders.aggregate([
  // Stage 1: Filter
  { $match: { status: "shipped", createdAt: { $gte: ISODate("2023-01-01") } } },

  // Stage 2: Join with users collection
  { $lookup: {
      from: "users",
      localField: "userId",
      foreignField: "_id",
      as: "user"
  }},

  // Stage 3: Unwind array
  { $unwind: "$user" },

  // Stage 4: Group and aggregate
  { $group: {
      _id: "$user.city",
      totalRevenue: { $sum: "$total" },
      orderCount: { $sum: 1 }
  }},

  // Stage 5: Sort
  { $sort: { totalRevenue: -1 } },

  // Stage 6: Limit
  { $limit: 10 }
]);

Sharding Strategy

graph TB Mongos[mongos Router] subgraph "Shard Selection" Hash["Hash Sharding
shard = hash(shardKey) % numShards"] Range["Range Sharding
Chunk A-M → Shard1
Chunk N-Z → Shard2"] Zone["Zone Sharding
USA data → Shard1
EU data → Shard2"] end Mongos --> Hash Mongos --> Range Mongos --> Zone Hash --> Shard1[Shard 1] Hash --> Shard2[Shard 2] Range --> Shard1 Range --> Shard2 Zone --> Shard1 Zone --> Shard2
Shard Key Type Distribution Range Queries Use Case
Hashed Uniform (random distribution) Requires scatter-gather (query all shards) High write throughput; no hot spots; don't need range queries
Range Skewed if key not evenly distributed Efficient (target specific shard) Time-series data (shard by date); ordered data access
Zone Manual control (geo, tenant, etc.) Depends on key Data locality (GDPR), multi-tenancy
Shard Key is Immutable: Once chosen, you can't change it without migrating data. Choose carefully based on access patterns. Bad shard key (e.g., incrementing timestamp) creates hot spots (all writes go to one shard).

Elasticsearch

Document store + full-text search engine. Built on Apache Lucene; inverted index for fast text search; also used for log analytics (ELK stack).

Architecture

graph TB Client[Client] subgraph "Elasticsearch Cluster" Master[Master Node
Cluster coordination] subgraph "Data Nodes" Node1[Node 1] Node2[Node 2] Node3[Node 3] end subgraph "Index: products (3 primary shards, 1 replica)" P1[Primary Shard 1] P2[Primary Shard 2] P3[Primary Shard 3] R1[Replica Shard 1] R2[Replica Shard 2] R3[Replica Shard 3] end Master -.->|Manages| Node1 Master -.->|Manages| Node2 Master -.->|Manages| Node3 Node1 --> P1 Node1 --> R2 Node2 --> P2 Node2 --> R3 Node3 --> P3 Node3 --> R1 end Client -->|Index document| Node1 Client -->|Search query| Node2

Inverted Index

Core data structure for full-text search:

// Documents
Doc1: "The quick brown fox jumps"
Doc2: "Quick brown dogs"
Doc3: "The lazy fox sleeps"

// Inverted Index (after tokenization + stemming)
Term        → Document IDs
"quick"     → [Doc1, Doc2]
"brown"     → [Doc1, Doc2]
"fox"       → [Doc1, Doc3]
"jump"      → [Doc1]
"dog"       → [Doc2]
"lazy"      → [Doc3]
"sleep"     → [Doc3]

// Query: "quick fox" → Match Doc1 (contains both terms)

Search Query DSL

POST /products/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "description": "laptop" } }  // Full-text search
      ],
      "filter": [
        { "range": { "price": { "gte": 500, "lte": 1500 } } },
        { "term": { "brand": "Apple" } }
      ]
    }
  },
  "aggs": {
    "price_ranges": {
      "histogram": {
        "field": "price",
        "interval": 100
      }
    }
  },
  "sort": [
    { "_score": "desc" },  // Relevance score
    { "price": "asc" }
  ]
}

Use Cases

Use Case Why Elasticsearch
Full-Text Search Inverted index; relevance scoring (TF-IDF, BM25); fuzzy matching; autocomplete
Log Analytics ELK stack (Elasticsearch + Logstash + Kibana); ingest logs, index, visualize dashboards
Real-Time Analytics Aggregations (histograms, percentiles, cardinality); near-real-time indexing (1 sec refresh)
Geospatial Search Geo-point/geo-shape types; radius search, bounding box, polygon queries
Application Search E-commerce product search, content recommendations, typeahead suggestions
Interview Question: "Elasticsearch vs MongoDB for search?"
Answer: MongoDB has text indexes, but Elasticsearch is purpose-built for search: (1) Advanced text analysis (stemming, synonyms, language-specific), (2) Relevance scoring (BM25), (3) Faceted search (aggregations), (4) Performance (inverted index optimized for reads), (5) Real-time analytics. Use MongoDB for general document storage + basic search; use Elasticsearch when search is a primary feature.

Firestore

Google's managed document database. Real-time sync, offline support, automatic scaling, tight integration with Firebase ecosystem.

Feature Description
Real-Time Listeners Clients subscribe to queries; get updates pushed instantly when data changes
Offline Support Client-side cache; writes queued locally and synced when online
Automatic Scaling No sharding config; scales to millions of concurrent connections
Security Rules Declarative access control at document/collection level
Multi-region Replication Synchronous replication across regions for high availability

When to Use Document Databases

Scenario Why Document DB Fits Recommended Store
Content Management Flexible schema (articles, pages, posts have varying fields); rich queries MongoDB, Firestore
User Profiles Nested data (preferences, settings); embed related entities (addresses, payment methods) MongoDB, Firestore
Product Catalogs Varying attributes per category (laptop specs ≠ shirt specs); full-text search Elasticsearch (search), MongoDB (storage)
Mobile/Web Apps Real-time sync, offline support, rapid prototyping (schema evolution) Firestore
Log Storage Unstructured/semi-structured logs; full-text search; aggregations Elasticsearch
Event Sourcing Append-only events (documents); flexible schema for event types MongoDB, Firestore

11.4 Wide-Column Stores

Conceptual Model

Data organized into rows and column families. Each row can have arbitrary columns; optimized for high write throughput and time-series data.

graph TD subgraph "Wide-Column Model" Row1["Row Key: user123
Column Family: profile
name: 'Alice'
email: 'alice@example.com'
Column Family: activity
login_2024-01-01: '10:30'
login_2024-01-02: '09:15'"] Row2["Row Key: user456
Column Family: profile
name: 'Bob'
Column Family: activity
login_2024-01-01: '14:20'"] end Client[Client] -->|GET user123:profile:name| Row1 Client -->|GET user123:activity:login_*| Row1
Characteristic Implication
Sparse Columns Rows can have millions of columns; only store non-null values (efficient for sparse data)
Column-Oriented Storage Columns stored together on disk; efficient for analytical queries (read specific columns)
No Joins Denormalize data; model relationships via row keys and column naming
Tunable Consistency Trade consistency for availability/latency (quorum reads/writes)
Optimized for Writes Log-structured merge trees (LSM); sequential writes; background compaction

Cassandra

Masterless, AP-focused wide-column store. Inspired by Amazon DynamoDB paper + Google Bigtable; tunable consistency, linear scalability, no single point of failure.

Architecture

graph TB Client[Client] subgraph "Cassandra Ring (Consistent Hashing)" Node1[Node 1
Token Range: 0-25] Node2[Node 2
Token Range: 26-50] Node3[Node 3
Token Range: 51-75] Node4[Node 4
Token Range: 76-100] Node1 -->|Gossip Protocol| Node2 Node2 -->|Gossip Protocol| Node3 Node3 -->|Gossip Protocol| Node4 Node4 -->|Gossip Protocol| Node1 end subgraph "Data Replication (RF=3)" Primary[Primary Replica
Node 1] Replica1[Replica 2
Node 2] Replica2[Replica 3
Node 3] Primary -.->|Async replication| Replica1 Primary -.->|Async replication| Replica2 end Client -->|Write/Read| Node1 Client -->|Write/Read| Node2 Client -->|Write/Read| Node3

Gossip Protocol

Nodes exchange cluster state information every second (peer-to-peer). No central coordinator; eventually all nodes learn about cluster changes (new nodes, failures).

// Node 1 gossips to Node 2:
{
  "Node1": { "status": "UP", "load": 45%, "version": 123 },
  "Node3": { "status": "UP", "load": 60%, "version": 122 },
  "Node4": { "status": "DOWN", "last_seen": 1620000000 }
}

// Node 2 merges with its own state and gossips to Node 3...
// Within seconds, all nodes agree on cluster state

Tunable Consistency

Specify consistency level per query (trade-off latency vs consistency):

Level Read Write Latency Consistency
ONE Return first response 1 node ACK Lowest Eventual (may see stale data)
QUORUM Majority of replicas Majority ACK Medium Strong (R + W > RF)
ALL All replicas respond All replicas ACK Highest Strongest (but fails if any node down)
LOCAL_QUORUM Majority in local DC Majority in local DC Low (no cross-DC) Strong within DC

Example: For strong consistency, use QUORUM for both reads and writes (with RF=3, require 2 nodes to agree). This ensures read always sees latest write.

Data Model

// CQL (Cassandra Query Language, SQL-like)
CREATE TABLE user_activity (
  user_id UUID,
  activity_date DATE,
  activity_time TIMESTAMP,
  action TEXT,
  PRIMARY KEY ((user_id), activity_date, activity_time)
);

// Partition Key: user_id (determines which node stores data)
// Clustering Columns: activity_date, activity_time (order within partition)

// Query: Get all activities for user on 2024-01-15
SELECT * FROM user_activity
WHERE user_id = 123e4567-e89b-12d3-a456-426614174000
  AND activity_date = '2024-01-15';

LSM Tree Storage

graph TB Write[Write Request] subgraph "In-Memory" Memtable[Memtable
Sorted in-memory buffer] end subgraph "Disk (SSTables)" SSTable1[SSTable 1
Immutable sorted file
Recent] SSTable2[SSTable 2
Older] SSTable3[SSTable 3
Oldest] end Write --> Memtable Write -.->|Also write| CommitLog[Commit Log
Append-only WAL] Memtable -->|Full? Flush to disk| SSTable1 SSTable1 -.->|Compaction| SSTable2 SSTable2 -.->|Compaction| SSTable3 Read[Read Request] --> Memtable Read --> SSTable1 Read --> SSTable2 Read --> SSTable3

Write Path:

  1. Write to commit log (append-only, fast sequential I/O)
  2. Write to memtable (in-memory sorted structure)
  3. Acknowledge write immediately (no disk read)
  4. When memtable full, flush to SSTable (immutable on-disk file)

Read Path:

  1. Check memtable (recent writes)
  2. Check SSTables (use bloom filters to skip files that definitely don't have key)
  3. Merge results from multiple SSTables (tombstones mark deletions)

Compaction: Background process merges SSTables, removes deleted data (tombstones), and reduces read amplification (fewer files to check).

When to Use Cassandra

Use Case Why Cassandra
Time-Series Data High write throughput; efficient range queries (clustering columns); time-based partitioning
IoT / Sensor Data Millions of writes/sec; scale horizontally; eventual consistency tolerable
Activity Feeds User-partitioned data; high write volume; read your own writes
Messaging / Notifications Append-only workload; time-ordered data; no single point of failure
Multi-Datacenter Built-in multi-DC replication; LOCAL_QUORUM for low latency
Anti-Pattern: Don't use Cassandra for: (1) Complex joins/transactions, (2) Ad-hoc queries (must design table per query pattern), (3) Small datasets (< 1TB), (4) Strong consistency requirements (banking, inventory).

HBase

Hadoop's wide-column store. Built on HDFS; strong consistency (CP); tight integration with Hadoop ecosystem (MapReduce, Spark).

Architecture

graph TB Client[Client] subgraph "HBase Cluster" Master[HMaster
Region assignment, DDL] subgraph "Region Servers" RS1[Region Server 1] RS2[Region Server 2] RS3[Region Server 3] end subgraph "Regions (Row Ranges)" R1[Region 1
Rows A-M] R2[Region 2
Rows N-Z] end ZooKeeper[ZooKeeper
Coordination, leader election] end subgraph "Storage" HDFS[HDFS
Distributed file system
3x replication] end Client --> ZooKeeper Client --> RS1 Client --> RS2 Master -.->|Assigns| RS1 Master -.->|Assigns| RS2 Master -.->|Assigns| RS3 RS1 --> R1 RS2 --> R2 R1 --> HDFS R2 --> HDFS Master -.-> ZooKeeper RS1 -.-> ZooKeeper RS2 -.-> ZooKeeper
Component Role
HMaster Schema changes, region assignment, load balancing, failover coordination
Region Server Serves reads/writes for assigned regions; manages memstore and HFiles
Region Contiguous row range for a table; splits automatically when too large (10GB default)
ZooKeeper Cluster coordination; stores region-to-server mapping; HMaster election
HDFS Durable storage; 3x replication; stores HFiles (immutable SSTables)
Interview Comparison: "Cassandra vs HBase?"
Cassandra: AP (eventual consistency by default), no single point of failure, optimized for always-on writes, multi-DC out-of-box.
HBase: CP (strong consistency), HMaster is SPOF (mitigated by ZooKeeper), optimized for read-heavy + Hadoop integration, better for sequential scans.

ScyllaDB

Cassandra-compatible, rewritten in C++. Shard-per-core architecture; 10x throughput of Cassandra; drop-in replacement (uses CQL).

graph TB subgraph "Traditional Cassandra (JVM)" JVM[Single-threaded execution
per core
Garbage collection pauses] end subgraph "ScyllaDB (C++)" Core1[Core 1
Independent shard] Core2[Core 2
Independent shard] Core3[Core 3
Independent shard] Core4[Core 4
Independent shard] Core1 -.->|No shared state| Core2 Core2 -.->|No shared state| Core3 Core3 -.->|No shared state| Core4 end style ScyllaDB fill:#2d5,stroke:#3f6,stroke-width:2px

Key Optimizations:

  • Shard-per-Core: Each CPU core owns a shard (partition of data); no locks, no context switching
  • User-Space I/O: Bypass kernel for disk/network I/O (DPDK, io_uring)
  • No GC Pauses: Manual memory management (C++); predictable latency
  • Automatic Tuning: Self-tunes cache sizes, compaction schedules, etc.

11.5 Graph Databases

Property Graph Model

Store nodes (entities) and relationships (edges) as first-class citizens. Each can have properties (key-value pairs).

graph LR Alice[Node: Person
name: Alice
age: 30] Bob[Node: Person
name: Bob
age: 25] Carol[Node: Person
name: Carol
age: 28] Product[Node: Product
name: Laptop
price: 999] Alice -->|FRIEND_OF
since: 2020| Bob Bob -->|FRIEND_OF
since: 2021| Carol Alice -->|FRIEND_OF
since: 2019| Carol Alice -->|PURCHASED
date: 2024-01-15| Product Bob -->|VIEWED
date: 2024-01-20| Product style Alice fill:#3498db style Bob fill:#3498db style Carol fill:#3498db style Product fill:#e74c3c
Component Description Example
Node Entity with labels and properties (:Person {name: "Alice", age: 30})
Relationship Directed edge with type and properties -[:FRIEND_OF {since: 2020}]->
Property Key-value pair on node or relationship name: "Alice", since: 2020
Label Node type (like a table in RDBMS) Person, Product

Neo4j

The leading graph database. Native graph storage (index-free adjacency); Cypher query language; ACID transactions.

Index-Free Adjacency

Each node physically contains pointers to its relationships (like a linked list). Traversing relationships is O(1) (follow pointer), not O(log n) (index lookup).

graph TB subgraph "Traditional RDBMS (Index Join)" UserTable[Users Table] FriendTable[Friends Table
user_id, friend_id] Index[B-Tree Index on friend_id] UserTable -->|Lookup in index| Index Index -->|Find matching rows| FriendTable end subgraph "Neo4j (Index-Free Adjacency)" Node1[Node: Alice] Node2[Node: Bob] Node3[Node: Carol] Node1 -.->|Direct pointer| Rel1[Relationship: FRIEND_OF] Rel1 -.->|Direct pointer| Node2 Node1 -.->|Direct pointer| Rel2[Relationship: FRIEND_OF] Rel2 -.->|Direct pointer| Node3 end style Neo4j fill:#2d5,stroke:#3f6,stroke-width:2px

Cypher Query Language

ASCII-art syntax for pattern matching:

// Find Alice's friends
MATCH (alice:Person {name: "Alice"})-[:FRIEND_OF]->(friend)
RETURN friend.name

// Find friends-of-friends (2 hops)
MATCH (alice:Person {name: "Alice"})-[:FRIEND_OF*2]-(foaf)
RETURN DISTINCT foaf.name

// Shortest path between Alice and Carol
MATCH path = shortestPath(
  (alice:Person {name: "Alice"})-[:FRIEND_OF*]-(carol:Person {name: "Carol"})
)
RETURN path

// Recommend products (friends who purchased)
MATCH (alice:Person {name: "Alice"})-[:FRIEND_OF]-(friend)
     -[:PURCHASED]->(product)
WHERE NOT (alice)-[:PURCHASED]->(product)
RETURN product.name, count(friend) AS friendCount
ORDER BY friendCount DESC
LIMIT 5

// Create new data
CREATE (alice:Person {name: "Alice", age: 30})
CREATE (bob:Person {name: "Bob", age: 25})
CREATE (alice)-[:FRIEND_OF {since: 2020}]->(bob)

Use Cases

Use Case Why Graph DB Query Pattern
Social Networks Multi-hop traversals (friends-of-friends); relationship-centric queries "Find mutual friends", "Suggest connections", "Influencer detection"
Fraud Detection Detect suspicious patterns (e.g., multiple accounts with same phone number, address, IP) "Find accounts linked by shared properties", "Detect rings of collusion"
Recommendation Engines Collaborative filtering via graph traversal (users who bought X also bought Y) "Recommend products based on social graph + purchase history"
Knowledge Graphs Store entities and relationships (e.g., Google Knowledge Graph, Wikipedia) "Answer complex questions by traversing relationships"
Network/IT Operations Model infrastructure dependencies (servers, apps, services) "Impact analysis: which services affected if server X goes down?"
Supply Chain Track product flow through suppliers, manufacturers, distributors "Trace origin of defective batch", "Optimize logistics"
Interview Question: "When would you NOT use a graph database?"
Answer: (1) Simple CRUD with no relationships (overkill), (2) Analytics on large datasets (graph DBs slow for full table scans; use data warehouse), (3) Highly variable schema with no relationships (use document DB), (4) Write-heavy workloads (graph writes update indexes on relationships; less optimized than KV/column stores).

Amazon Neptune

AWS managed graph database. Supports both property graph (Gremlin) and RDF (SPARQL); multi-AZ replication, automated backups.

Feature Description
Dual Model Property graph (Gremlin/TinkerPop) + RDF/SPARQL on same data
Storage Up to 128TB; 6-way replication across 3 AZs
Read Replicas Up to 15 read replicas; millisecond replica lag
ML Integration Neptune ML for graph neural networks (link prediction, node classification)

When to Use Graph Databases

Rule of thumb: If your queries involve 3+ joins or recursive traversals in a relational database, consider a graph database.

graph TD Start{Data Access Pattern} Start -->|Mostly single-entity lookups| KV[Key-Value Store] Start -->|Complex multi-hop relationships| Graph[Graph Database] Graph --> Q1{Query Type} Q1 -->|Fixed depth traversal
e.g., 2-hop friends| Neo4j[Neo4j/Neptune] Q1 -->|Variable depth
e.g., shortest path| Neo4j Q1 -->|Analytical queries
e.g., centrality, clustering| GraphAnalytics[Graph Analytics
GraphX, Spark] style Graph fill:#2d5,stroke:#3f6,stroke-width:2px

11.6 Time-Series Databases

Characteristics

Optimized for append-only data with timestamps. Common in monitoring, IoT, financial data.

Feature Implication
Immutable Data No updates/deletes; only inserts and time-range queries
Time-Based Indexing Primary index on timestamp; efficient range scans
Compression Delta encoding, run-length encoding; 10-100x compression
Downsampling Aggregate old data (1-sec → 1-min → 1-hour); save storage
Retention Policies Automatically delete data older than N days

InfluxDB

Purpose-built time-series database. InfluxQL (SQL-like) + Flux (functional query language); clustering in Enterprise edition.

Data Model

// Measurement (like table)
// Tags: indexed, low-cardinality dimensions
// Fields: values, not indexed
// Timestamp: nanosecond precision

cpu_usage,host=server1,region=us-east value=45.2 1620000000000000000
cpu_usage,host=server2,region=us-west value=62.8 1620000001000000000
memory_usage,host=server1,region=us-east value=8192 1620000000000000000

// Query: Average CPU usage per region in last hour
SELECT MEAN(value) FROM cpu_usage
WHERE time > now() - 1h
GROUP BY region, time(5m)

Storage Engine

graph TB Write[Write Point] subgraph "InfluxDB Storage" WAL[Write-Ahead Log
Append-only] Cache[In-Memory Cache] subgraph "TSM Files (Time-Structured Merge Tree)" TSM1[TSM File 1
Compressed blocks
Time range: Hour 1] TSM2[TSM File 2
Time range: Hour 2] TSM3[TSM File 3
Time range: Hour 3] end end Write --> WAL Write --> Cache Cache -->|Flush to disk| TSM1 TSM1 -.->|Compaction| TSM2 TSM2 -.->|Compaction| TSM3 Query[Query] --> Cache Query --> TSM1 Query --> TSM2

Compression: Delta encoding (store differences), run-length encoding (repeated values), dictionary encoding (strings).

TimescaleDB

PostgreSQL extension for time-series. Full SQL support; combines relational + time-series capabilities; automatic partitioning.

Hypertables

Automatic partitioning into "chunks" (time ranges):

-- Create hypertable
CREATE TABLE metrics (
  time TIMESTAMPTZ NOT NULL,
  device_id INT,
  temperature DOUBLE PRECISION,
  humidity DOUBLE PRECISION
);

SELECT create_hypertable('metrics', 'time');

-- Behind the scenes: TimescaleDB creates chunks
-- _hyper_1_1_chunk (2024-01-01 to 2024-01-08)
-- _hyper_1_2_chunk (2024-01-08 to 2024-01-15)
-- ...

-- Query transparently spans chunks
SELECT AVG(temperature) FROM metrics
WHERE time > NOW() - INTERVAL '7 days'
  AND device_id = 42;

Features

Feature Benefit
Full SQL JOINs, window functions, CTEs — leverage PostgreSQL ecosystem
Continuous Aggregates Materialized views that auto-update incrementally (real-time rollups)
Compression Columnar compression on old chunks (10-20x reduction)
Retention Policies Automatically drop old chunks (e.g., > 90 days)
Multi-Node Distributed hypertables across nodes (Enterprise)

Prometheus

Monitoring system + time-series DB. Pull model (scrapes metrics); PromQL query language; integrates with Grafana for dashboards.

Architecture

graph TB subgraph "Monitored Services" App1[App Server 1
:9090/metrics] App2[App Server 2
:9090/metrics] DB[Database
:9100/metrics] end Prometheus[Prometheus Server
Scrapes + Stores + Queries] Grafana[Grafana
Dashboards] AlertManager[Alert Manager
Notifications] Prometheus -->|Scrape every 15s| App1 Prometheus -->|Scrape every 15s| App2 Prometheus -->|Scrape every 15s| DB Grafana -->|PromQL queries| Prometheus Prometheus -->|Fire alerts| AlertManager AlertManager -->|Email/Slack/PagerDuty| Admin[Admin]

Metric Types

Type Description Example
Counter Monotonically increasing (resets on restart) Total HTTP requests, bytes sent
Gauge Value can go up/down CPU usage, memory usage, queue depth
Histogram Distribution of values in buckets Request latencies (p50, p95, p99)
Summary Like histogram but calculates quantiles client-side Response times
# PromQL Examples

# CPU usage over 80% in last 5 minutes
avg(cpu_usage) > 80

# Request rate (requests/sec)
rate(http_requests_total[5m])

# 95th percentile latency
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))

# Disk space running out (predicts when disk full)
predict_linear(disk_free_bytes[1h], 4*3600) < 0

When to Use Time-Series Databases

Scenario Why Time-Series DB Recommended DB
Application Monitoring High write throughput; range queries; downsampling; alerting Prometheus + Grafana
IoT Sensor Data Millions of devices; append-only; compression; retention policies InfluxDB, TimescaleDB
Financial Market Data Tick data (price/volume); high precision timestamps; fast ingestion TimescaleDB (SQL), InfluxDB
Log Aggregation Time-ordered logs; full-text search; aggregations Elasticsearch (ELK stack)
Business Metrics Dashboards, KPIs, real-time analytics; SQL for ad-hoc queries TimescaleDB

11.7 NewSQL

The Best of Both Worlds

NewSQL databases aim to provide NoSQL scalability + RDBMS ACID guarantees. They achieve this through modern distributed systems techniques (Raft/Paxos, distributed transactions, snapshot isolation).

flowchart TD RDBMS[Traditional RDBMS: MySQL PostgreSQL] --> NewSQL[NewSQL: CockroachDB Spanner] NoSQL[NoSQL: Cassandra MongoDB] --> NewSQL RDBMS -.->|Scaling Problem| NewSQL NoSQL -.->|Consistency Problem| NewSQL style RDBMS fill:#4ecdc4,color:#1a1a2e style NoSQL fill:#ff6b6b,color:#1a1a2e style NewSQL fill:#95e1d3,color:#1a1a2e

CockroachDB

Distributed SQL inspired by Spanner. PostgreSQL-compatible; multi-region; automatic rebalancing; survivable zone/region failures.

Architecture

graph TB Client[SQL Client] subgraph "CockroachDB Cluster" subgraph "Range-Based Sharding" R1[Range 1
Keys: A-M
Raft Group] R2[Range 2
Keys: N-Z
Raft Group] end subgraph "Nodes (3 regions)" Node1[Node 1
us-east] Node2[Node 2
us-central] Node3[Node 3
us-west] end subgraph "Raft Consensus" Leader1[Leader
Node 1] Follower1[Follower
Node 2] Follower2[Follower
Node 3] end end Client -->|SQL query| Node1 Node1 -->|Route to range| R1 R1 --> Leader1 Leader1 -->|Replicate| Follower1 Leader1 -->|Replicate| Follower2 Leader1 -.->|Commit when majority ack| Storage[Persistent Storage]

Key Features

Feature How It Works Benefit
Range Partitioning Data split into sorted ranges (~64MB); each range is a Raft group (3-5 replicas) Automatic rebalancing; add nodes → ranges split/move automatically
Raft Consensus Each range uses Raft for leader election + log replication (quorum writes) Strong consistency; tolerates minority failures (2 out of 3 nodes can fail)
Distributed Transactions Serializable isolation (strongest); two-phase commit across ranges ACID guarantees even across nodes/regions
Geo-Partitioning Pin data to specific regions (e.g., EU data in EU for GDPR) Data locality; compliance; low latency
Automatic Failover If leader fails, Raft elects new leader in < 5 seconds High availability; no manual intervention
-- Standard PostgreSQL-compatible SQL
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email STRING UNIQUE,
  name STRING,
  region STRING
);

-- Geo-partition: Keep EU users in EU
ALTER TABLE users PARTITION BY LIST (region) (
  PARTITION eu VALUES IN ('eu'),
  PARTITION us VALUES IN ('us')
);

ALTER PARTITION eu CONFIGURE ZONE USING constraints='[+region=eu-west]';
ALTER PARTITION us CONFIGURE ZONE USING constraints='[+region=us-east]';

-- Transaction (serializable across nodes)
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 'alice';
UPDATE accounts SET balance = balance + 100 WHERE id = 'bob';
COMMIT;

TiDB

MySQL-compatible NewSQL. Separates compute (TiDB servers) from storage (TiKV); Raft-based replication; HTAP (hybrid transactional/analytical).

graph TB Client[MySQL Client] subgraph "TiDB Architecture" subgraph "Compute Layer (Stateless)" TiDB1[TiDB Server 1
SQL parser, optimizer] TiDB2[TiDB Server 2
SQL parser, optimizer] end subgraph "Storage Layer (TiKV)" TiKV1[TiKV Node 1
Raft replica] TiKV2[TiKV Node 2
Raft replica] TiKV3[TiKV Node 3
Raft replica] end PD[Placement Driver
Cluster metadata
Load balancing] TiFlash[TiFlash
Columnar storage
Analytical queries] end Client -->|MySQL protocol| TiDB1 Client --> TiDB2 TiDB1 --> PD TiDB1 --> TiKV1 TiDB1 --> TiKV2 TiKV1 -.->|Raft replication| TiKV2 TiKV1 -.->|Raft replication| TiKV3 TiKV1 -.->|Async replication| TiFlash

Google Spanner

The original NewSQL. Global scale (used by Gmail, Google Ads); TrueTime API for external consistency; serializable isolation.

TrueTime

Spanner's secret sauce: TrueTime API provides a time interval with bounded uncertainty (typically < 7ms).

// TrueTime API
earliest, latest = TT.now()  // e.g., [t-3ms, t+3ms]

// Spanner uses this to order transactions globally
// Wait out the uncertainty before committing (commit-wait)
// Ensures transaction T1 committed before T2 starts → T1's timestamp < T2's timestamp
Aspect Implementation
Clock Sync GPS + atomic clocks in every datacenter; sync error < 7ms
Commit Wait Transaction waits max(uncertainty) before committing (ensures external consistency)
Snapshot Reads Read from past timestamp (no locks); stale reads with bounded staleness
Interview Insight: "Why can't other databases use TrueTime?"
Answer: TrueTime requires specialized hardware (atomic clocks, GPS) in every datacenter, which is expensive and complex. CockroachDB and others use hybrid logical clocks (HLC) instead — software-based, no special hardware, but slightly higher latency for global transactions.

YugabyteDB

Distributed SQL with PostgreSQL compatibility. Built on Raft + RocksDB; multi-API (SQL, Cassandra CQL, Redis).

Feature Description
Dual API YSQL (PostgreSQL) for transactions; YCQL (Cassandra) for high-throughput writes
Raft Consensus Like CockroachDB; each shard is a Raft group
Read Replicas Async replicas in distant regions for low-latency reads
Tablespaces Pin tables to specific regions (geo-partitioning)

When to Use NewSQL

Scenario Why NewSQL Recommended DB
Global Applications Multi-region deployments; low latency everywhere; strong consistency Spanner, CockroachDB, YugabyteDB
Financial Services ACID guarantees at scale; regulatory compliance; no data loss CockroachDB, Spanner
E-commerce Transactions (inventory, orders); scale during peaks; relational model TiDB, CockroachDB
SaaS Platforms Multi-tenant; horizontal scaling; SQL familiarity; compliance (GDPR) CockroachDB, YugabyteDB
RDBMS Migration Outgrown single-node MySQL/Postgres; need scale but keep SQL TiDB (MySQL), YugabyteDB (PostgreSQL), CockroachDB (PostgreSQL)
Trade-off: NewSQL databases sacrifice single-node performance for distributed capabilities. Expect higher latency than single-node Postgres (network hops, consensus). Use NewSQL when you need scale, not for small/medium workloads.

11.8 Selection Decision Framework

Use this flowchart to narrow down database type based on access patterns and requirements:

graph TD Start[Start: Define Requirements] Start --> Q1{Data Model} Q1 -->|Key-based lookup
Simple values| KV[Key-Value Store
Redis, DynamoDB, Memcached] Q1 -->|Nested documents
Flexible schema| Q2{Search Requirements} Q2 -->|Full-text search
Relevance ranking| ES[Elasticsearch] Q2 -->|Rich queries
No search focus| DOC[Document Store
MongoDB, Firestore] Q1 -->|Relationships
Graph structure| Q3{Query Type} Q3 -->|Multi-hop traversals
Pattern matching| GRAPH[Graph Database
Neo4j, Neptune] Q3 -->|Analytical
Clustering, pagerank| ANALYTICS[Graph Analytics
Neo4j + GDS, GraphX] Q1 -->|Tabular with
structured schema| Q4{Consistency Need} Q4 -->|Strong ACID
Global scale| NEWSQL[NewSQL
CockroachDB, Spanner] Q4 -->|Eventual OK
High write throughput| Q5{Access Pattern} Q5 -->|Time-series
Append-only| TS[Time-Series DB
InfluxDB, Prometheus] Q5 -->|Wide rows
Sparse columns| WIDE[Wide-Column
Cassandra, HBase] Q5 -->|Traditional SQL
Single region| RDBMS[Traditional RDBMS
PostgreSQL, MySQL] style KV fill:#e74c3c style DOC fill:#47a248 style ES fill:#005571 style GRAPH fill:#008cc1 style NEWSQL fill:#2d5,stroke:#3f6,stroke-width:2px style TS fill:#9b59b6 style WIDE fill:#1287b1 style RDBMS fill:#336791

Key Decision Factors

Factor Questions to Ask Impact
Data Model Key-value pairs? Nested documents? Graph relationships? Tabular? Determines database category (KV, document, graph, relational)
Access Patterns Read-heavy or write-heavy? Point lookups or range scans? Joins needed? KV for simple lookups; RDBMS for complex queries; column for high writes
Consistency Strong ACID required? Eventual consistency acceptable? CP systems (RDBMS, NewSQL) vs AP systems (Cassandra, DynamoDB)
Scale Dataset size? Query throughput (QPS)? Geographic distribution? Single-node RDBMS OK? Need horizontal scaling (NoSQL, NewSQL)?
Query Complexity Simple CRUD? Multi-table joins? Full-text search? Graph traversal? Simpler queries → NoSQL; complex → SQL or specialized (search, graph)
Operational Complexity Team expertise? Managed service available? Monitoring/backup needs? Managed services (RDS, DynamoDB, Atlas) vs self-hosted
Cost Budget constraints? Pay-per-request or provisioned? Storage costs? DynamoDB on-demand costly at scale; Cassandra requires ops expertise

Common Scenarios

Application Type Primary Database Supporting Databases Rationale
Social Network Neo4j (graph) Redis (cache), Cassandra (feeds), Elasticsearch (search) Relationships are core feature; graph for friends/follows; Cassandra for activity feeds
E-commerce PostgreSQL or CockroachDB Redis (sessions, cart), Elasticsearch (product search), DynamoDB (inventory counters) Transactions critical (orders, inventory); relational model fits; scale with NewSQL
SaaS App MongoDB or PostgreSQL Redis (cache), Elasticsearch (logs), TimescaleDB (metrics) Flexible schema for multi-tenant data; MongoDB for rapid iteration; Postgres for stability
IoT Platform Cassandra or TimescaleDB InfluxDB (metrics), PostgreSQL (metadata) High write throughput from sensors; time-series for sensor data; Cassandra for scale
Real-Time Analytics ClickHouse or Druid Kafka (streaming), Redis (state), Elasticsearch (dashboards) OLAP workloads; columnar storage for fast aggregations; streaming ingestion
Content Management MongoDB or Firestore Elasticsearch (search), Redis (cache), S3 (media) Flexible schema for varied content types; search for discovery; MongoDB for storage
Mobile App Firestore None (Firestore handles most needs) Real-time sync, offline support, managed scaling; ideal for mobile/web apps
Interview Strategy: When asked "Which database would you use?", follow this structure:
  1. Clarify requirements: Data model, scale, consistency, access patterns
  2. Identify constraints: Latency SLAs, budget, team expertise, compliance
  3. Propose primary database: Based on core workload (transactions → SQL, writes → column, etc.)
  4. Add supporting databases: Cache layer (Redis), search (Elasticsearch), etc.
  5. Justify trade-offs: Why this choice over alternatives (CAP, cost, complexity)

11.9 Master Comparison Matrix

Comprehensive comparison of all NoSQL and NewSQL databases covered:

Database Type Data Model Consistency Partition Strategy Query Language Scalability Managed Service Ideal Use Case
Redis Key-Value Strings, hashes, lists, sets, sorted sets, streams CP (cluster), eventual (replication) Consistent hashing (16384 slots) Redis commands Horizontal (cluster), vertical (single node) AWS ElastiCache, Azure Cache, GCP Memorystore Caching, sessions, real-time analytics, queues
Memcached Key-Value Strings only None (pure cache) Client-side consistent hashing Text protocol (GET/SET) Horizontal (client-side sharding) AWS ElastiCache, GCP Memorystore Pure cache layer, page fragments, sessions
DynamoDB Key-Value + Document Items (JSON-like) with partition/sort keys Eventual or strong (per-request) Hash partitioning (auto-sharding) PartiQL, SDK query API Unlimited (auto-scaling) AWS DynamoDB (fully managed) Serverless apps, shopping carts, session store, game state
etcd Key-Value Key-value with hierarchical keys CP (Raft consensus) Single Raft cluster (not horizontally scaled) gRPC API (Get/Put/Watch) Vertical (small datasets, < 8GB) None (self-hosted) Service discovery, configuration, distributed locks (Kubernetes)
MongoDB Document BSON documents (JSON-like) CP (strong with majority reads/writes) Hash or range sharding (configurable) MongoDB Query Language (MQL) Horizontal (sharding + replica sets) MongoDB Atlas, AWS DocumentDB Content management, user profiles, catalogs, mobile apps
Elasticsearch Document + Search JSON documents with inverted index Eventual (async replication) Hash sharding (automatic) Query DSL (JSON), SQL (v7+) Horizontal (shards + replicas) Elastic Cloud, AWS OpenSearch Full-text search, log analytics, real-time analytics (ELK)
Firestore Document Documents in collections (hierarchical) Strong (multi-region sync) Auto-sharding (managed by Google) SDK queries (where/orderBy/limit) Unlimited (auto-scaling) Google Firestore (fully managed) Mobile/web apps, real-time sync, offline support
CouchDB Document JSON documents with MVCC AP (eventual consistency, conflict resolution) Master-master replication HTTP REST API, MapReduce views Horizontal (multi-master) IBM Cloudant Offline-first apps, multi-master sync, mobile backends
Cassandra Wide-Column Column families (sparse columns) Tunable (ONE, QUORUM, ALL) Consistent hashing (token ring) CQL (SQL-like) Linear horizontal scaling (masterless) DataStax Astra, AWS Keyspaces, Azure Cosmos DB (Cassandra API) Time-series, IoT, activity feeds, high write throughput
HBase Wide-Column Column families (sparse columns) CP (strong consistency) Range-based regions on HDFS Java API, Thrift, REST Horizontal (regions across RegionServers) AWS EMR, GCP Bigtable Hadoop ecosystem, large-scale batch processing, Bigtable workloads
ScyllaDB Wide-Column Column families (Cassandra-compatible) Tunable (same as Cassandra) Consistent hashing (token ring) CQL (Cassandra-compatible) Linear horizontal scaling (10x faster than Cassandra) ScyllaDB Cloud Same as Cassandra but higher performance (low-latency, high-throughput)
Neo4j Graph Property graph (nodes + relationships) CP (ACID within single instance), eventual (cluster) Sharding via Fabric (Enterprise) Cypher Vertical (single node), horizontal (Fabric sharding in Enterprise) Neo4j Aura Social networks, fraud detection, recommendation engines, knowledge graphs
Amazon Neptune Graph Property graph + RDF CP (strong consistency) Auto-sharding (managed by AWS) Gremlin (property), SPARQL (RDF) Vertical (up to 15 replicas), managed scaling AWS Neptune (fully managed) Same as Neo4j but managed; integrates with AWS services
JanusGraph Graph Property graph Depends on storage backend (Cassandra/HBase/BerkeleyDB) Storage backend handles partitioning Gremlin Horizontal (via storage backend) None (self-hosted) Large-scale graphs (billions of edges), needs custom backend
InfluxDB Time-Series Measurements with tags (indexed) + fields (values) AP (eventual in cluster), single-node (CP) Time-based sharding (shards per time range) InfluxQL, Flux Horizontal (Enterprise/Cloud), vertical (OSS) InfluxDB Cloud Metrics, IoT sensor data, DevOps monitoring
TimescaleDB Time-Series (PostgreSQL extension) Relational tables (hypertables with time partitioning) CP (ACID via PostgreSQL) Time-based chunks, optional multi-node (Enterprise) SQL (full PostgreSQL) Vertical (single node), horizontal (multi-node in Enterprise) Timescale Cloud, AWS RDS (via extension) Time-series with SQL, financial data, business metrics, IoT
Prometheus Time-Series Metrics with labels (key-value tags) Single-node (no replication by design) Single-node (federate for scale) PromQL Vertical (federation for scale) Grafana Cloud, AWS Managed Prometheus Application/infrastructure monitoring, alerting (Prometheus + Grafana)
CockroachDB NewSQL Relational tables (PostgreSQL-compatible) CP (serializable isolation, Raft consensus) Range-based sharding (auto-balancing) SQL (PostgreSQL dialect) Linear horizontal scaling (add nodes → auto-rebalance) CockroachDB Cloud Global apps, financial services, multi-region ACID transactions
TiDB NewSQL Relational tables (MySQL-compatible) CP (snapshot isolation, Raft via TiKV) Range-based sharding (auto-balancing) SQL (MySQL dialect) Linear horizontal scaling (compute/storage separation) TiDB Cloud MySQL migration at scale, HTAP (transactional + analytical)
Google Spanner NewSQL Relational tables (SQL) CP (external consistency via TrueTime, Paxos) Range-based sharding (auto-balancing) SQL (Google SQL dialect) Unlimited horizontal scaling (global distribution) Google Cloud Spanner (fully managed) Mission-critical global apps (AdWords, Gmail), financial services
YugabyteDB NewSQL Relational tables (PostgreSQL-compatible) + Cassandra API CP (serializable via Raft) Range-based + hash sharding (auto-balancing) SQL (PostgreSQL), CQL (Cassandra) Linear horizontal scaling (multi-API) Yugabyte Cloud Multi-region, multi-cloud, PostgreSQL/Cassandra workloads at scale

CAP Theorem Classification

CP (Consistency + Partition Tolerance) AP (Availability + Partition Tolerance) Tunable
  • MongoDB (strong consistency mode)
  • HBase
  • etcd
  • CockroachDB
  • TiDB
  • Google Spanner
  • YugabyteDB
  • TimescaleDB
  • Neo4j (single instance)
  • CouchDB
  • Riak
  • Elasticsearch (default)
  • Cassandra (ONE to ALL)
  • DynamoDB (eventual or strong)
  • MongoDB (read/write concerns)
Interview Cheat Sheet: Quick recognition patterns:
  • "High write throughput" → Cassandra, ScyllaDB, InfluxDB
  • "Strong consistency + scale" → CockroachDB, Spanner, YugabyteDB
  • "Full-text search" → Elasticsearch
  • "Graph traversal" → Neo4j, Neptune
  • "Real-time sync + offline" → Firestore
  • "Simple key lookups + cache" → Redis, Memcached
  • "Time-series + SQL" → TimescaleDB
  • "Monitoring metrics" → Prometheus, InfluxDB
  • "Multi-region, always on" → Cassandra, DynamoDB, CockroachDB