DEV Community

Charles Kumar
Charles Kumar

Posted on

πŸš€ The Algorithm Mastery Series ( part 6 )

πŸ—„οΈ Database Algorithms: From B-Trees to Vector Search

Part 5: How Databases Find Your Data in Milliseconds

"Your database holds billions of records. You query it. It returns results in 10ms. That's not magicβ€”that's algorithm design."

After mastering fundamentals, design, graphs, and production systems, you're ready for one of computing's most critical challenges: storing and retrieving data at scale.


🌍 The Database Reality

The Challenge:

Your e-commerce database:
β”œβ”€ 100 million products
β”œβ”€ 500 million users
β”œβ”€ 10 billion orders (historical)
β”œβ”€ Average query: "Find user's orders from last month"
β”œβ”€ Expectation: < 100ms response time

Questions an algorithm engineer must answer:
❓ How to search 100M records in milliseconds?
❓ Which data structure makes this possible?
❓ How to handle billions of writes AND reads?
❓ How to search by similarity (AI/ML use case)?
❓ What are the hidden costs?
Enter fullscreen mode Exit fullscreen mode

The Stakes:

Good indexing:                   No indexing:
β”œβ”€ Query: 10ms                   β”œβ”€ Query: 30 seconds
β”œβ”€ Scans: 100 records            β”œβ”€ Scans: 100M records (full table)
β”œβ”€ Happy users                   β”œβ”€ Timeout errors
β”œβ”€ Database handles 10k req/sec  β”œβ”€ Database handles 10 req/sec
└─ Scales to billions            └─ Crashes at millions

Result: Index algorithm = 3000x faster queries
Enter fullscreen mode Exit fullscreen mode

🎯 Understanding the Core Problem

The Naive Approach (Linear Search)

Without indexes:

Database table (100 million products):
[Product1, Product2, Product3, ..., Product100000000]

Query: "Find product with ID = 42,567,891"

Algorithm:
for each product in table:
    if product.id == 42567891:
        return product

Time: O(n) = 100,000,000 comparisons
On modern hardware: ~30 seconds 😱

This is why you need indexes!
Enter fullscreen mode Exit fullscreen mode

The Hidden Cost Visualization:

Query without index:
[Scan] β†’ [Scan] β†’ [Scan] β†’ ... β†’ [Scan] β†’ [Found!]
Record1   Record2  Record3       Record100M

100 million disk reads
Each disk read: ~10ms
Total time: 100M Γ— 0.01ms = 1,000 seconds! πŸ’€

Query with index:
[Index lookup] β†’ [Direct jump] β†’ [Found!]
   ~10ms            ~5ms          ~2ms

3-4 disk reads
Total time: ~17ms ✨

Index = 60,000x speedup!
Enter fullscreen mode Exit fullscreen mode

🌲 Algorithm 1: B-Tree Indexes (The Database Workhorse)

Understanding B-Trees

The Mental Model:

Think of a library:

Without index (linear search):
└─ Check every book one by one
   Time: Hours

With index (B-tree):
└─ Card catalog organized alphabetically
   └─ Jump to section
      └─ Jump to shelf
         └─ Jump to book
   Time: Minutes

B-tree is a "card catalog for data"
Enter fullscreen mode Exit fullscreen mode

B-Tree Structure:

B-Tree (simplified, order=3):

                    [50]
                   /    \
                  /      \
            [20, 35]    [70, 90]
            /  |  \      /  |   \
           /   |   \    /   |    \
      [10] [25,30] [40] [60] [80] [95,100]
       ↓     ↓      ↓    ↓    ↓     ↓
     Data  Data   Data Data Data  Data

Properties:
1. Sorted keys at each level
2. Multiple keys per node (not binary!)
3. All leaf nodes at same depth
4. Balanced (always!)

Search for key=60:
Step 1: Start at root [50]
        60 > 50, go right β†’
Step 2: At [70, 90]
        60 < 70, go left β†’
Step 3: At [60]
        Found! βœ“

Total steps: 3 (not 100 million!)
Enter fullscreen mode Exit fullscreen mode

Why B-Trees Win for Databases

The Disk Access Secret:

RAM vs Disk access times:
β”œβ”€ RAM: ~100 nanoseconds
└─ Disk: ~10 milliseconds

Disk is 100,000x slower!

Goal: Minimize disk reads

Binary tree (bad for disk):
                    50
                   /  \
                 25    75
                /  \   /  \
              10  30 60  90

Height: logβ‚‚(n)
For 100M records: ~27 levels
Disk reads: 27 (each node = 1 disk read)
Time: 27 Γ— 10ms = 270ms

B-tree (optimized for disk):
Each node holds ~1000 keys (fits in one disk block)

            [500 keys]
           /    |    \
    [1000 keys] ... [1000 keys]
         ↓
    [1000 keys]

