skip to content

How would you decide how much memory to give a database's buffer pool on a dedicated 128 GB server, and what is 'double buffering' between the engine's page cache and the operating system's page cache?

level: principalimportance: should knowfreq 40%

answer

  1. size from working set, not a percentage
  2. index upper levels are always worth caching
  3. connections x per-session memory, operators x workspace
  4. swap = a 'hit' becomes two disk I/Os
  5. double buffering: same page in kernel cache and pool

basics

~20 s

Size it from the working set, not a fixed percentage, and leave room for per-connection memory, sort/hash workspace and the OS. Double buffering is the same page being cached twice - once by the engine, once by the kernel - wasting RAM; direct I/O or a much larger pool avoids it.

solid answer

~60 s

Start from the **working set**, not a rule of thumb: how much data is actually touched in a normal hour - hot tables plus the index levels above their leaves. If that fits comfortably in the pool, more memory buys nothing; if it is far larger than RAM, you are sizing for damage limitation, not for hits. Then subtract what else needs memory: per-connection stacks and buffers times peak connections, per-operation sort/hash workspace (which multiplies across concurrent operators), maintenance work, and headroom for the OS so nothing swaps. Swapping a buffer pool is catastrophic - it turns a memory hit into two disk I/Os. **Double buffering** is the consequence of the engine reading through the kernel: a page lands in the OS page cache and is then copied into a buffer-pool frame, so RAM holds two copies. Engines that rely on OS caching therefore deliberately keep a modest pool; engines that use direct I/O bypass the kernel cache and take nearly all of RAM. Choose one philosophy - a pool sized at half of RAM through buffered I/O is the worst of both.

go deeper

for a junior

Know that the buffer pool gets a large share of a dedicated server's RAM but not all of it, because connections, sorts and the OS also need memory.

for a middle

Explain the competing consumers and what double buffering is, and why swapping is disastrous.

for a senior

Derive the size from measured working set and marginal physical-read reduction, and validate the change with before/after metrics.

for a principal

Frame it as a memory budget under a chosen I/O philosophy: buffered-with-modest-pool versus direct-I/O-with-large-pool, worst-case concurrency accounting, restart warm-up cost, and the point where memory stops being the lever.

