 Command

Pranesh Nikhar's personal site. Vim-style keybinds for navigation; theme + font pickers below.

Theme
 Font Body Code
Reader
Keybinds
Navigation
j / ↓ Next item k / ↑ Previous item g First item in region G Last item in region zz Center focused item h / l Move left/right region ] / [ Next/previous heading } / { Next/previous block d / u Half-page down/up
Layout
<zh> / <zl> Toggle left/right sidebar <zr> Toggle reader view <zj> / <zk> Focus main/navbar <S-h/j/k/l> Focus left/main/navbar/right ⌃H / ⌃L Focus left/right sidebar ⌃J / ⌃K Focus main/navbar ⇧C / ⇧E Collapse / expand all sections
Dialogs
⌃P / : Command palette ⌃X Theme picker / Search ? Show keybinds Esc / ⌃C Close dialog
History
n Next document b Previous document ⌃O History back ⌃I History forward
 Search
about: Pranesh Nikhar about/more: πŸͺͺ More docs/test: Docs Test ideas: πŸ’‘ Ideas more: βž• More now: Now posts: πŸ“¬ Posts projects: πŸ“š Projects webtui: Style posts/agentic-eda: πŸ“Š AgenticEDA β€” Automated Exploratory Data Analysis with LangGraph posts/cap-theorem-outage-story: 🌐 CAP Theorem with a Real Outage Story posts/codepilot: ✈️ CodePilot β€” From Requirements to Deployable FastAPI Backend posts/common-auth-mistakes: πŸ” Common Auth Mistakes Developers Make posts/compiled-vs-jit-vs-interpreted: ⚑ Why Is X Language Fast or Slow? β€” Compiled vs JIT vs Interpreted posts/cs-degree-gaps: πŸŽ“ Things CS Degrees Don't Teach You posts/cve-2025-breach-analysis: πŸ›‘οΈ CVE-2025 Breach Analysis β€” Midnight Blizzard and the 16 Billion Credential Leak posts/fixloop: πŸ”„ FixLoop β€” AI Agent Loop for Self-Correcting Code posts/functional-vs-oop: ⚑ Functional vs OOP β€” Same Problem, Both Ways posts/getman: 🦾 Getman β€” Declarative API Tester for CLI & TUI posts/how-compilers-optimize: βš™οΈ How Compilers Actually Optimize Your Code posts/http3-quic: ⚑ HTTP/3 and QUIC β€” Why They Matter posts/leetcode-vs-engineering: 🧩 LeetCode vs Real Engineering Skills posts/llm-from-scratch: 🧠 LLM from Scratch β€” GPT-Style Transformer in PyTorch posts/lsm-trees-bloom-filters: 🌳 LSM Trees & Bloom Filters β€” Production Deep Dive posts/mcp-workflow-builder: πŸ”§ MCP Workflow Builder β€” Visual DAG for MCP Tools posts/persistent-memory: 🧠 Persistent Memory β€” Long-Term Memory for AI Agents via MCP posts/playcli: 🎬 PlayCLI β€” Terminal Video Player posts/postgres-mvcc: πŸ—„οΈ How PostgreSQL MVCC Works β€” Multi-Version Concurrency Control Deep Dive posts/raft-consensus: β›΅ Raft Consensus Algorithm Explained posts/rust-borrow-checker: πŸ¦€ Rust Borrow Checker β€” Catches Real Bugs posts/titan: πŸ€– Titan β€” Terminal AI Coding Agent posts/what-happens-url: 🌐 What Happens Between Typing a URL and Seeing the Page posts/what-happens-when-you-run-a-program: βš™οΈ What Actually Happens When You Run a Program posts/zero-knowledge-proofs: πŸ” Zero-Knowledge Proofs Explained Simply webtui/components/accordion: Accordion webtui/components/badge: Badge webtui/components/button: Button webtui/components/checkbox: Checkbox webtui/components/dialog: Dialog webtui/components/input: Input webtui/components/popover: Popover webtui/components/pre: Pre webtui/components/progress: Progress webtui/components/radio: Radio webtui/components/range: Range webtui/components/separator: Separator webtui/components/spinner: Spinner webtui/components/switch: Switch webtui/components/table: Table webtui/components/textarea: Textarea webtui/components/tooltip: Popover webtui/components/typography: Typography webtui/components/view: View webtui/contributing/contributing: Contributing webtui/contributing/contributing: ## Local Development webtui/contributing/contributing: ## Issues webtui/contributing/contributing: ## Pull Requests webtui/contributing/style-guide: Style Guide webtui/contributing/style-guide: ## CSS Units webtui/contributing/style-guide: ## Selectors webtui/contributing/style-guide: ## Documentation webtui/installation/astro: Astro webtui/installation/astro: ## Scoping webtui/installation/astro: ### Frontmatter Imports webtui/installation/astro: ### β€Ήstyleβ€Ί tag webtui/installation/astro: ### Full Library Import webtui/installation/nextjs: Next.js webtui/installation/vite: Vite webtui/plugins/plugin-dev: Developing Plugins webtui/plugins/plugin-dev: ### Style Layers webtui/plugins/plugin-nf: Nerd Font Plugin webtui/plugins/theme-catppuccin: Catppuccin Theme webtui/plugins/theme-custom: Custom Theme webtui/plugins/theme-everforest: Everforest Theme webtui/plugins/theme-gruvbox: Gruvbox Theme webtui/plugins/theme-nord: Nord Theme webtui/plugins/theme-vitesse: Vitesse Theme webtui/start/ascii-boxes: ASCII Boxes webtui/start/changelog: Changelog webtui/start/installation: Installation webtui/start/installation: ## Installation webtui/start/installation: ## Using CSS webtui/start/installation: ## Using ESM webtui/start/installation: ## Using a CDN webtui/start/installation: ## Full Library Import webtui/start/installation: ### CSS webtui/start/installation: ### ESM webtui/start/installation: ### CDN webtui/start/intro: Introduction webtui/start/intro: ## Features webtui/start/plugins: Plugins webtui/start/plugins: ## Official Plugins webtui/start/plugins: ### Themes webtui/start/plugins: ## Community Plugins webtui/start/theming: Theming webtui/start/theming: ## CSS Variables webtui/start/theming: ### Font Styles webtui/start/theming: ### Colors webtui/start/theming: ### Light & Dark webtui/start/theming: ## Theme Plugins webtui/start/theming: ### Using Multiple Theme Accents webtui/start/tuis-vs-guis: TUIs vs GUIs webtui/start/tuis-vs-guis: ## Monospace Fonts webtui/start/tuis-vs-guis: ## Character Cells
 Theme Current: Light j/k or ↑/↓ + Enter

🌳 LSM Trees & Bloom Filters β€” Production Deep Dive

Why LSM trees exist, how they work (MemTable β†’ WAL β†’ SSTable β†’ compaction), the read path with bloom filters, tiered vs leveled compaction, write amplification, and the RUM conjecture.

🧱 The Problem LSM Trees Solve

Databases are built around one fundamental constraint: random writes are slow on physical media.

MediaRandom WriteSequential WriteRatio
HDD~0.5 MB/s (seek time ~10ms)~200 MB/s1:400
SSD~50 MB/s (write amplification, erase blocks)~2000 MB/s1:40

If you insert 1,000 rows into a B-tree one at a time, each insert triggers a random write to disk. On an HDD, that’s 10 seconds of seek time for 1,000 random inserts.

LSM (Log-Structured Merge) trees solve this by buffering writes in memory and flushing them as large sequential batches to disk. The idea originated in the 1996 Ousterhout paper on log-structured file systems and was popularized by Google’s Bigtable (2006), then LevelDB, RocksDB, Cassandra, ScyllaDB, and many others.


πŸ—οΈ LSM Tree Architecture

An LSM tree has three main components:

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  MemTable    β”‚  ← in-memory balanced tree (red-black / skip list)
                    β”‚  (read+write)β”‚
                    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
                           β”‚ flush (when full)
                           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  WAL (Write-Ahead Log) on disk               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ op β”‚ op β”‚ op β”‚ op β”‚ op β”‚ op β”‚ op β”‚ op β”‚  β”‚  ← sequential append
β”‚  β””β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                           β–Ό
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚  Immutable   β”‚  ← frozen, no longer accepting writes
                    β”‚  MemTable    β”‚
                    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
                           β”‚ flush to disk
                           β–Ό
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚  SSTable File (Level 0) β”‚  ← sorted, immutable data file
              β”‚  [Footer]               β”‚
              β”‚  [Index Block]          β”‚
              β”‚  [Bloom Filter Block]   β”‚
              β”‚  [Data Blocks...]       β”‚
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                    compaction merges
                           β–Ό
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚  SSTable (Level 1)      β”‚  ← larger, merged
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

MemTable

The MemTable is an in-memory data structure (typically a skip list in LevelDB/RocksDB or a red-black tree). All writes go here first:

// Pseudocode: skipping to a skip list
void put(const Slice& key, const Slice& value) {
    WAL_append(key, value);              // Durability: write to log first
    memtable->insert(key, value);        // Then update in-memory table
}

The MemTable supports O(log n) reads and writes. When it reaches a configurable size (typically 4-64 MB), it’s made immutable and a new MemTable takes its place.

WAL (Write-Ahead Log)

Before updating the MemTable, every write is appended to the WAL β€” a sequential file on disk. This provides durability: if the process crashes, the WAL is replayed on restart to reconstruct the MemTable.

  • WAL is written sequentially β†’ fast on both HDD and SSD
  • On crash recovery: replay WAL from last checkpoint
  • WAL can be fsync’d per write (durability) or batched (performance)

SSTable (Sorted String Table)

When an immutable MemTable is flushed to disk, it becomes an SSTable β€” an immutable, sorted file on disk. The format:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Data Block 0  (keys a-f)    β”‚
β”‚ Data Block 1  (keys g-l)    β”‚
β”‚ Data Block 2  (keys m-r)    β”‚
β”‚ Data Block 3  (keys s-z)    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Bloom Filter (all keys)     β”‚  ← false-positive check
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Index                       β”‚  ← for each block: last key + offset
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Block 0: offset=0     β”‚  β”‚
β”‚  β”‚ Block 1: offset=4096   β”‚  β”‚
β”‚  β”‚ Block 2: offset=8192   β”‚  β”‚
β”‚  β”‚ Block 3: offset=12288  β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Footer                      β”‚  ← pointer to index + bloom filter
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The index block at the end is loaded into memory on SSTable open. It maps the last key of each data block to the block’s offset, so the read path can binary-search the index to find which data block to load.


πŸ” The Read Path (Step by Step)

Reading a key from an LSM tree is more expensive than writing to one because data exists in multiple levels:

Read "key_xyz":

1. Check MemTable (in memory)
   β”œβ”€β”€ Found? Return immediately.
   └── Not found? Continue.

2. Check immutable MemTable (in memory)
   β”œβ”€β”€ Found? Return immediately.
   └── Not found? Continue.

3. For each SSTable, newest to oldest:
   a. Query the SSTable's bloom filter
      β”œβ”€β”€ "key_xyz definitely not here" β†’ skip this SSTable
      └── "key_xyz might be here" β†’ continue
   
   b. Binary-search the in-memory index to find the relevant data block
   c. Read and decompress the data block from disk
   d. Binary-search within the data block
      β”œβ”€β”€ Found? Return value.
      └── Not found? Continue to next SSTable.

4. Key does not exist.

This is why LSM-tree reads can be expensive: in the worst case, you check every level. Bloom filters are critical for skipping SSTables that can’t contain the key.


🌸 Bloom Filters: How They Work

A bloom filter is a probabilistic data structure that answers: β€œIs element x in set S?”

  • No false negatives: If the filter says β€œno”, the element is definitely not in the set.
  • False positives possible: If the filter says β€œyes”, the element might be in the set (we still need to check the actual data).

The Math

A bloom filter is a bit array of m bits with k hash functions.

Insert "apple":
  h1("apple") = 2   β†’ set bit 2
  h2("apple") = 7   β†’ set bit 7
  h3("apple") = 12  β†’ set bit 12

Insert "banana":
  h1("banana") = 5  β†’ set bit 5
  h2("banana") = 7  β†’ set bit 7 (already set)
  h3("banana") = 1  β†’ set bit 1

Query "apple":
  h1("apple") = 2   β†’ bit is 1 βœ“
  h2("apple") = 7   β†’ bit is 1 βœ“
  h3("apple") = 12  β†’ bit is 1 βœ“  β†’ "probably present"

Query "grape":
  h1("grape") = 2   β†’ bit is 1
  h2("grape") = 9   β†’ bit is 0 βœ— β†’ "definitely absent"

Optimal Parameter Calculation

Given n elements and desired false-positive rate p:

m = -n Β· ln(p) / (ln(2))Β²     // optimal number of bits
k = (m/n) Β· ln(2)              // optimal number of hash functions
Desired FPRm/n (bits per key)k (hash functions)
10%4.83
1%9.67
0.1%14.410
0.01%19.214

RocksDB default SSTable bloom filter: 10 bits per key (~0.8% false positive rate).

Practical Implementation

A common optimization: each SSTable has its own bloom filter. The filter for L0 might have 100 keys (smaller, less accurate), while the filter for L3 might have 10M keys (larger, more memory).

// Simplified bloom filter class
class BloomFilter {
    std::bitset<m> bits;
    HashFn hashes[k];

    void insert(Slice key) {
        for (auto& h : hashes) {
            bits.set(h(key) % m);
        }
    }

    bool possibly_contains(Slice key) {
        for (auto& h : hashes) {
            if (!bits.test(h(key) % m)) return false;
        }
        return true; // may be false positive
    }
};

πŸ”„ Compaction: Why LSM Trees Don’t Grow Forever

Without compaction, you’d have thousands of SSTables on disk, and every read would need to check all of them. Compaction merges SSTables together and discards old data.

Tiered Compaction (Cassandra / HBase)

Level 0: [sst1] [sst2] [sst3] [sst4]  ← up to N files
                  ↓ merge to L1
Level 1: [sst_large]                   ← single merged file
                  ↓ when L1 gets big
Level 2: [sst_even_larger]

Strategy: Each level can hold up to N SSTables. When a level exceeds N, all its files are merged into one file in the next level.

  • Pro: Lower write amplification (data is compacted only once when promoted)
  • Con: Temporary space amplification (multiple copies of the same key exist)

Leveled Compaction (LevelDB / RocksDB default)

Level 0: [sst1] [sst2] [sst3]  ← files may overlap in key range
Level 1: β”Œβ”€β”€β”¬β”€β”€β”¬β”€β”€β”¬β”€β”€β”¬β”€β”€β”¬β”€β”€β”  ← non-overlapping, sorted runs
         β”‚a-eβ”‚f-jβ”‚k-oβ”‚p-tβ”‚u-xβ”‚y-zβ”‚
         β””β”€β”€β”΄β”€β”€β”΄β”€β”€β”΄β”€β”€β”΄β”€β”€β”΄β”€β”€β”˜
Level 2: β”Œβ”€β”€β”¬β”€β”€β”¬β”€β”€β”¬β”€β”€β”¬β”€β”€β”¬β”€β”€β”¬β”€β”€β”¬β”€β”€β”¬β”€β”€β”¬β”€β”€β”¬β”€β”€β”¬β”€β”€β”
         β”‚a-cβ”‚d-fβ”‚g-iβ”‚j-lβ”‚m-oβ”‚p-rβ”‚s-uβ”‚v-xβ”‚y-zβ”‚...
         β””β”€β”€β”΄β”€β”€β”΄β”€β”€β”΄β”€β”€β”΄β”€β”€β”΄β”€β”€β”΄β”€β”€β”΄β”€β”€β”΄β”€β”€β”΄β”€β”€β”΄β”€β”€β”΄β”€β”€β”˜
         (10Γ— larger)

Strategy: Each level is 10Γ— larger than the previous. An L1 file is compacted into a subset of L2 files that overlap in key range.

  • Pro: Better read performance (fewer files to check per level)
  • Con: Higher write amplification (same key data gets rewritten multiple times as it flows down levels)

Write Amplification

Write amplification is the ratio of bytes written to disk vs bytes of new data ingested:

Write Amplification = (Total bytes written to disk) / (Ingested data)
CompactionTypical Write AmpTypical Space Amp
Leveled (LevelDB)10-30Γ—1.1Γ—
Tiered (Cassandra)3-10Γ—1.5-3Γ—
Size-Tiered (HBase)4-8Γ—2-5Γ—

A write amplification of 20Γ— means: if you write 1 GB of new data, 20 GB is written to disk (including compaction rewrites). This matters for SSD lifespan β€” consumer SSDs are rated for ~100-300 TBW (Total Bytes Written).


πŸ“ The RUM Conjecture

The RUM Conjecture (Read Overhead, Update Overhead, Memory/Storage Overhead) states that for a data structure or access method:

You can optimize any two of Read, Update, and Memory but must sacrifice the third.

                    Read
                     β–²
                     β”‚
        B-tree       β”‚     Hash table
       (R low,      β”‚    (R low,
        U high,     β”‚     U low,
        M mid)      β”‚     M high)
                     β”‚
                     β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ί Update
                     β”‚
                     β”‚
        LSM Tree     β”‚
       (R high,      β”‚
        U low,       β”‚
        M mid)       β”‚
                     β”‚
                    Memory
  • B-tree: Fast reads (one traversal to leaf), slow random writes (cache line split, rebalancing), medium memory.
  • Hash table: Fast reads (O(1)), fast writes (O(1) amortized), high memory (keep everything in RAM).
  • LSM tree: Fast writes (sequential), slow reads (check N levels), medium memory (bloom filters + index blocks).

In practice, you tune LSM parameters to shift along the RUM triangle:

  • More bloom filter bits β†’ more memory, fewer false positives β†’ faster reads
  • Smaller SSTable sizes β†’ more files to check β†’ slower reads, faster compaction
  • Leveled compaction β†’ better reads, worse write amplification

🏭 Production Examples

RocksDB (Facebook/Meta)

RocksDB powers MySQL’s MyRocks storage engine, Apache Kafka’s internal state stores, and many more systems:

// RocksDB configuration for write-heavy workload
Options options;
options.create_if_missing = true;
options.write_buffer_size = 64 << 20;        // 64 MB MemTable
options.max_write_buffer_number = 4;         // up to 3 immutables
options.target_file_size_base = 64 << 20;    // 64 MB SSTables
options.max_bytes_for_level_base = 512 << 20; // 512 MB for L1
options.soft_pending_compaction_bytes_limit = 64ULL << 30;
options.level0_slowdown_writes_trigger = 20;
options.level0_stop_writes_trigger = 36;

Key RocksDB features:

  • Prefix bloom filters: Skip reading entire SSTable when prefix is known
  • Partitioned index/filters: Read only a fraction of the index into memory
  • Dictionary compression: Each SSTable data block is compressed (lz4, zstd, snappy)
  • Rate limiter: Throttle compaction I/O to avoid starving user reads

Cassandra (Apache)

Cassandra uses tiered compaction by default (SizeTieredCompactionStrategy):

Table schema:
CREATE TABLE user_timeline (
    user_id UUID,
    timestamp TIMESTAMP,
    content TEXT,
    PRIMARY KEY (user_id, timestamp)
) WITH compaction = {'class': 'LeveledCompactionStrategy'};

Cassandra bloom filters are stored off-heap (in native memory, not Java heap) and are serialized with each SSTable. When a node restarts, bloom filters are loaded into memory (not rebuilt from data) β€” this takes seconds for hundreds of GB of data.


πŸ“Š Summary: LSM Tree Trade-offs

AspectLSM TreeB-tree
Random writesFast (buffered in MemTable)Slow (4KB random writes to disk)
Sequential readsFast (within SSTable)Fast (B-tree traversal)
Random readsSlow (check N levels, even with bloom filters)Fast (single traversal)
Space amplificationMedium (duplicate keys across levels)Low (in-place updates)
Write amplification5-30Γ— depending on compactionLow (~1Γ— with in-place update)
Range scansFast (sorted SSTables, merge)Fast (in-order leaf traversal)
Concurrent writesGood (MemTable is lock-free skip list)Moderate (page latch contention)
Crash recoveryFast (replay WAL)Slow (redo log replay + recovery)

LSM trees are the dominant design for modern write-heavy workloads: time-series databases (InfluxDB/TimescaleDB), key-value stores (RocksDB/LevelDB), wide-column stores (Cassandra/Bigtable/HBase), and search indices (Lucene/Solr/Elasticsearch segments are LSM-like).

The next time you choose a database, remember: you’re really choosing where on the RUM triangle you want to sit.


πŸ“– Series Navigation

 praneshnikhar.site / posts / lsm-trees-bloom-filters Β· Top 1:1