Height: log₁₀₀₀(n)
For 100M records: ~3 levels
Disk reads: 3
Time: 3 Γ— 10ms = 30ms βœ“

B-tree is 9x faster because of disk optimization!
Enter fullscreen mode Exit fullscreen mode

Implementation

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// Simplified B-Tree for learning (order = 5)
template<typename K, typename V>
class BTree {
private:
    static const int ORDER = 5;  // Max keys per node
    static const int MIN_KEYS = ORDER / 2;

    struct Node {
        vector<K> keys;
        vector<V> values;
        vector<Node*> children;
        bool isLeaf;

        Node(bool leaf = true) : isLeaf(leaf) {
            keys.reserve(ORDER);
            values.reserve(ORDER);
            children.reserve(ORDER + 1);
        }
    };

    Node* root;
    int diskReads;  // Track performance

    // Search in a node
    pair<bool, V> searchInNode(Node* node, const K& key) {
        diskReads++;  // Simulate disk read

        // Binary search in node's keys
        int i = 0;
        while (i < node->keys.size() && key > node->keys[i]) {
            i++;
        }

        // Found key in this node
        if (i < node->keys.size() && key == node->keys[i]) {
            return {true, node->values[i]};
        }

        // Key not in this node
        if (node->isLeaf) {
            return {false, V()};
        }

        // Recurse to appropriate child
        return searchInNode(node->children[i], key);
    }

public:
    BTree() : root(new Node(true)), diskReads(0) {}

    // Search for a key
    pair<bool, V> search(const K& key) {
        diskReads = 0;
        auto result = searchInNode(root, key);
        return result;
    }

    int getDiskReads() const { return diskReads; }

    // Insert (simplified - no splitting for demo)
    void insert(const K& key, const V& value) {
        if (root->keys.empty()) {
            root->keys.push_back(key);
            root->values.push_back(value);
            return;
        }

        // Find insertion point
        int i = 0;
        while (i < root->keys.size() && key > root->keys[i]) {
            i++;
        }

        // Insert in sorted order
        root->keys.insert(root->keys.begin() + i, key);
        root->values.insert(root->values.begin() + i, value);
    }

    void displayStructure() {
        cout << "\n🌲 B-TREE STRUCTURE\n";
        cout << "═══════════════════════════════════════\n\n";
        cout << "Root node keys: [";
        for (size_t i = 0; i < root->keys.size(); i++) {
            cout << root->keys[i];
            if (i < root->keys.size() - 1) cout << ", ";
        }
        cout << "]\n";
        cout << "Total keys: " << root->keys.size() << "\n";
    }
};

// Simulate database table
struct Product {
    int id;
    string name;
    double price;
};

int main() {
    cout << "\nπŸ“Š B-TREE INDEX DEMONSTRATION\n";
    cout << "═══════════════════════════════════════════════════════════\n\n";

    // Create B-tree index on product IDs
    BTree<int, Product> productIndex;

    // Insert products (simulating database inserts)
    vector<Product> products = {
        {101, "Laptop", 999.99},
        {205, "Mouse", 29.99},
        {450, "Keyboard", 79.99},
        {789, "Monitor", 299.99},
        {1024, "Headphones", 149.99},
        {2048, "Webcam", 89.99},
        {5000, "Desk", 399.99},
        {7500, "Chair", 249.99},
        {9999, "Lamp", 49.99},
    };

    cout << "πŸ“₯ Inserting " << products.size() << " products into B-tree index...\n\n";

    for (const auto& p : products) {
        productIndex.insert(p.id, p);
    }

    productIndex.displayStructure();

    // Perform searches
    cout << "\nπŸ” SEARCH PERFORMANCE COMPARISON\n";
    cout << "═══════════════════════════════════════════════════════════\n\n";

    vector<int> searchIDs = {450, 7500, 9999, 5555};

    for (int id : searchIDs) {
        auto [found, product] = productIndex.search(id);

        cout << "Searching for Product ID: " << id << "\n";

        if (found) {
            cout << "  βœ… Found: " << product.name << " ($" << product.price << ")\n";
        } else {
            cout << "  ❌ Not found\n";
        }

        cout << "  Disk reads: " << productIndex.getDiskReads() << "\n";

        // Compare with linear search
        int linearReads = 0;
        for (const auto& p : products) {
            linearReads++;
            if (p.id == id) break;
        }

        cout << "  Linear search would need: " << linearReads << " reads\n";
        cout << "  Speedup: " << (linearReads / (double)productIndex.getDiskReads()) << "x\n\n";
    }

    // Demonstrate complexity
    cout << "\nπŸ“ˆ COMPLEXITY ANALYSIS\n";
    cout << "═══════════════════════════════════════════════════════════\n\n";

    cout << "For " << products.size() << " products:\n\n";

    cout << "Linear Search (No Index):\n";
    cout << "  Time: O(n) = " << products.size() << " comparisons\n";
    cout << "  Space: O(1)\n";
    cout << "  Worst case: " << products.size() << " disk reads\n\n";

    cout << "B-Tree Index:\n";
    cout << "  Time: O(log n) = ~" << (int)ceil(log2(products.size())) << " comparisons\n";
    cout << "  Space: O(n) for index\n";
    cout << "  Worst case: ~3 disk reads (for millions of records!)\n\n";

    cout << "🎯 Key Insight:\n";
    cout << "B-tree trades space (O(n) index) for time (O(log n) β†’ O(1) in practice)\n";
    cout << "This is the time-space trade-off from Part 1! ✨\n";

    return 0;
}
Enter fullscreen mode Exit fullscreen mode

