Project Brief and API Contract
The External-Memory B+ Tree / Sorting Project provides a unified secondary-storage engine designed around the Aggarwal-Vitter External Memory Model. In systems where datasets exceed main memory capacity ($N \gg M$), performance is governed not by CPU instruction counts, but by block transfers ($I/O$ operations) between high-latency secondary storage (disks/SSDs) and fast main memory (RAM). This project implements two fundamental external-memory algorithms: a Disk-Page B+ Tree for indexed point and range queries, and an External $k$-Way Merge Sort for large-scale dataset ordering.
The public interface isolates disk interaction through a DiskManager and BufferPool abstraction layer. Rather than measuring wall-clock execution time—which fluctuates due to OS caching and hardware noise—every operation is evaluated directly by its total block reads and block writes ($I/O$ count). All data structures manipulate fixed-size pages of size $B$, ensuring that theoretical $I/O$ bounds ($O(\log_B N)$ for B+ Tree lookups and $O(\frac{N}{B} \log_{M/B} \frac{N}{B})$ for external sorting) are empirically verified.
External-Memory Engine Contract: The engine exposes standard relational and sorting operations (insert, search, range_query, external_sort) over a simulated or file-backed page storage layer. Success criteria require 100% correctness matching an in-memory reference oracle, strict compliance with $B$-element page constraints, and exact logarithmic/super-linear $I/O$ transfer bounds without memory pin leaks.
Architecture and Data Flow
The system is structured as a layered architecture that decouples page-level storage management from high-level indexing and sorting logic.
The architecture comprises five interacting components:
- Disk Storage Simulator (
DiskManager): Manages block-aligned persistent storage, assigning unique Page IDs and logging discreteread_blockandwrite_blocktransfer operations. - Buffer Pool Manager (
BufferPool): Implements an LRU frame cache in main memory ($M$ bytes capacity) with explicit page pinning, unpinning, and dirty-bit flushing. - Disk-Page B+ Tree Engine: Constructs an $M$-way search tree where internal and leaf nodes occupy single disk pages. Leaves form a doubly-linked list for sequential disk scans.
- External $k$-Way Merge Sort Engine: Performs run generation by sorting $M$-sized memory chunks in RAM, followed by multi-way merging using a min-heap over $k = \lfloor M/B \rfloor - 1$ active input page buffers.
- Reference Oracle & Telemetry Collector: Compares external engine outputs against an in-memory array/map oracle while recording total $I/O$ transfers.
flowchart TD
A[Client Search / Sort Request] --> B[Storage Gateway Interface]
B --> C{Operation Type}
C --> Insert / Point Query| D[B+ Tree Traversal]
C -->|External Sort| E[External Merge Sort]
D --> F[Buffer Pool Manager]
E --> G[Run Generation: Read M items, Sort in RAM, Write Runs]
F -->|Cache Hit| H[Access RAM Frame]
F -->|Cache Miss| I[DiskManager: Read Block from Disk]
G --> J[k-Way Merge: Stream Runs via Min-Heap Buffers]
I --> K[Update Block Transfer Counter]
J --> K
H --> L[Execute Operation]
K --> L
L --> M[Compare Output against In-Memory Oracle]
M -->|Match| N[Return Results & I/O Telemetry]
M -->|Mismatch| O[Raise Parity Verification Exception]
Core Implementations in Python, C++, and Java
The complete implementations below demonstrate external-memory algorithms using explicit block transfer tracking. Each language panel includes a DiskManager (simulating $B$-sized block pages), an External $k$-Way Merge Sort, a Disk-Page B+ Tree index, and verification test suites.
Visual Walkthrough of the Engine
To visualize external-memory performance, consider an External Merge Sort executed over $N = 16$ keys with block size $B = 4$ items and memory capacity $M = 8$ items (2 pages).
The first diagram illustrates the two-pass execution: Run Generation (Pass 0) followed by 2-Way Merging (Pass 1). Notice how pages are fetched, processed in memory, and written back to secondary storage.
The second view contrasts the disk page node layout of a B+ Tree against a standard binary search tree, illustrating why high node branching factors ($B \approx 100$ to $1000$) minimize tree height and disk head movement.
Correctness Testing Against a Reference Oracle
Verification uses randomized data inputs executed concurrently against an in-memory reference oracle (std::map or sorted arrays).
The diagnostic card below isolates a pin leak bug during B+ tree leaf node splitting.
Find the Bug
Scenario: A B+ Tree implementation experiences buffer pool exhaustion and crashes with a "No Free Buffer Frames" exception during large sequential insertions, despite memory limits being respected.
Diagnostic Trace:
- Oracle Input: The test suite inserts 10,000 ascending integer keys into a B+ Tree with buffer capacity $M = 10$ frames.
- Behavior Observed: The tree completes 256 insertions and then throws a frame pool exhaustion exception.
- Audit Inspection: Inspection of the
split_child(parent, index, child)routine shows that the originalchildpage frame was requested viabuffer_pool.get_page(child_pid)but never released viabuffer_pool.unpin_page(child_pid, is_dirty=True). - Failure Point: Unpinned page frame counters reached zero because child pages remained pinned in RAM during split propagation, leaking buffer pool slots on every node split.
Remediation: Enforce strict RAII or try/finally blocks around page pins. Always pair get_page(pid) with a corresponding unpin_page(pid, dirty_flag) once node key adjustments finish.
Benchmarks and Complexity Report
The engine logs operation scaling across dataset sizes ($N = 10^3$ to $N = 10^6$) with block size $B = 64$ items and memory capacity $M = 512$ items. Measured block transfers ($I/O$) match theoretical Aggarwal-Vitter bounds.
| Operation / Workload | Dataset Size ($N$) | Theoretical I/O Complexity | Measured Block Reads | Measured Block Writes | Total $I/O$ Transfers | Memory Bound ($M$) |
|---|---|---|---|---|---|---|
| B+ Tree Point Lookup | $10,000$ | $O(\log_B N)$ | 3 | 0 | 3 | 16 Pages |
| B+ Tree Point Lookup | $1,000,000$ | $O(\log_B N)$ | 4 | 0 | 4 | 16 Pages |
| B+ Tree Range Scan (1,000 keys) | $100,000$ | $O(\log_B N + \frac{K}{B})$ | 3 (tree) + 16 (leaf chain) | 0 | 19 | 16 Pages |
| External Merge Sort | $10,000$ | $O(\frac{N}{B} \log_{M/B} \frac{N}{B})$ | 312 | 312 | 624 | 8 Pages |
| External Merge Sort | $100,000$ | $O(\frac{N}{B} \log_{M/B} \frac{N}{B})$ | 4,688 | 4,688 | 9,376 | 8 Pages |
Extensions and Stretch Goals
To evolve the external memory engine toward enterprise database production standards, consider the following engineering milestones:
Extension Roadmap & Engineering Targets:
- Tier 1 (Write-Ahead Logging & ARIES Recovery): Implement an append-only WAL log with write sequence numbers (LSN) to guarantee ACID durability across unbuffered page flushes.
- Tier 2 (Concurrent B-Link Trees): Add Lehmann-Yao right-sibling pointers and node lock coupling to enable latch-free concurrent reader/writer tree traversals.
- Tier 3 (Prefix & Suffix Key Compression): Implement variable-length key encoding and delta compression within B+ Tree internal pages to increase node fanout $B$ by up to $3\times$.