The demo below simulates a buffer pool with 4 frames. A fixed sequence of page requests arrives one at a time. Watch how the pool fills up, how evictions happen, and how the hit rate (hits / total requests) changes with each policy.
<!-- Buffer pool simulator — pool of 4 frames, sequence of page requests -->
<div class="controls">
<label>Policy:
<select id="policy">
<option value="lru">LRU</option>
<option value="clock">Clock</option>
</select>
</label>
<label>Speed:
<select id="speed">
<option value="800">Slow</option>
<option value="350" selected>Medium</option>
<option value="120">Fast</option>
</select>
</label>
<button id="btn-run" type="button">Run</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div class="stats">
<span>Requests: <strong id="stat-req">0</strong></span>
<span>Hits: <strong id="stat-hit">0</strong></span>
<span>Misses: <strong id="stat-miss">0</strong></span>
<span>Hit rate: <strong id="stat-rate">—</strong></span>
</div>
<div class="pool-label">Buffer pool (4 frames)</div>
<div id="pool" class="pool"></div>
<div class="queue-label">Page request sequence</div>
<div id="queue" class="queue"></div>
<div id="status" class="status"></div>
/* Buffer pool demo styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.controls { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin-bottom: .6rem; }
label { display: flex; align-items: center; gap: .3rem; font-weight: 600; }
select { font: inherit; border: 1px solid #9ab; border-radius: 6px; padding: .2rem .4rem; background: #f4f7f9; }
button { font: 600 13px system-ui; padding: .35rem .85rem; border-radius: 7px; cursor: pointer;
border: 1px solid #1d3557; background: #1d3557; color: #fff; }
button.ghost { background: #fff; color: #1d3557; }
.stats { display: flex; flex-wrap: wrap; gap: .5rem 1.2rem; margin-bottom: .5rem; }
.stats span { font-size: 13px; }
.pool-label, .queue-label { font-weight: 700; font-size: 12px; text-transform: uppercase;
letter-spacing: .06em; color: #5a7088; margin: .4rem 0 .2rem; }
/* Pool frames — colored by last event */
.pool { display: flex; gap: 6px; margin-bottom: .3rem; }
.frame { width: 54px; height: 54px; border-radius: 10px; border: 2px solid #cdd9e3;
display: flex; flex-direction: column; align-items: center; justify-content: center;
font: 700 18px ui-monospace, monospace; position: relative; transition: background .2s; }
.frame .ref-bit { position: absolute; top: 3px; right: 5px; font-size: 10px; font-weight: 700;
color: #e07b00; font-family: ui-monospace, monospace; }
.frame .lru-rank { position: absolute; bottom: 2px; right: 5px; font-size: 9px; color: #888; }
.frame.empty { background: #f0f3f5; color: #bbb; border-style: dashed; }
.frame.loaded { background: #e8eef3; color: #1d3557; }
.frame.hit { background: #d0f0dc; border-color: #43b06c; color: #0a7d33; }
.frame.miss { background: #fde8ea; border-color: #e06070; color: #c92f3c; }
.frame.evict { background: #fff3cd; border-color: #d4a200; color: #8a6000; }
.frame.hand { border-color: #e07b00; }
/* Queue chips */
.queue { display: flex; flex-wrap: wrap; gap: 4px; min-height: 30px; margin-bottom: .4rem; }
.pg { width: 30px; height: 30px; border-radius: 6px; border: 1.5px solid #cdd9e3;
display: flex; align-items: center; justify-content: center;
font: 700 13px ui-monospace, monospace; color: #1d3557; background: #e8eef3; }
.pg.active { background: #1d3557; color: #fff; border-color: #1d3557; }
.pg.done-hit { background: #d0f0dc; border-color: #43b06c; color: #0a7d33; }
.pg.done-miss { background: #fde8ea; border-color: #e06070; color: #c92f3c; }
.pg.done-plain { background: #dce3e8; border-color: #adb5bd; color: #555; }
/* Status line */
.status { min-height: 1.5em; font-weight: 600; font-size: 13px; }
.status.hit-msg { color: #0a7d33; }
.status.miss-msg { color: #c92f3c; }
.status.done-msg { color: #1d3557; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Buffer pool management demo — LRU and Clock eviction policies
// Page request sequence (pages 1-5, pool size 4)
const PAGES = [1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5, 1, 3, 2, 4, 5, 1, 2, 3];
const POOL_SIZE = 4;
// Simulation state
let policy = 'lru';
let frames = [];
let lruOrder = [];
let clockHand = 0;
let refBits = [];
let reqIdx = 0;
let hits = 0;
let misses = 0;
let running = false;
let timer = null;
function resetState() {
frames = new Array(POOL_SIZE).fill(null);
lruOrder = [];
clockHand = 0;
refBits = new Array(POOL_SIZE).fill(0);
reqIdx = 0;
hits = 0;
misses = 0;
running = false;
clearTimeout(timer);
}
function renderPool(highlightSlot, mode) {
// Draw each frame with its current state highlight
const poolEl = document.getElementById('pool');
poolEl.innerHTML = '';
frames.forEach((pg, i) => {
const d = document.createElement('div');
let cls = pg === null ? 'empty' : 'loaded';
if (i === highlightSlot) cls = mode;
if (policy === 'clock' && i === clockHand && pg !== null) d.dataset.hand = '1';
d.className = 'frame ' + cls;
if (d.dataset.hand === '1') d.classList.add('hand');
d.textContent = pg !== null ? String(pg) : '·';
if (policy === 'clock' && pg !== null) {
const rb = document.createElement('span');
rb.className = 'ref-bit';
rb.textContent = refBits[i] ? '1' : '0';
d.appendChild(rb);
}
if (policy === 'lru' && pg !== null) {
const pos = lruOrder.indexOf(i);
const rnk = document.createElement('span');
rnk.className = 'lru-rank';
rnk.textContent = '#' + (pos + 1);
d.appendChild(rnk);
}
poolEl.appendChild(d);
});
}
function renderQueue(activeIdx, doneResults) {
// Draw each page chip with hit/miss coloring
const qEl = document.getElementById('queue');
qEl.innerHTML = '';
PAGES.forEach((pg, i) => {
const d = document.createElement('div');
let cls = 'pg';
if (i === activeIdx) cls += ' active';
else if (i < activeIdx) {
const res = doneResults[i];
cls += res === 'hit' ? ' done-hit' : res === 'miss' ? ' done-miss' : ' done-plain';
}
d.className = cls;
d.textContent = String(pg);
qEl.appendChild(d);
});
}
function updateStats() {
const total = hits + misses;
document.getElementById('stat-req').textContent = String(total);
document.getElementById('stat-hit').textContent = String(hits);
document.getElementById('stat-miss').textContent = String(misses);
document.getElementById('stat-rate').textContent = total ? (100 * hits / total).toFixed(1) + '%' : '—';
}
function lruEvict() {
// LRU: evict the frame at the tail of the LRU order list
const slot = lruOrder[lruOrder.length - 1];
lruOrder.pop();
return slot;
}
function clockEvict() {
// Clock: scan hand, clear ref bits, evict first frame with ref=0
while (true) {
if (refBits[clockHand] === 1) {
refBits[clockHand] = 0;
clockHand = (clockHand + 1) % POOL_SIZE;
} else {
const slot = clockHand;
clockHand = (clockHand + 1) % POOL_SIZE;
return slot;
}
}
}
const doneResults = [];
function step() {
if (reqIdx >= PAGES.length) {
const statusEl = document.getElementById('status');
statusEl.textContent = i18nFmt('Done — %h% hits, %m% misses, hit rate %r%%', { h: hits, m: misses, r: (100 * hits / PAGES.length).toFixed(1) });
statusEl.className = 'status done-msg';
running = false;
document.getElementById('btn-run').textContent = 'Run';
return;
}
const pg = PAGES[reqIdx];
const slot = frames.indexOf(pg);
const isHit = slot !== -1;
let targetSlot = slot;
if (isHit) {
hits++;
doneResults.push('hit');
if (policy === 'lru') {
const idx = lruOrder.indexOf(slot);
if (idx !== -1) lruOrder.splice(idx, 1);
lruOrder.unshift(slot);
} else {
refBits[slot] = 1;
}
renderPool(slot, 'hit');
const statusEl = document.getElementById('status');
statusEl.textContent = i18nFmt('Page %pg% — hit! Already in pool.', { pg });
statusEl.className = 'status hit-msg';
} else {
misses++;
doneResults.push('miss');
const emptySlot = frames.indexOf(null);
if (emptySlot !== -1) {
targetSlot = emptySlot;
} else {
targetSlot = policy === 'lru' ? lruEvict() : clockEvict();
}
frames[targetSlot] = pg;
if (policy === 'lru') {
const idx = lruOrder.indexOf(targetSlot);
if (idx !== -1) lruOrder.splice(idx, 1);
lruOrder.unshift(targetSlot);
} else {
refBits[targetSlot] = 1;
}
renderPool(targetSlot, emptySlot !== -1 ? 'miss' : 'evict');
const statusEl = document.getElementById('status');
statusEl.textContent = emptySlot !== -1
? i18nFmt('Page %pg% — miss. Loaded into empty frame.', { pg })
: i18nFmt('Page %pg% — miss. Evicted a frame to make room.', { pg });
statusEl.className = 'status miss-msg';
}
renderQueue(reqIdx, doneResults);
updateStats();
reqIdx++;
timer = setTimeout(step, parseInt(document.getElementById('speed').value, 10));
}
function doRun() {
if (running) {
// Toggle pause
running = false;
clearTimeout(timer);
document.getElementById('btn-run').textContent = 'Run';
return;
}
if (reqIdx >= PAGES.length) doReset();
policy = document.getElementById('policy').value;
running = true;
document.getElementById('btn-run').textContent = 'Pause';
step();
}
function doReset() {
resetState();
doneResults.length = 0;
renderPool(-1, '');
renderQueue(-1, []);
updateStats();
document.getElementById('status').textContent = '';
document.getElementById('status').className = 'status';
document.getElementById('btn-run').textContent = 'Run';
}
document.getElementById('btn-run').addEventListener('click', doRun);
document.getElementById('btn-reset').addEventListener('click', doReset);
// Initialize on load
doReset();
<!-- Buffer pool simulator — pool of 4 frames, sequence of page requests -->
<div class="controls">
<label>Policy:
<select id="policy">
<option value="lru">LRU</option>
<option value="clock">Clock</option>
</select>
</label>
<label>Speed:
<select id="speed">
<option value="800">Slow</option>
<option value="350" selected>Medium</option>
<option value="120">Fast</option>
</select>
</label>
<button id="btn-run" type="button">Run</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div class="stats">
<span>Requests: <strong id="stat-req">0</strong></span>
<span>Hits: <strong id="stat-hit">0</strong></span>
<span>Misses: <strong id="stat-miss">0</strong></span>
<span>Hit rate: <strong id="stat-rate">—</strong></span>
</div>
<div class="pool-label">Buffer pool (4 frames)</div>
<div id="pool" class="pool"></div>
<div class="queue-label">Page request sequence</div>
<div id="queue" class="queue"></div>
<div id="status" class="status"></div>
/* Buffer pool demo styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; font-size: 14px; }
.controls { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin-bottom: .6rem; }
label { display: flex; align-items: center; gap: .3rem; font-weight: 600; }
select { font: inherit; border: 1px solid #9ab; border-radius: 6px; padding: .2rem .4rem; background: #f4f7f9; }
button { font: 600 13px system-ui; padding: .35rem .85rem; border-radius: 7px; cursor: pointer;
border: 1px solid #1d3557; background: #1d3557; color: #fff; }
button.ghost { background: #fff; color: #1d3557; }
.stats { display: flex; flex-wrap: wrap; gap: .5rem 1.2rem; margin-bottom: .5rem; }
.stats span { font-size: 13px; }
.pool-label, .queue-label { font-weight: 700; font-size: 12px; text-transform: uppercase;
letter-spacing: .06em; color: #5a7088; margin: .4rem 0 .2rem; }
/* Pool frames — colored by last event */
.pool { display: flex; gap: 6px; margin-bottom: .3rem; }
.frame { width: 54px; height: 54px; border-radius: 10px; border: 2px solid #cdd9e3;
display: flex; flex-direction: column; align-items: center; justify-content: center;
font: 700 18px ui-monospace, monospace; position: relative; transition: background .2s; }
.frame .ref-bit { position: absolute; top: 3px; right: 5px; font-size: 10px; font-weight: 700;
color: #e07b00; font-family: ui-monospace, monospace; }
.frame .lru-rank { position: absolute; bottom: 2px; right: 5px; font-size: 9px; color: #888; }
.frame.empty { background: #f0f3f5; color: #bbb; border-style: dashed; }
.frame.loaded { background: #e8eef3; color: #1d3557; }
.frame.hit { background: #d0f0dc; border-color: #43b06c; color: #0a7d33; }
.frame.miss { background: #fde8ea; border-color: #e06070; color: #c92f3c; }
.frame.evict { background: #fff3cd; border-color: #d4a200; color: #8a6000; }
.frame.hand { border-color: #e07b00; }
/* Queue chips */
.queue { display: flex; flex-wrap: wrap; gap: 4px; min-height: 30px; margin-bottom: .4rem; }
.pg { width: 30px; height: 30px; border-radius: 6px; border: 1.5px solid #cdd9e3;
display: flex; align-items: center; justify-content: center;
font: 700 13px ui-monospace, monospace; color: #1d3557; background: #e8eef3; }
.pg.active { background: #1d3557; color: #fff; border-color: #1d3557; }
.pg.done-hit { background: #d0f0dc; border-color: #43b06c; color: #0a7d33; }
.pg.done-miss { background: #fde8ea; border-color: #e06070; color: #c92f3c; }
.pg.done-plain { background: #dce3e8; border-color: #adb5bd; color: #555; }
/* Status line */
.status { min-height: 1.5em; font-weight: 600; font-size: 13px; }
.status.hit-msg { color: #0a7d33; }
.status.miss-msg { color: #c92f3c; }
.status.done-msg { color: #1d3557; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Buffer pool management demo — LRU and Clock eviction policies
// Page request sequence (pages 1-5, pool size 4)
const PAGES = [1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5, 1, 3, 2, 4, 5, 1, 2, 3];
const POOL_SIZE = 4;
// Simulation state
let policy = 'lru';
let frames = [];
let lruOrder = [];
let clockHand = 0;
let refBits = [];
let reqIdx = 0;
let hits = 0;
let misses = 0;
let running = false;
let timer = null;
function resetState() {
frames = new Array(POOL_SIZE).fill(null);
lruOrder = [];
clockHand = 0;
refBits = new Array(POOL_SIZE).fill(0);
reqIdx = 0;
hits = 0;
misses = 0;
running = false;
clearTimeout(timer);
}
function renderPool(highlightSlot, mode) {
// Draw each frame with its current state highlight
const poolEl = document.getElementById('pool');
poolEl.innerHTML = '';
frames.forEach((pg, i) => {
const d = document.createElement('div');
let cls = pg === null ? 'empty' : 'loaded';
if (i === highlightSlot) cls = mode;
if (policy === 'clock' && i === clockHand && pg !== null) d.dataset.hand = '1';
d.className = 'frame ' + cls;
if (d.dataset.hand === '1') d.classList.add('hand');
d.textContent = pg !== null ? String(pg) : '·';
if (policy === 'clock' && pg !== null) {
const rb = document.createElement('span');
rb.className = 'ref-bit';
rb.textContent = refBits[i] ? '1' : '0';
d.appendChild(rb);
}
if (policy === 'lru' && pg !== null) {
const pos = lruOrder.indexOf(i);
const rnk = document.createElement('span');
rnk.className = 'lru-rank';
rnk.textContent = '#' + (pos + 1);
d.appendChild(rnk);
}
poolEl.appendChild(d);
});
}
function renderQueue(activeIdx, doneResults) {
// Draw each page chip with hit/miss coloring
const qEl = document.getElementById('queue');
qEl.innerHTML = '';
PAGES.forEach((pg, i) => {
const d = document.createElement('div');
let cls = 'pg';
if (i === activeIdx) cls += ' active';
else if (i < activeIdx) {
const res = doneResults[i];
cls += res === 'hit' ? ' done-hit' : res === 'miss' ? ' done-miss' : ' done-plain';
}
d.className = cls;
d.textContent = String(pg);
qEl.appendChild(d);
});
}
function updateStats() {
const total = hits + misses;
document.getElementById('stat-req').textContent = String(total);
document.getElementById('stat-hit').textContent = String(hits);
document.getElementById('stat-miss').textContent = String(misses);
document.getElementById('stat-rate').textContent = total ? (100 * hits / total).toFixed(1) + '%' : '—';
}
function lruEvict() {
// LRU: evict the frame at the tail of the LRU order list
const slot = lruOrder[lruOrder.length - 1];
lruOrder.pop();
return slot;
}
function clockEvict() {
// Clock: scan hand, clear ref bits, evict first frame with ref=0
while (true) {
if (refBits[clockHand] === 1) {
refBits[clockHand] = 0;
clockHand = (clockHand + 1) % POOL_SIZE;
} else {
const slot = clockHand;
clockHand = (clockHand + 1) % POOL_SIZE;
return slot;
}
}
}
const doneResults = [];
function step() {
if (reqIdx >= PAGES.length) {
const statusEl = document.getElementById('status');
statusEl.textContent = i18nFmt('Done — %h% hits, %m% misses, hit rate %r%%', { h: hits, m: misses, r: (100 * hits / PAGES.length).toFixed(1) });
statusEl.className = 'status done-msg';
running = false;
document.getElementById('btn-run').textContent = 'Run';
return;
}
const pg = PAGES[reqIdx];
const slot = frames.indexOf(pg);
const isHit = slot !== -1;
let targetSlot = slot;
if (isHit) {
hits++;
doneResults.push('hit');
if (policy === 'lru') {
const idx = lruOrder.indexOf(slot);
if (idx !== -1) lruOrder.splice(idx, 1);
lruOrder.unshift(slot);
} else {
refBits[slot] = 1;
}
renderPool(slot, 'hit');
const statusEl = document.getElementById('status');
statusEl.textContent = i18nFmt('Page %pg% — hit! Already in pool.', { pg });
statusEl.className = 'status hit-msg';
} else {
misses++;
doneResults.push('miss');
const emptySlot = frames.indexOf(null);
if (emptySlot !== -1) {
targetSlot = emptySlot;
} else {
targetSlot = policy === 'lru' ? lruEvict() : clockEvict();
}
frames[targetSlot] = pg;
if (policy === 'lru') {
const idx = lruOrder.indexOf(targetSlot);
if (idx !== -1) lruOrder.splice(idx, 1);
lruOrder.unshift(targetSlot);
} else {
refBits[targetSlot] = 1;
}
renderPool(targetSlot, emptySlot !== -1 ? 'miss' : 'evict');
const statusEl = document.getElementById('status');
statusEl.textContent = emptySlot !== -1
? i18nFmt('Page %pg% — miss. Loaded into empty frame.', { pg })
: i18nFmt('Page %pg% — miss. Evicted a frame to make room.', { pg });
statusEl.className = 'status miss-msg';
}
renderQueue(reqIdx, doneResults);
updateStats();
reqIdx++;
timer = setTimeout(step, parseInt(document.getElementById('speed').value, 10));
}
function doRun() {
if (running) {
// Toggle pause
running = false;
clearTimeout(timer);
document.getElementById('btn-run').textContent = 'Run';
return;
}
if (reqIdx >= PAGES.length) doReset();
policy = document.getElementById('policy').value;
running = true;
document.getElementById('btn-run').textContent = 'Pause';
step();
}
function doReset() {
resetState();
doneResults.length = 0;
renderPool(-1, '');
renderQueue(-1, []);
updateStats();
document.getElementById('status').textContent = '';
document.getElementById('status').className = 'status';
document.getElementById('btn-run').textContent = 'Run';
}
document.getElementById('btn-run').addEventListener('click', doRun);
document.getElementById('btn-reset').addEventListener('click', doReset);
// Initialize on load
doReset();
Notice how LRU tracks recency perfectly but requires maintaining an ordered list with every access. Clock approximates LRU with a single reference bit per frame and a rotating hand — almost as accurate, far cheaper to implement. Real engines like PostgreSQL use a variant of Clock for exactly this reason.
Comments
Loading comments...