skip to content

Engine Architecture & Storage Internals

How a relational engine is actually built: connections come in, pages and the buffer pool sit in the middle, and write-ahead logging plus recovery guarantee durability underneath. Interviewers probe these internals to see whether you understand what the database does with your SQL beyond the syntax — why commits are durable, where memory goes, and what happens after a crash.

part ofRelational database conceptsoverview, primer and where to startread it →
on this pageshow

questions

page 1 of 2

What is a database engine's buffer pool, and why does the engine maintain its own in-memory page cache instead of simply reading and writing files through the operating system?

level: juniorimportance: must knowfreq 70%

answer

  1. frames = page-sized slots in shared memory
  2. hit vs miss, page table lookup
  3. pin = don't evict me, not a lock
  4. dirty = changed in RAM, log flushed first
  5. OS cache doesn't know index roots from scans

basics

~20 s

A shared region of RAM holding fixed-size disk pages. All reads and writes go through it, so hot pages never touch disk. The engine caches itself because it knows access patterns and must control when a modified page may be written.

solid answer

~50 s

The buffer pool is a fixed-size region of shared memory divided into **frames**, each holding one disk page (commonly 4-16 KB). To read a row the engine locates its page: if the page is already in a frame that is a **hit**; otherwise it picks a victim frame, evicts it (writing it out first if it was modified), reads the page in, and that is a **miss**. Whoever uses a frame **pins** it so eviction cannot steal it, and unpins when done. Updates happen in memory and mark the frame **dirty**; the page is written back later by a background writer or at a checkpoint, and never before its log records are durable. The engine keeps its own cache because it knows things the OS does not: which pages are index roots versus one-off scan pages, which pages a transaction still has pinned, and the ordering constraint between log writes and data writes.

go deeper

for a junior

Be able to say: shared RAM cache of disk pages, reads and writes go through it, hit vs miss, dirty means changed in memory.

for a middle

Add the mechanics: page table lookup, pin/unpin, victim selection, write-ahead ordering, and why hits are orders of magnitude cheaper than misses.

for a senior

Frame it operationally - working set versus pool size, what misses cost on your storage, and why the engine keeps its own cache instead of trusting kernel writeback.

for a principal

Discuss it as a memory-budget and I/O-control decision: who owns writeback timing, direct I/O versus double caching, and the blast radius of getting the pool size wrong on a shared machine.