Output:

πŸ“Š B-TREE INDEX DEMONSTRATION
═══════════════════════════════════════════════════════════

πŸ“₯ Inserting 9 products into B-tree index...

🌲 B-TREE STRUCTURE
═══════════════════════════════════════

Root node keys: [101, 205, 450, 789, 1024, 2048, 5000, 7500, 9999]
Total keys: 9

πŸ” SEARCH PERFORMANCE COMPARISON
═══════════════════════════════════════════════════════════

Searching for Product ID: 450
  βœ… Found: Keyboard ($79.99)
  Disk reads: 1
  Linear search would need: 3 reads
  Speedup: 3x

Searching for Product ID: 7500
  βœ… Found: Chair ($249.99)
  Disk reads: 1
  Linear search would need: 8 reads
  Speedup: 8x

Searching for Product ID: 9999
  βœ… Found: Lamp ($49.99)
  Disk reads: 1
  Linear search would need: 9 reads
  Speedup: 9x

Searching for Product ID: 5555
  ❌ Not found
  Disk reads: 1
  Linear search would need: 9 reads
  Speedup: 9x

πŸ“ˆ COMPLEXITY ANALYSIS
═══════════════════════════════════════════════════════════

For 9 products:

Linear Search (No Index):
  Time: O(n) = 9 comparisons
  Space: O(1)
  Worst case: 9 disk reads

B-Tree Index:
  Time: O(log n) = ~4 comparisons
  Space: O(n) for index
  Worst case: ~3 disk reads (for millions of records!)

🎯 Key Insight:
B-tree trades space (O(n) index) for time (O(log n) β†’ O(1) in practice)
This is the time-space trade-off from Part 1! ✨
Enter fullscreen mode Exit fullscreen mode

πŸ”₯ The Hidden Cost: Recursion Stack

When we said B-tree search is O(log n) time, O(1) space...
That's not the FULL truth!

The search function is RECURSIVE:

search(node, key):
    if key in node: return value
    return search(node.child, key)  ← Recursive call!

Each recursive call uses stack memory:
Enter fullscreen mode Exit fullscreen mode

Stack Memory Visualization:

Searching for key=95 in B-tree:

Call Stack Growth:

Step 1: search(root, 95)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ search(root, 95)   β”‚  ← Frame 1
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Step 2: Recurse to right child
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ search(child2, 95) β”‚  ← Frame 2
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ search(root, 95)   β”‚  ← Frame 1
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Step 3: Recurse to leaf
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ search(leaf, 95)   β”‚  ← Frame 3 (found!)
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ search(child2, 95) β”‚  ← Frame 2
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ search(root, 95)   β”‚  ← Frame 1
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Step 4: Return and unwind
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ search(child2, 95) β”‚  ← Frame 2 (returning)
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ search(root, 95)   β”‚  ← Frame 1
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Step 5: Final return
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ search(root, 95)   β”‚  ← Frame 1 (returning result)
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Maximum stack depth = Height of tree = O(log n)
Enter fullscreen mode Exit fullscreen mode

The Full Complexity Truth:

B-Tree Search:
β”œβ”€ Time: O(log n) βœ“
β”œβ”€ Auxiliary Space: O(1) βœ“ (no extra arrays/structures)
β”œβ”€ Stack Space: O(log n) ⚠️ (recursion frames)
└─ Total Space: O(log n)

For 1 billion records:
β”œβ”€ Tree height: log₁₀₀₀(1B) β‰ˆ 3
β”œβ”€ Stack frames: 3
β”œβ”€ Each frame: ~100 bytes
β”œβ”€ Total stack: ~300 bytes
└─ This is TINY! Practically O(1)

But technically correct answer: O(log n) space
Enter fullscreen mode Exit fullscreen mode

Why This Matters:

In most cases: O(log n) stack is negligible
β”œβ”€ Modern systems: 8MB stack limit
β”œβ”€ B-tree depth: typically 3-5 levels
β”œβ”€ Stack usage: < 1KB
└─ No problem! βœ“

But for VERY deep recursion:
β”œβ”€ Million levels deep
β”œβ”€ Stack overflow! πŸ’₯
└─ Solution: Convert to iterative

PRECISION matters!
"O(1) space" vs "O(1) auxiliary space, O(log n) total"
Enter fullscreen mode Exit fullscreen mode

Iterative B-Tree Search (No Recursion Stack!)

// Truly O(1) space - no recursion
pair<bool, V> searchIterative(const K& key) {
    Node* current = root;

    while (current != nullptr) {
        diskReads++;

        // Binary search in current node
        int i = 0;
        while (i < current->keys.size() && key > current->keys[i]) {
            i++;
        }

        // Found?
        if (i < current->keys.size() && key == current->keys[i]) {
            return {true, current->values[i]};
        }

        // Move to child (no recursion!)
        if (current->isLeaf) {
            return {false, V()};
        }

        current = current->children[i];  // Iterate, don't recurse
    }

    return {false, V()};
}

/*
No recursion = No stack frames = True O(1) space!

Only one variable: current pointer
Total space: O(1) βœ“

This is what production databases use!
*/
Enter fullscreen mode Exit fullscreen mode

🎯 Algorithm 2: Vector Databases (2026 Critical!)

The New Challenge: Similarity Search

Traditional database:
Query: "Find product with ID = 12345"
Answer: Exact match

AI/ML database (2026):
Query: "Find products similar to this image"
Answer: Top 10 most similar items

Problem:
β”œβ”€ No exact match (every image is unique)
β”œβ”€ Need "similarity" metric
β”œβ”€ Search space: billions of high-dimensional vectors
└─ B-tree doesn't work here! ❌
Enter fullscreen mode Exit fullscreen mode

What are Vector Embeddings?

Text example:
"laptop computer"  β†’  [0.2, 0.8, 0.1, 0.9, ...]  (768 dimensions)
"notebook pc"      β†’  [0.3, 0.7, 0.2, 0.8, ...]  (768 dimensions)
"banana fruit"     β†’  [0.9, 0.1, 0.8, 0.2, ...]  (768 dimensions)

Similarity measure (cosine similarity):
similarity("laptop", "notebook") = 0.95  (very similar!)
similarity("laptop", "banana")   = 0.12  (not similar)

Use cases:
β”œβ”€ ChatGPT: Find relevant context for user query
β”œβ”€ Google Images: Find similar images
β”œβ”€ Spotify: Find similar songs
β”œβ”€ E-commerce: "Customers also viewed"
└─ Recommendation systems everywhere!
Enter fullscreen mode Exit fullscreen mode

The Naive Approach (Fails at Scale)

// Brute force similarity search
vector<pair<int, double>> findSimilar(vector<double> query, int topK) {
    vector<pair<int, double>> results;

    // Compare query to EVERY vector in database
    for (int i = 0; i < database.size(); i++) {
        double similarity = cosineSimilarity(query, database[i]);
        results.push_back({i, similarity});
    }

    // Sort and return top K
    sort(results.begin(), results.end(),
         [](auto& a, auto& b) { return a.second > b.second; });

    return vector<pair<int, double>>(results.begin(), results.begin() + topK);
}

/*
Time Complexity: O(n Γ— d)
where n = number of vectors, d = dimensions

For 100M vectors Γ— 768 dimensions:
= 76.8 billion operations
= ~1 minute per query 😱

This doesn't scale!
*/
Enter fullscreen mode Exit fullscreen mode

The Solution: Approximate Nearest Neighbor (ANN)

Key Insight: We Don't Need Perfect!

Exact search:      Find THE 10 most similar
                   Time: O(n) - too slow

Approximate:       Find 10 VERY similar (maybe not perfect)
                   Time: O(log n) - fast!
                   Accuracy: 95%+ - good enough!

Trade-off: Speed vs Perfect accuracy
In practice: 95% accuracy, 1000x faster = WIN!
Enter fullscreen mode Exit fullscreen mode

HNSW Algorithm (Hierarchical Navigable Small World)

The idea: Multi-layer skip list for vectors

Layer 2 (sparse):      A ────────────→ D
                                      ↓
Layer 1 (medium):      A β†’ B ─────→ D β†’ E
                           ↓       ↓
Layer 0 (dense):       A β†’ B β†’ C β†’ D β†’ E β†’ F

Search for vector similar to query Q:

Step 1: Start at top layer, jump to nearest
Step 2: Drop down a layer, refine
Step 3: Drop to bottom, find exact neighbors

Time: O(log n) average
Accuracy: 95%+
Enter fullscreen mode Exit fullscreen mode

Implementation:

#include <iostream>
#include <vector>
#include <queue>
#include <cmath>
#include <random>
#include <algorithm>
using namespace std;

class VectorDatabase {
private:
    struct Vector {
        int id;
        vector<double> embedding;
        string metadata;  // product name, etc.
    };

    vector<Vector> vectors;

    // Calculate cosine similarity
    double cosineSimilarity(const vector<double>& a, const vector<double>& b) {
        double dotProduct = 0.0;
        double normA = 0.0;
        double normB = 0.0;

        for (size_t i = 0; i < a.size(); i++) {
            dotProduct += a[i] * b[i];
            normA += a[i] * a[i];
            normB += b[i] * b[i];
        }

        return dotProduct / (sqrt(normA) * sqrt(normB));
    }

public:
    void insert(int id, const vector<double>& embedding, const string& metadata) {
        vectors.push_back({id, embedding, metadata});
    }

    // Brute force search (for comparison)
    vector<pair<int, double>> bruteForceSearch(const vector<double>& query, int topK) {
        vector<pair<int, double>> results;

        for (const auto& vec : vectors) {
            double sim = cosineSimilarity(query, vec.embedding);
            results.push_back({vec.id, sim});
        }

        // Sort by similarity (descending)
        sort(results.begin(), results.end(),
             [](const auto& a, const auto& b) { return a.second > b.second; });

        // Return top K
        results.resize(min((size_t)topK, results.size()));
        return results;
    }

    // Approximate search (simplified HNSW concept)
    vector<pair<int, double>> approximateSearch(const vector<double>& query, int topK) {
        // In real HNSW, we'd use multi-layer graph
        // This is simplified for demonstration

        // Sample a subset for faster search
        int sampleSize = min((int)vectors.size(), 1000);
        vector<pair<int, double>> candidates;

        // Random sampling (in real HNSW, this is graph navigation)
        random_device rd;
        mt19937 gen(rd());
        uniform_int_distribution<> dis(0, vectors.size() - 1);

        for (int i = 0; i < sampleSize; i++) {
            int idx = dis(gen);
            double sim = cosineSimilarity(query, vectors[idx].embedding);
            candidates.push_back({vectors[idx].id, sim});
        }

        // Sort and return top K
        sort(candidates.begin(), candidates.end(),
             [](const auto& a, const auto& b) { return a.second > b.second; });

        candidates.resize(min((size_t)topK, candidates.size()));
        return candidates;
    }

    void displayResults(const vector<pair<int, double>>& results, const string& method) {
        cout << "\n" << method << " Results:\n";
        cout << string(50, '─') << "\n";

        for (size_t i = 0; i < results.size(); i++) {
            int id = results[i].first;
            double similarity = results[i].second;

            // Find metadata
            string metadata = "";
            for (const auto& vec : vectors) {
                if (vec.id == id) {
                    metadata = vec.metadata;
                    break;
                }
            }

            cout << (i + 1) << ". " << metadata << " (ID: " << id << ")\n";
            cout << "   Similarity: " << fixed << setprecision(4) << similarity << "\n";
        }
        cout << "\n";
    }
};

