Database Systems Architecture and Implementation
Storage and Indexing
From Memory to Disk
A database doesn't just live in a single, neat location. It's constantly moving between fast, volatile main memory (RAM) and slower, persistent disk storage. The component that orchestrates this traffic is the Buffer Pool Manager. Think of it as a smart caching layer. Its job is to keep frequently accessed data pages from the disk in a memory area called the buffer pool, dramatically reducing the need for slow disk I/O operations.
When the database needs to read or write data, it first checks the buffer pool . If the required page is already there—a 'cache hit'—the operation is fast. If not—a 'cache miss'—the manager fetches the page from the disk, finds a free slot (or frame) in the pool, and loads it. But what happens when the pool is full? The manager must evict an existing page to make room. To do this, it uses a replacement policy, like the Least Recently Used (LRU) algorithm, which discards the page that hasn't been accessed for the longest time. This ensures the most relevant data stays in memory.
Anatomy of a Page
Data on disk isn't a chaotic mess of bytes. It's organized into fixed-size units called pages (or blocks). A page is the smallest unit of data transfer between the disk and the buffer pool. A typical page size is 4KB, 8KB, or 16KB. Inside each page, we need a way to manage the records, or tuples, it contains, especially since they can have variable lengths.
Most systems use a slotted page layout. This structure consists of a header, the raw data, and a slot directory that acts as an index within the page.
The page header stores metadata, like the page ID and the number of records it holds. The slot directory contains entries for each tuple, storing a pointer (offset) to where the tuple begins within the page and its length. The actual tuple data is packed at the end of the page, growing backward. This clever design allows tuples to be moved around inside the page—to fill gaps after a deletion, for instance—without invalidating external references. Only the small pointer in the slot directory needs to be updated.
Indexing for Speed
Storing records in pages is just the first step. Without an index, finding a specific record would require a full table scan, reading every single page from disk. This is incredibly inefficient for large tables. Indexes are separate data structures that provide a fast lookup path to the data, much like the index in a book.
The most common and important indexing structure for databases is the . It's a self-balancing tree structure that keeps data sorted and allows for searches, sequential access, insertions, and deletions in logarithmic time. B+ Trees are optimized for systems that read and write large blocks of data, making them a perfect fit for disk-based databases.
Each node in a B+ Tree corresponds to one disk page. The branching factor of the tree—the number of children per node—is determined by how many keys can fit into a single page. This creates a wide, shallow tree, which is crucial for minimizing disk I/O. A lookup might only require traversing 3 or 4 levels (and thus 3-4 disk reads) to find any record, even in a table with billions of rows.
Storage Strategies
How you arrange the data itself has a huge impact on performance. This leads to two fundamental indexing and storage approaches: clustered vs. non-clustered indexes and row vs. columnar storage.
A clustered index determines the physical order of data in a table. Because the data can only be sorted in one way, a table can have only one clustered index.
In systems like SQL Server or MySQL's InnoDB engine, the primary key is typically the clustered index. The leaf nodes of the B+ Tree contain the actual data rows. This is great for range queries on the clustered key, as all the data you need is physically co-located. However, it can make insertions more expensive, as new rows might require shifting existing data pages to maintain order. A , on the other hand, is a separate structure where the leaf nodes contain pointers back to the data rows. You can have many non-clustered indexes on a single table.
| Feature | Clustered Index | Non-Clustered Index |
|---|---|---|
| Number Per Table | One | Many |
| Data Storage | Sorts and stores the actual data rows | A separate structure with pointers to rows |
| Primary Use Case | Range queries on the index key | Point lookups on non-primary keys |
| Lookup Speed | Faster, data is in the leaf node | Slower, requires an extra lookup step |
| Insert Overhead | Higher, may require page splits | Lower, only the index is updated |
Finally, we have the choice between row-based and column-based storage. Traditional databases like PostgreSQL and MySQL are row-stores. They store all the attributes of a single record together on a page. This is efficient for transactional workloads where you often need to read or write an entire row at once (e.g., fetching all information for a specific user).
Analytical databases like Amazon Redshift or ClickHouse often use . Here, all values for a single column are stored together on disk. If you have a query that only needs to analyze two columns from a table with 100 columns, a columnar database only reads the data for those two columns. This drastically reduces I/O and is highly effective for aggregations and analytics over large datasets, as the similar data types compress very well together.
Understanding these physical storage concepts is the key to unlocking true database performance. Choosing the right indexing strategy and storage layout for your workload can be the difference between a query that takes minutes and one that returns in milliseconds.
Time to test your knowledge on how databases manage and access data.
What is the primary function of the Buffer Pool Manager in a database system?
When the Buffer Pool is full and a new page needs to be fetched from disk, an existing page must be evicted. Which policy is commonly used to decide which page to remove?