## What a buffer pool is A relational engine never fetches a single row from disk. Storage is organized into fixed-size **pages** (blocks) - typically 4 KB, 8 KB, or 16 KB - and the unit of I/O is an entire page. The **buffer pool** (buffer cache) is a large region of shared memory carved into **frames**, each frame exactly one page wide. It is *shared*: every session of the instance uses the same pool, so a page faulted in by one session is immediately cheap for all the others. Beside the frames the engine keeps bookkeeping structures: a hash table mapping page identity (file/tablespace + block number) to the frame that holds it, and per-frame metadata - a **pin count**, a **dirty flag**, a usage/reference counter used by the eviction policy, and a short-duration lock (latch) protecting the frame's bytes while they are read or modified. ## The path of one page access 1. The executor needs block 4711 of a table. It hashes the page identity and looks in the page table. 2. **Hit**: the frame is found. The engine pins it, latches it, copies out the tuple, unlatches, unpins. No I/O at all. 3. **Miss**: no frame holds it. The engine must free a frame - it runs the eviction policy to choose a **victim**. If the victim is dirty it must be written to disk first (and its log records must already be durable). The engine then issues a read of block 4711 into that frame, registers it in the page table, and proceeds as in the hit case. The cost difference is enormous: a hit is tens to hundreds of nanoseconds; a miss is tens of microseconds on NVMe and milliseconds on spinning disk. This is why the buffer pool is usually the single most important memory setting on a database server. ## Pinning A **pin** is a reference count saying "someone is using this page, do not evict it". It is not a lock: many readers can hold pins at once, and a pin says nothing about transactional visibility. Pins are held for the duration of an operation on a page, not for the duration of a transaction - a scan pins a page, reads the tuples it needs, and unpins before moving on. A frame with pin count greater than zero is simply not a candidate for eviction. Leaked pins are a real class of engine bug: they shrink the effective pool. ## Dirty pages and write ordering When a statement changes a row it does *not* write to disk. It writes a log record describing the change, modifies the page in its frame, and sets the frame's dirty flag. The page now differs from the on-disk copy. Two rules govern when that page may go back to disk: - **Write-ahead logging**: the log records covering a page's changes must be flushed to durable storage *before* the page itself is written. Otherwise a crash could leave a changed page on disk with no log record to explain or undo it. - **Nobody waits for the page write.** Commit durability is provided by flushing the log, not the page. The dirty page can drift in memory for minutes, absorbing many further updates, and is written out later by a background writer, by eviction pressure, or by a checkpoint. This is why an in-memory row can be updated a thousand times and produce one page write. ## Why not just use the OS page cache The operating system also caches file blocks, so it is fair to ask why the engine duplicates the effort. The engine has information the kernel cannot have: - **Semantics of the access.** It knows a page is a B-tree root touched by every query, or conversely that a page belongs to a one-shot sequential scan that should not displace hot data. - **Write ordering.** It must guarantee log-before-page. Through the OS cache, writeback timing is the kernel's decision; the engine would have to fsync constantly to regain control. - **Pinning.** The engine must be able to say "this page must stay resident and stable while I read a tuple from it". The kernel offers no such contract. - **Coordination with recovery, MVCC, and checkpoints**, all of which are expressed in terms of pages the engine owns. Most engines therefore run a real cache of their own and treat the OS cache as a secondary, dumber layer (some engines bypass it entirely with direct I/O to avoid caching the same page twice). ## What good looks like The headline metric is the **hit ratio**: reads served from the pool divided by total page reads. A healthy OLTP system usually sits very high (99%+) because the working set fits. But the ratio alone is a weak signal - it says nothing about *which* pages miss or how expensive each miss is - so it is read alongside physical read counts, I/O wait, and eviction/write-back activity.

  • If a committed transaction's changed pages are still only in memory, how is the commit durable?
    Durability comes from the write-ahead log, not from the data pages. At commit the engine flushes the log records to stable storage; the dirty pages may stay in the buffer pool much longer. After a crash, recovery replays the log against the on-disk pages to reconstruct the committed state, so the page write can safely be deferred and batched.
  • What is the difference between pinning a page and locking a row on that page?
    A pin is a short-lived, non-transactional reference count that only keeps the frame resident; many sessions can pin the same frame simultaneously and it is released as soon as the page access finishes. A row lock is a transactional construct held until commit or rollback and it controls concurrent modification, not residency. Confusing the two suggests no real model of the storage layer.

A workshop bench with a fixed number of slots. You fetch a heavy crate (page) from the warehouse, work on it at the bench, and mark it 'do not put away' (pin) while your hands are on it. Changes are chalked on a running notebook (the log) before the crate itself is carried back.

saying these in an interview costs you the question

  • Saying the buffer pool caches rows or query results rather than fixed-size pages
  • Claiming a commit must write the modified data pages to disk
  • Treating a pin as a lock that provides isolation
  • Assuming the OS page cache makes an engine-level cache redundant
  • Believing a dirty page is written out immediately after the statement that changed it

context

open as a page

What is a checkpoint in a database storage engine, and what two problems would a system have if it never took one?

level: juniorimportance: must knowfreq 55%

basics

~20 s

A checkpoint flushes modified in-memory pages to disk and records a position in the transaction log. Without it, crash recovery would have to replay the log from the beginning, and the log could never be truncated, so it would grow forever.

open as a page

Walk through what a relational database server does from the moment a client opens a TCP connection until that client can run its first SQL statement.

level: juniorimportance: must knowfreq 58%

basics

~20 s

TCP connect, a protocol handshake naming the database and user, an optional TLS upgrade, then authentication. The server allocates a session (a backend process or thread with its own memory), loads role metadata, and signals it is ready for queries.

open as a page

Why do applications keep a pool of open database connections instead of opening a new connection for each request, and what does a pool actually do when code asks for one?