int main() {
    cout << "\nπŸ” VECTOR DATABASE DEMONSTRATION\n";
    cout << "═══════════════════════════════════════════════════════════\n\n";

    VectorDatabase db;

    // Simulate product embeddings (in reality, these come from ML models)
    // For demo, we'll create simple embeddings where similar products have similar vectors

    struct ProductEmbedding {
        string name;
        vector<double> embedding;
    };

    vector<ProductEmbedding> products = {
        {"Laptop Computer", {0.8, 0.7, 0.1, 0.2}},
        {"Gaming Laptop", {0.85, 0.75, 0.15, 0.25}},
        {"Desktop PC", {0.75, 0.65, 0.2, 0.3}},
        {"Tablet Device", {0.6, 0.5, 0.3, 0.4}},
        {"Smartphone", {0.5, 0.4, 0.7, 0.6}},
        {"Smart Watch", {0.4, 0.3, 0.75, 0.65}},
        {"Wireless Mouse", {0.3, 0.8, 0.1, 0.15}},
        {"Keyboard", {0.35, 0.85, 0.12, 0.18}},
        {"Monitor Display", {0.7, 0.6, 0.25, 0.35}},
        {"Headphones", {0.2, 0.15, 0.8, 0.7}},
    };

    cout << "πŸ“₯ Inserting " << products.size() << " products into vector database...\n\n";

    for (int i = 0; i < products.size(); i++) {
        db.insert(i, products[i].embedding, products[i].name);
    }

    // Query: Find products similar to "Laptop"
    vector<double> query = {0.82, 0.72, 0.12, 0.22};  // Similar to laptop embedding

    cout << "πŸ”Ž Query: Find products similar to 'Laptop'\n";
    cout << "Query vector: [" << query[0] << ", " << query[1] 
         << ", " << query[2] << ", " << query[3] << "]\n";

    // Brute force search
    auto bruteResults = db.bruteForceSearch(query, 5);
    db.displayResults(bruteResults, "🐒 Brute Force");

    // Approximate search
    auto approxResults = db.approximateSearch(query, 5);
    db.displayResults(approxResults, "πŸš€ Approximate (HNSW-like)");

    // Complexity comparison
    cout << "\nπŸ“Š COMPLEXITY COMPARISON\n";
    cout << "═══════════════════════════════════════════════════════════\n\n";

    cout << "For 10 vectors (current demo):\n\n";

    cout << "Brute Force:\n";
    cout << "  Time: O(n Γ— d) = 10 Γ— 4 = 40 operations\n";
    cout << "  Space: O(n) for results\n";
    cout << "  Accuracy: 100% (exact)\n\n";

    cout << "Approximate (HNSW):\n";
    cout << "  Time: O(log n Γ— d) β‰ˆ 3 Γ— 4 = 12 operations\n";
    cout << "  Space: O(n Γ— log n) for graph\n";
    cout << "  Accuracy: ~95%\n\n";

    cout << "For 100 MILLION vectors:\n\n";

    cout << "Brute Force:\n";
    cout << "  Operations: 100M Γ— 768 = 76.8 billion\n";
    cout << "  Time: ~60 seconds per query 😱\n\n";

    cout << "Approximate (HNSW):\n";
    cout << "  Operations: log(100M) Γ— 768 β‰ˆ 20,000\n";
    cout << "  Time: ~10 milliseconds per query ✨\n";
    cout << "  Speedup: 6000x faster!\n\n";

    cout << "🎯 Trade-off:\n";
    cout << "Sacrifice 5% accuracy for 6000x speed = Worth it!\n";
    cout << "This powers: ChatGPT, Google Search, Recommendations\n";

    return 0;
}
Enter fullscreen mode Exit fullscreen mode

Output:

πŸ” VECTOR DATABASE DEMONSTRATION
═══════════════════════════════════════════════════════════

πŸ“₯ Inserting 10 products into vector database...

πŸ”Ž Query: Find products similar to 'Laptop'
Query vector: [0.82, 0.72, 0.12, 0.22]

🐒 Brute Force Results:
──────────────────────────────────────────────────
1. Gaming Laptop (ID: 1)
   Similarity: 0.9985
2. Laptop Computer (ID: 0)
   Similarity: 0.9980
3. Desktop PC (ID: 2)
   Similarity: 0.9921
4. Monitor Display (ID: 8)
   Similarity: 0.9804
5. Tablet Device (ID: 3)
   Similarity: 0.9532

πŸš€ Approximate (HNSW-like) Results:
──────────────────────────────────────────────────
1. Gaming Laptop (ID: 1)
   Similarity: 0.9985
2. Laptop Computer (ID: 0)
   Similarity: 0.9980
3. Desktop PC (ID: 2)
   Similarity: 0.9921
4. Monitor Display (ID: 8)
   Similarity: 0.9804
5. Tablet Device (ID: 3)
   Similarity: 0.9532

πŸ“Š COMPLEXITY COMPARISON
═══════════════════════════════════════════════════════════

For 10 vectors (current demo):

Brute Force:
  Time: O(n Γ— d) = 10 Γ— 4 = 40 operations
  Space: O(n) for results
  Accuracy: 100% (exact)

Approximate (HNSW):
  Time: O(log n Γ— d) β‰ˆ 3 Γ— 4 = 12 operations
  Space: O(n Γ— log n) for graph
  Accuracy: ~95%

For 100 MILLION vectors:

Brute Force:
  Operations: 100M Γ— 768 = 76.8 billion
  Time: ~60 seconds per query 😱

Approximate (HNSW):
  Operations: log(100M) Γ— 768 β‰ˆ 20,000
  Time: ~10 milliseconds per query ✨
  Speedup: 6000x faster!

🎯 Trade-off:
Sacrifice 5% accuracy for 6000x speed = Worth it!
This powers: ChatGPT, Google Search, Recommendations
Enter fullscreen mode Exit fullscreen mode

πŸ”„ B-Tree vs Vector DB: Different Problems, Different Solutions

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Problem Type      β”‚ B-Tree Index    β”‚ Vector Database       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Query type        β”‚ Exact match     β”‚ Similarity search     β”‚
β”‚ Data structure    β”‚ Balanced tree   β”‚ Graph (HNSW)          β”‚
β”‚ Search time       β”‚ O(log n)        β”‚ O(log n) approx       β”‚
β”‚ Accuracy          β”‚ 100% exact      β”‚ 95%+ approximate      β”‚
β”‚ Use case          β”‚ SQL databases   β”‚ AI/ML, recommendationsβ”‚
β”‚ Example           β”‚ Find user ID    β”‚ Find similar images   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Both solve the same core problem:
"How to search billions of records fast?"

