A production-grade playbook for selecting, designing, and scaling databases. Deep-dive into B-Tree indexes, NoSQL document modeling, cluster sharding, and cache eviction patterns.
Data management is the core bottleneck of distributed software systems. Building a system that scales requires a deep understanding of relational storage engines, NoSQL document schemas, scaling strategies, and high-performance memory caches.
Relational databases like MySQL and PostgreSQL utilize B-Trees (specifically B+ Trees) to store indices. A B+ Tree organizes data in a balanced search tree where all data rows are referenced in leaf nodes.
┌───────────────┐
│ Root │
│ [ 50 ] │
└───────┬───────┘
│
┌────────────┴────────────┐
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Internal 1 │ │ Internal 2 │
│ [ 20|35 ] │ │ [ 65|80 ] │
└──────┬──────┘ └──────┬──────┘
│ │
┌──────┴──────┬──────┐ ┌──────┴──────┬──────┐
▼ ▼ ▼ ▼ ▼ ▼
[10..19] [20..34] ... [50..64] [65..79] ...
When you execute a query using an indexed field, the complexity is $O(\log N)$ instead of an $O(N)$ full table scan.
[!TIP] Always inspect queries using the
EXPLAINkeyword to verify that your queries are hitting the indexes you created.
EXPLAIN SELECT id, title FROM posts WHERE category = 'devops';
In MongoDB, documents are stored in BSON format. Unlike normalized relational tables, MongoDB documents allow embedding arrays and sub-objects, which matches the object models inside your application.
Sharding distributes collection data across a cluster of independent machines using a Shard Key. MongoDB uses the shard key to route operations to target nodes:
┌───────────────┐
│ Application │
└───────┬───────┘
│
┌───────▼───────┐
│ Mongos │ (Router)
└───────┬───────┘
┌─────────────┼─────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Shard A │ │ Shard B │ │ Shard C │
│ [0..30] │ │[31..60] │ │ [61..+] │
└─────────┘ └─────────┘ └─────────┘
Redis acts as a sub-millisecond memory store. A common architecture pattern is the Cache-Aside Pattern:
import { createClient } from 'redis';
const client = createClient();
await client.connect();
async function getOrSetCache(key, dbQueryFn, ttl = 3600) {
const cachedData = await client.get(key);
if (cachedData) {
return JSON.parse(cachedData);
}
const freshData = await dbQueryFn();
await client.setEx(key, ttl, JSON.stringify(freshData));
return freshData;
}
To avoid filling RAM, always configure a key eviction policy like LRU (Least Recently Used) or LFU (Least Frequently Used) in your redis.conf configuration.