level: juniorimportance: must knowfreq 74%

basics

~20 s

Opening a connection costs a network handshake, authentication, and server-side setup - milliseconds plus memory - and the server can only hold a limited number. A pool keeps a bounded set of authenticated connections open, lends one to each unit of work, takes it back afterwards, and makes callers wait when all are busy.

open as a page

After an unexpected server crash, a relational database keeps the effects of some transactions and throws others away. How does it decide which is which, and what does that mean for an application that already received a successful commit acknowledgement?

level: juniorimportance: must knowfreq 52%

basics

~20 s

The transaction log decides. A transaction whose commit record reached durable storage before the crash is kept, and reapplied if needed. Every transaction without a durable commit record is rolled back completely. So an acknowledged commit survives; in-flight work disappears atomically, never half-applied.

open as a page

Relational storage engines read and write their data files in fixed-size pages, commonly 4 to 16 KB, rather than fetching individual rows. Why is the page the unit of I/O and caching, and what follows from that choice?

level: juniorimportance: must knowfreq 55%

basics

~20 s

Storage devices and the operating system move data in blocks, and per-request overhead dominates, so reading one row would cost about the same as reading a whole page. Fixed size also makes addressing trivial: page number times page size gives the byte offset. Consequences: caching, logging and locking all work in page units, and reading one row pulls in its neighbours.

open as a page

A relational engine can lay a table's data out on disk row by row or column by column. Describe both physical layouts and explain which workloads each favors, and why.

level: juniorimportance: must knowfreq 70%

basics

~20 s

A row store keeps all of a row's columns together, so reading or writing one whole row touches one place. A column store keeps each column's values contiguous, so scanning a few columns over many rows reads far less data. Rows suit point reads and writes; columns suit wide scans and aggregates.

open as a page

What is a relational database's system catalog (data dictionary), and why do engines store their own metadata as ordinary queryable tables?

level: juniorimportance: must knowfreq 55%

basics

~20 s

The catalog is the database describing itself: tables that list tables, columns, indexes, constraints, views, roles and statistics. Keeping metadata in ordinary tables lets the engine reuse its own storage, transactions and permissions, so you read metadata with a plain SELECT.

open as a page

In a database that uses multi-version concurrency control (MVCC), deleting a million rows usually frees no disk space immediately. Why does the engine keep the old row versions, and what eventually removes them?

level: juniorimportance: must knowfreq 60%

basics

~20 s

Under MVCC an UPDATE or DELETE does not overwrite data; it marks the old version dead so transactions with older snapshots can still read it. The space is only reclaimable once no running transaction can see that version, and a background cleanup process (vacuum or undo purge) reclaims it later.

open as a page

When a transaction commits, what must reach durable storage before the client is told it succeeded, and which process writes the modified data pages to the table files?

level: middleimportance: must knowfreq 45%

basics

~20 s

Only the transaction log records for that transaction must be durable at commit. The modified data pages stay dirty in the buffer pool and are written later by the background writer or the checkpointer, so commit costs one sequential log flush rather than scattered page writes.

open as a page

Compare the eviction policies a database page cache can use - plain LRU, clock-sweep (second-chance), and midpoint-insertion LRU. What problem does each later variant solve?

level: middleimportance: must knowfreq 55%

basics

~20 s

Plain LRU is accurate but needs a global list updated on every hit, which contends. Clock-sweep approximates it with a usage counter and a rotating pointer, so hits are lock-free. Midpoint insertion admits new pages into the middle of the list so a scan cannot flush the hot end.

open as a page

How do you decide the maximum size of an application's database connection pool, and why is a bigger pool often slower than a smaller one?

level: middleimportance: must knowfreq 62%

basics

~20 s

Size it to the database's real concurrency, not to the request rate. Useful parallelism is bounded by CPU cores and storage, so a small pool (often around twice the cores plus an allowance for I/O waits) maximises throughput; larger pools add contention and context switching, so queries slow down while queueing simply moves into the database.

open as a page

During crash recovery, the engine may encounter a log record describing a page change that had already been written to the data file before the crash. How does it avoid applying that change a second time, and why would double application be harmful?