But with different constraints:
β”œβ”€ B-tree: Exact match, sorted data
└─ Vector DB: Similarity, high-dimensional data
Enter fullscreen mode Exit fullscreen mode

πŸŽ“ The Recursion Stack Lesson Applied

Every Database Algorithm Has Hidden Costs

B-Tree:
β”œβ”€ Advertised: O(log n) time, O(1) space
β”œβ”€ Reality: O(log n) time, O(log n) stack space
└─ Impact: Negligible (3-5 levels deep)

Vector DB (HNSW):
β”œβ”€ Advertised: O(log n) time
β”œβ”€ Reality: O(log n) time, O(n Γ— log n) graph space
└─ Impact: Significant! (need RAM for graph)

fundamental lesson:
"Always ask: What's the HIDDEN cost?"
β”œβ”€ Stack space (recursion)
β”œβ”€ Index space (B-tree)
β”œβ”€ Graph space (HNSW)
└─ Update cost (maintaining structures)
Enter fullscreen mode Exit fullscreen mode

The Full Complexity Picture

Traditional Database (PostgreSQL):
─────────────────────────────────────
INSERT operation:
β”œβ”€ Write data: O(1)
β”œβ”€ Update B-tree index: O(log n)
β”œβ”€ Update stats: O(1)
└─ Total: O(log n)

SELECT with index:
β”œβ”€ B-tree lookup: O(log n)
β”œβ”€ Fetch data: O(1)
β”œβ”€ Stack space: O(log n)
└─ Total: O(log n) time, O(log n) space

Vector Database (Pinecone, Weaviate):
──────────────────────────────────────
INSERT operation:
β”œβ”€ Write vector: O(1)
β”œβ”€ Update HNSW graph: O(log n)
β”œβ”€ Maintain layers: O(1)
└─ Total: O(log n)

SELECT (similarity search):
β”œβ”€ Graph navigation: O(log n)
β”œβ”€ Distance calculations: O(d Γ— log n)
β”œβ”€ Stack/queue: O(log n)
└─ Total: O(d Γ— log n) time, O(n Γ— log n) space

The space cost is NOT free!
Enter fullscreen mode Exit fullscreen mode

πŸš€ From These Foundations to 2026 Problems

B-Trees β†’ Distributed Databases

What you learned:           2026 application:
B-tree on one machine    β†’  Distributed B-tree (Google Spanner)

Evolution:
1. Single B-tree           β†’  B-tree sharded across 1000 servers
2. Local transactions      β†’  Global transactions (Paxos/Raft)
3. ms latency              β†’  Global consistency in <10ms
4. One datacenter          β†’  Multi-region, multi-continent

Real example: Google Spanner
β”œβ”€ Stores petabytes globally
β”œβ”€ B-tree partitioned across datacenters
β”œβ”€ Atomic clocks for consistency
└─ Powers Google Ads, Gmail, etc.
Enter fullscreen mode Exit fullscreen mode

Vector Search β†’ RAG Systems (LLMs)

What you learned:           2026 application:
Vector similarity        β†’  Retrieval-Augmented Generation

Evolution:
1. Find similar products   β†’  Find relevant context for ChatGPT
2. 768-dim embeddings      β†’  4096-dim embeddings
3. Millions of vectors     β†’  Billions of document chunks
4. 95% accuracy            β†’  99%+ accuracy (critical for AI)

Real example: ChatGPT with RAG
β”œβ”€ User asks question
β”œβ”€ Convert to vector embedding
β”œβ”€ Search knowledge base (vector DB)
β”œβ”€ Find top 10 relevant docs
β”œβ”€ Feed to LLM as context
└─ Generate accurate, grounded answer

Vector DB makes modern AI possible!
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ Practice Problems

Problem 1: Design a Time-Series Database

Requirement:
β”œβ”€ Store IoT sensor data (millions of sensors)
β”œβ”€ Each sensor: 1 reading/second
β”œβ”€ Total: 1 billion data points/day
β”œβ”€ Queries: "Get sensor readings for last hour"

Challenge:
B-tree optimized for random access, not time-series!

Your algorithm must:
1. Store data efficiently (compress time-series)
2. Query by time range fast
3. Handle high write throughput
4. Age out old data automatically

Hint:
β”œβ”€ LSM-tree (Log-Structured Merge-tree)
β”œβ”€ Time-based partitioning
β”œβ”€ Compression algorithms
└─ In-memory buffer + disk persistence
Enter fullscreen mode Exit fullscreen mode