## Why percentage rules of thumb are a starting point, not an answer 'Give it 75% of RAM' and 'give it 25% of RAM' are both real, defensible advice - for *different engines*, because they differ in whether the engine expects the kernel page cache to be a meaningful second tier. Quoting a percentage without knowing which philosophy the engine follows is the shallow answer. The reasoned version has three inputs: the working set, the competing memory consumers, and the I/O philosophy. ## Input 1: the working set The working set is the set of pages actually touched over a representative interval - not the database size. In a typical OLTP system it is a small fraction of total data: recent orders, active accounts, all the upper levels of the hot B-trees, small reference tables. Estimating it: - Sample which objects generate logical and physical reads over a busy hour. - Add index internal levels: even for a huge table, everything above the leaves is small and is touched by every lookup, so those pages are always worth caching. - Watch the marginal return: as the pool grows, physical reads fall steeply, then flatten. The knee of that curve is the useful size. Past it you are buying RAM for pages nobody reads twice. Three regimes follow. If the whole database fits, cache it all and the design question becomes write throughput instead. If the working set fits but the database does not - the common case - size to the working set with headroom for growth. If the working set is far larger than any affordable RAM, extra memory yields little; the leverage moves to indexing, partitioning, workload separation, and faster storage. ## Input 2: everything else that needs memory The buffer pool is not the only consumer, and the failure mode of over-allocating is severe. - **Per-connection memory.** Each session has stacks, buffers and caches. At 500 connections this is not a rounding error - and it is one of the strongest arguments for a connection pooler, which cuts both this and context-switch overhead. - **Per-operation workspace.** Sorts, hash joins and aggregates each take a workspace allocation, and a single query can open several such operators; multiply by concurrency for the realistic worst case. Under-provision it and operations spill to disk; over-provision it and a burst of concurrency exhausts RAM. - **Maintenance operations**: index builds, vacuum/purge, backups. - **The OS itself**, plus filesystem metadata and any agents on the box. Leave real headroom. The catastrophic outcome is **swapping**: the kernel pages out part of the buffer pool, so what the engine believes is a memory hit becomes a page fault - one disk read to bring the frame back, potentially one write to evict something else. Performance does not degrade gracefully; it collapses. On a database server the correct posture is to size so that swap is never touched (and to configure the kernel accordingly, and to avoid being killed by an out-of-memory reaper). ## Input 3: double buffering and the I/O philosophy With ordinary buffered file I/O, reading a page goes: disk -> kernel page cache -> copy into the buffer-pool frame. The page now occupies memory **twice**. That is double buffering. Its costs are wasted RAM, an extra memory copy per read, and split control - two independent replacement policies making decisions about the same data, neither aware of the other's intent. Writes are worse: the engine's write goes into kernel dirty pages, and *when* those reach the device is the kernel's decision until an fsync forces it, so the engine must fsync to enforce log-before-page ordering, and kernel writeback bursts can appear as latency the engine did not schedule. Two coherent stances exist: 1. **Buffered I/O with a deliberately modest pool.** The engine keeps a pool sized for the hottest pages and treats the kernel cache as a large, cheap second tier - a miss in the pool is often satisfied from RAM anyway. Cost: some duplication, less control. Benefit: memory is shared elastically with the rest of the system, and a restart re-warms from the still-populated OS cache. 2. **Direct I/O with a large pool.** The engine bypasses the kernel cache entirely, takes the majority of RAM, and owns caching and writeback timing end to end. Cost: it must implement its own read-ahead and write scheduling, and a misconfiguration is unforgiving. Benefit: one copy, one policy, predictable ordering. The anti-pattern is the middle: buffered I/O with a pool at ~50% of RAM, so a large fraction of memory holds duplicate copies while neither layer has enough to hold the working set. ## Validating the choice Sizing is a hypothesis; verify it. Watch physical reads and the eviction rate before and after a change, confirm latency percentiles moved, and check that peak memory (pool + connections + workspaces) stays clear of RAM with the machine never swapping. Re-derive after data growth or a workload change - a pool that was ample at 300 GB of data may not be at 3 TB. And remember the ceiling: a bigger pool only ever removes *read* I/O. If the bottleneck is log flushes at commit, checkpoint write bursts, or lock contention, more cache changes nothing.

  • Why is swapping the buffer pool so much worse than simply having a smaller pool?
    A smaller pool makes the engine's miss accounting honest: it knows the page is absent and issues one read. Swapping makes a page the engine believes is resident actually absent, so an assumed memory access silently becomes a page fault, possibly plus a write to evict another page. The engine cannot see or schedule that I/O, so latency becomes erratic and the usual metrics stop explaining it.
  • An engine designed around buffered I/O restarts. Why might it reach normal performance faster than an engine using direct I/O with a huge pool?
    With buffered I/O the kernel page cache survives the process restart, so the engine's misses are often satisfied from RAM while the pool re-warms - warm-up is largely free. A direct-I/O engine with most of RAM in its own pool starts completely cold and must re-read its working set from storage, which on a large pool can take a long time and is why such systems sometimes persist or pre-warm the cache explicitly.

saying these in an interview costs you the question

  • Answering only with a percentage such as '75% of RAM' with no reference to workload or engine philosophy
  • Forgetting per-connection and per-operation memory, then being surprised by out-of-memory under concurrency
  • Assuming a bigger pool fixes write-bound or lock-bound problems
  • Treating the OS page cache as free extra capacity while also sizing the pool at most of RAM
  • Believing swap provides a safe cushion for over-allocation

context