level: middleimportance: must knowfreq 45%

basics

~20 s

Every data page stores the log sequence number of the last change applied to it. Before replaying a record, recovery compares the record's LSN with the page's stored LSN; if the page is already at or beyond it, the change is skipped. That makes replay idempotent, which matters because many logged changes are not repeatable operations.

open as a page

Describe the phases an ARIES-style recovery process runs through when a relational database restarts after a crash, and what each phase accomplishes.

level: middleimportance: must knowfreq 58%

basics

~20 s

Three passes. Analysis reads forward from the last checkpoint to rebuild the list of in-flight transactions and dirty pages and to find where redo must start. Redo replays every logged change, committed or not, until the data files match the log. Undo then rolls back the transactions that never committed.

open as a page

Explain how a slotted page stores variable-length rows: what sits in the page header, where do the rows live, and why is there a slot or line-pointer array in between?

level: middleimportance: must knowfreq 55%

basics

~20 s

A page has a header at the front, an array of slots growing forward, and row data filling from the back. Free space is the gap in the middle. Each slot holds the offset and length of one row. Rows are addressed by slot number, not byte offset, so the engine can move rows within the page to compact free space without invalidating any external reference.

open as a page

Database engines divide RAM between memory shared by the whole instance and memory granted per operation for things like sorts and hash tables. Explain the difference between the two, and why raising the per-operation budget is riskier than raising the shared one.

level: middleimportance: must knowfreq 55%

basics

~20 s

Shared memory - mainly the buffer cache - is allocated once for the whole instance and used by everyone. Per-operation working memory is granted separately to each sort, hash join or hash aggregate in each concurrent query, so the same setting can be multiplied by operators per query times concurrent queries, and a value that looks safe alone can exhaust RAM under load.

open as a page

How does an MVCC engine decide that a particular old row version is safe to reclaim? Explain the role of the oldest still-running transaction.

level: middleimportance: must knowfreq 52%

basics

~20 s

The engine computes a horizon: the oldest snapshot any active transaction could use. A version is reclaimable only if it was expired by a committed transaction older than that horizon, so nobody could still see it. One old transaction holds the horizon back and blocks cleanup for the whole database.

open as a page

Relational engines write a description of every change into a sequential log before the modified data page itself reaches disk. What is that write-ahead logging protocol, and what problem does it solve?

level: middleimportance: must knowfreq 72%

basics

~20 s

The log record describing a change must reach stable storage before the changed data page does, and before a commit is acknowledged. One cheap sequential flush makes a crash recoverable: replay the log to rebuild lost page writes.

open as a page

Database logs are often described as containing redo information and undo information. What is the difference between the two, and what is each one needed for?

level: middleimportance: must knowfreq 56%

basics

~20 s

Redo information lets the engine re-apply a committed change whose data page never reached disk. Undo information lets it reverse a change made by a transaction that aborted or was still open at crash time. Redo moves forward, undo backward.

open as a page

A relational database server enforces a maximum number of concurrent client connections. Explain what each connection actually costs the server, what happens when the cap is reached, and how you would reason about setting it.

level: seniorimportance: must knowfreq 55%

basics

~20 s

Each connection costs a server-side process or thread, several megabytes of private memory, plus per-query workspace that multiplies under load. At the cap, new connections are rejected outright. Set it just above what your pools can actually use, since real throughput is bounded by cores and disk, not by connection count.

open as a page

Requests in a service start failing with timeouts while waiting to obtain a database connection, but the database server itself shows low CPU and I/O. How would you diagnose and fix this?

level: seniorimportance: must knowfreq 50%

basics

~20 s

Connections are being held rather than used. Look for leaked connections never returned, transactions left open across slow non-database work, long or unindexed queries, and lock waits. Fix the holder; enlarging the pool only multiplies held connections and pushes contention into the database.

open as a page

A reporting query has been running for six hours, and another session has sat idle inside an open transaction all morning. What does that do to version cleanup, undo or rollback-segment growth, and table size, and how would you diagnose and prevent it?

level: seniorimportance: must knowfreq 48%

basics

~20 s