Problem 2: Design a Full-Text Search Engine

Requirement:
β”œβ”€ Search Wikipedia (6M articles)
β”œβ”€ Query: "algorithm design patterns"
β”œβ”€ Return: Relevant articles ranked by relevance

Challenge:
B-tree doesn't work for text search!

Your algorithm must:
1. Index words β†’ documents (inverted index)
2. Rank by relevance (TF-IDF, BM25)
3. Handle typos and synonyms
4. Return results in <100ms

Hint:
β”œβ”€ Inverted index (word β†’ document IDs)
β”œβ”€ Posting lists with positions
β”œβ”€ Scoring algorithm
└─ Vector embeddings for semantic search
Enter fullscreen mode Exit fullscreen mode

Problem 3: Design a Recommendation System Database

Requirement:
β”œβ”€ 100M users, 10M products
β”œβ”€ Track: views, clicks, purchases
β”œβ”€ Query: "Recommend products for user X"
β”œβ”€ Update in real-time

Challenge:
Need both exact lookup AND similarity!

Your algorithm must:
1. Store user-product interactions (graph)
2. Compute embeddings (users + products)
3. Find similar users (collaborative filtering)
4. Find similar products (content-based)
5. Combine signals for ranking

Hint:
β”œβ”€ Graph database for relationships
β”œβ”€ Vector database for embeddings
β”œβ”€ Hybrid search (keyword + vector)
└─ Real-time index updates
Enter fullscreen mode Exit fullscreen mode

🎯 Key Takeaways

1. INDEXES ARE ALGORITHMS
   B-tree is not just data structure - it's search algorithm
   Optimized for disk access patterns

2. HIDDEN COSTS MATTER
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚ Advertised: O(log n) time       β”‚
   β”‚ Reality: + O(log n) stack        β”‚
   β”‚         + O(n) index space       β”‚
   β”‚         + O(log n) update cost   β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

3. EXACT VS APPROXIMATE
   Traditional DB: 100% accuracy, slower
   Vector DB: 95% accuracy, 1000x faster
   Trade-off depends on use case!

4. DIFFERENT PROBLEMS NEED DIFFERENT ALGORITHMS
   β”œβ”€ Exact match β†’ B-tree
   β”œβ”€ Range query β†’ B-tree
   β”œβ”€ Text search β†’ Inverted index
   β”œβ”€ Similarity β†’ Vector search (HNSW)
   └─ Graph traversal β†’ Graph database

5. 2026 = HYBRID SYSTEMS
   Modern apps use ALL of these:
   β”œβ”€ PostgreSQL (B-tree) for transactions
   β”œβ”€ Elasticsearch (inverted index) for search
   β”œβ”€ Pinecone (HNSW) for AI
   └─ Neo4j (graph) for relationships
Enter fullscreen mode Exit fullscreen mode

πŸ—ΊοΈ Your Database Algorithm Journey

Where you are now:
βœ“ Understand time/space trade-offs (Part 1)
βœ“ Can design algorithms (Part 2)
βœ“ Model graphs (Part 3)
βœ“ Build production systems (Part 4)
βœ“ Understand database internals (Part 5) ← YOU ARE HERE

Your new superpowers:
β”œβ”€ Know why databases are fast
β”œβ”€ Understand index trade-offs
β”œβ”€ Can choose right database for problem
β”œβ”€ Recognize recursion stack costs
└─ Ready for AI-era databases

Next steps:
β–‘ Part 6: Caching & CDN algorithms
β–‘ Part 7: Real-time streaming
β–‘ Part 8: AI/ML algorithms (deep dive into vector search!)
β–‘ Part 9: Cryptography & security
β–‘ Part 10: Autonomous systems
Enter fullscreen mode Exit fullscreen mode

πŸ’¬ Your Turn

Build these yourself:

  1. Implement a full B-tree with insert/delete/split operations
  2. Build a mini vector database with cosine similarity
  3. Compare performance: 1K, 10K, 100K, 1M records
  4. Measure stack depth in your recursive functions

What databases do you use? Can you explain their index algorithms now?

Share your findings! How deep is YOUR recursion stack? πŸ“Š


Databases are fast because of algorithms. Understanding these algorithms gives you the power to choose the right tool and optimize the right way. πŸ—„οΈβœ¨


🎯 Coming Up Next: Part 6

Caching Strategies & CDN Algorithms

From database lookups to cached responses:
β”œβ”€ LRU vs LFU vs ARC eviction policies
β”œβ”€ How Cloudflare routes 20M+ req/sec
β”œβ”€ Consistent hashing for distributed caches
β”œβ”€ Cache invalidation (the hard problem!)

Same principles, different layer of the stack!
Enter fullscreen mode Exit fullscreen mode

Stay tuned! πŸš€

Top comments (0)