Both pin the cleanup horizon, so no version expired since they started can be reclaimed anywhere. Undo segments grow, tables and indexes bloat, version chains lengthen and reads slow down. Diagnose by finding the oldest transaction and its state; prevent with idle-in-transaction and statement timeouts, chunked batches, and monitoring oldest-transaction age.

open as a page

Besides the processes serving client connections, a relational database server runs several dedicated background processes. Name the main ones and say what each is responsible for.

level: juniorimportance: should knowfreq 34%

basics

~20 s

Typically: a log/WAL writer that flushes the transaction log, a background writer that trickles dirty data pages out of the buffer pool, a checkpointer that periodically writes all dirty pages to bound recovery time, statistics collection and auto-analyze, and a vacuum/purge daemon reclaiming dead row versions.

open as a page

A database engine has both a background writer and a checkpointer, and both write dirty pages out of the buffer pool. Why does it need both, and what different goal does each one serve?

level: middleimportance: should knowfreq 32%

basics

~20 s

The background writer keeps clean, reusable buffers available so a query needing a free buffer does not have to flush one itself — it targets buffer availability. The checkpointer flushes everything dirty as of a point in time to bound crash-recovery work and let old log be recycled.

open as a page

Explain the difference between a 'sharp' (consistent) checkpoint and a 'fuzzy' checkpoint in a storage engine, and why production databases use the fuzzy variety.

level: middleimportance: should knowfreq 40%

basics

~20 s

A sharp checkpoint quiesces the system so the flushed image is transaction-consistent. A fuzzy checkpoint lets writes continue while pages are flushed, so the image is inconsistent, and records enough state - such as the oldest dirty page's log position and the active-transaction list - for recovery to fix it up.

open as a page

What is a torn page, why can a crash while the engine is flushing an 8 KB or 16 KB page produce one, and how do storage engines protect against it - for example with full-page images in the log or a doublewrite area?

level: middleimportance: should knowfreq 42%

basics

~20 s

A page is larger than the sector the device writes atomically, so a crash mid-write can leave a page half old, half new - internally corrupt and unusable by redo. Engines fix it by first writing a whole clean copy of the page: a full-page image in the log, or a doublewrite area, from which recovery restores it.

open as a page

Relational database engines serve concurrent client sessions with a process per connection, a thread per connection, or a pool of worker threads. Compare these architectures and their practical consequences.

level: middleimportance: should knowfreq 45%

basics

~20 s

Process per connection gives strong isolation but the highest per-session cost, so connection counts must stay low. Thread per connection is cheaper but shares one address space, so a crash or leak is riskier. Worker pools multiplex many sessions onto few threads, scaling to many connections at the cost of complexity and restrictions on session-bound work.

open as a page

What state does a relational database server keep that is scoped to a single client session, and why does that state matter when connections are shared or reused?

level: middleimportance: should knowfreq 42%

basics

~20 s

A session holds its transaction, session parameters (timezone, isolation level, search path, autocommit), temporary tables, prepared statements and cursors, session-level locks, sequence state, and the authenticated identity. If a connection is reused without resetting that state, the next user of the connection inherits it and behaves unpredictably.

open as a page

A table has a column that sometimes holds a multi-megabyte value, far larger than a single 8 KB page. How do storage engines physically store such values, and what does that mean for queries that do not select that column?

level: middleimportance: should knowfreq 40%

basics

~20 s

The value is moved out of the row into overflow storage: chopped into chunks held in a separate structure, often compressed first, with only a short pointer left inline. The main row therefore stays small, so scans and queries that do not select the column never read the large data at all; touching it costs extra I/O.

open as a page

Every stored row carries a per-row header, and the engine addresses rows internally by a physical identifier such as page number plus slot number. What lives in that row header, and why should application code never treat the physical row identifier as a stable key?

level: middleimportance: should knowfreq 42%

basics

~20 s

The row header holds bookkeeping the engine needs: version or visibility information, a NULL bitmap with one bit per column, flags such as whether a value is stored off-page, and often a column count. The physical identifier is just the row's current address; updates, page compaction and table reorganisation move rows, so it changes and can even be reused later by a different row.

open as a page

showing 1–30 of 54