Overview
What this concept solves
2Q — Two Queue — is the simplest answer to 'how do I make LRU survive a scan?' Theodore Johnson and Dennis Shasha published it in 1994 as a buffer-pool replacement that consistently beats LRU on database workloads. The idea: new entries are on probation in a small FIFO queue. Only if they're re-accessed do they get promoted to the main LRU. Scans, which never re-access, never make it past probation.
MySQL InnoDB ships a 2Q variant as its default. So do several CDN edge caches. It's cheap, easy to implement, and gives you most of ARC's scan resistance with far less complexity.
Mechanics
How it works
Two queues, a single promotion rule
There are two cached lists plus a ghost list:
- A1in — a small FIFO queue, typically ~25% of capacity. First-time entries land here.
- Am — a large LRU queue, the remaining ~75%. The 'proven' working set lives here.
- A1out (ghost) — keys recently evicted from A1in, no values. Used to detect 'almost popular' entries.
The algorithm on access:
- Hit in Am — move to head of Am (standard LRU touch).
- Hit in A1in — don't promote yet. Leave it in place. (This is the version called 2Q-simplified; the original 2Q-Full promotes here.)
- Hit in A1out (ghost) — the key was kicked out of A1in but is asked for again, so it's worth keeping. Insert into Am at the head.
- Miss everywhere — insert into A1in at the head. If A1in is over its budget, push its tail into A1out (just the key) and drop the value.
Why this kills scans
A scan reads N unique keys, each exactly once. They all enter A1in as misses, and they all get pushed out of A1in (and into A1out) in FIFO order without ever being promoted to Am. Your hot Am entries — the things your real workload cares about — are never touched.
Interactive prototype
Run it. Break it. Tune it.
Sandboxed simulation embedded right in the page. No setup, no install.
About this simulation
Three lists. New keys land in A1in (small FIFO probation, top-left). When A1in overflows, the evicted key drops into A1out (ghost list, bottom) — keys only, no values. A future hit on a ghost promotes the key into Am (the real LRU cache, top-right). Watch the demo: A enters A1in, becomes a ghost, then on its next access gets promoted to Am.
Hands-on
Try these on your own
Open the prototype above, run each experiment, predict the answer, then verify.
Walk a key from A1in to ghost to Am
Click A. It lands in A1in. Click B, then C — A is pushed out of A1in and shows up dashed in A1out as a ghost. Now click A again. The status line says GHOST HIT — A is promoted directly into Am.
Watch a scan get absorbed
Click Reset. Click A twice to plant it in A1in. Now scan: D, E, F, B. Watch A1in cycle through them while Am stays empty — none of the scan keys are accessed twice in time, so none of them earn promotion.
An A1in re-hit doesn't promote
Click A, then immediately click A again. The status confirms 'HIT A1in (no reorder)'. 2Q-simplified only promotes via the ghost path — re-hits inside A1in are noted but don't move the key.
In practice
When to use it — and what you give up
When to reach for it
- Buffer pools for storage engines — MySQL InnoDB ships a 2Q variant by default.
- Workloads that intermittently scan — analytics queries against an OLTP database, occasional batch jobs against a live cache.
- You want scan resistance without ARC's complexity or patent.
Real-world example
MySQL InnoDB splits the buffer pool into a 'young' sublist (≈63%) and an 'old' sublist (≈37%), with promotion gated by a time-on-old threshold. The shape is 2Q — different names, same idea.
Pros
- Strong scan resistance — the dominant reason it's chosen in database buffer pools.
- Conceptually simple — two lists and a small ghost list, with a single promotion rule.
- Empirically competitive with ARC on most workloads, at much lower implementation cost.
- Patent-free.
Cons
- Has tuning parameters: the size ratio between A1in, Am, and A1out. Not adaptive.
- More moving parts than plain LRU — three lists vs one.
- If the workload is purely recency-driven (no scans), plain LRU does about as well with less code.
Reference
Code & further reading
A minimal reference implementation and pointers worth bookmarking.
// 2Q simplified: A1in (FIFO), Am (LRU), A1out (ghost set).
class TwoQ<K, V> {
private a1in = new Map<K, V>(); // FIFO probation
private am = new Map<K, V>(); // LRU main
private a1out = new Set<K>(); // ghost
constructor(
private kIn: number, // capacity of A1in
private kOut: number, // capacity of A1out (ghost)
private kMain: number, // capacity of Am
) {}
get(key: K): V | undefined {
if (this.am.has(key)) {
const v = this.am.get(key)!;
this.am.delete(key); this.am.set(key, v); // touch (LRU)
return v;
}
if (this.a1in.has(key)) {
return this.a1in.get(key)!; // do NOT promote on first re-hit
}
return undefined;
}
set(key: K, value: V): void {
if (this.am.has(key) || this.a1in.has(key)) return; // already cached
if (this.a1out.has(key)) {
// Ghost hit: promote straight to Am
this.a1out.delete(key);
this.evictMainIfNeeded();
this.am.set(key, value);
return;
}
// Brand-new key -> A1in
if (this.a1in.size >= this.kIn) {
const oldest = this.a1in.keys().next().value!;
this.a1in.delete(oldest);
this.a1out.add(oldest);
if (this.a1out.size > this.kOut) {
const ghostOldest = this.a1out.values().next().value!;
this.a1out.delete(ghostOldest);
}
}
this.a1in.set(key, value);
}
private evictMainIfNeeded(): void {
if (this.am.size >= this.kMain) {
const oldest = this.am.keys().next().value!;
this.am.delete(oldest);
}
}
}References & further reading
3 sources- Papervldb.org
Johnson & Shasha (1994) — 2Q: A Low Overhead High Performance Buffer Management Replacement Algorithm
The original VLDB paper. Tight, readable, full of numbers.
- Docsdev.mysql.com
MySQL Reference Manual — The InnoDB Buffer Pool
Documents the young/old sublist + midpoint-insertion design — InnoDB's scan-resistant flavor of 2Q.
- Articleen.wikipedia.org
Wikipedia — Cache replacement policies
Places 2Q alongside LRU, ARC and LIRS for quick comparison.
Knowledge check
Did the prototype land?
Quick questions, answers revealed on submit. No scoring saved.
question 01 / 03
Why does 2Q resist scans better than LRU?
question 02 / 03
What is the role of the A1out ghost list?
question 03 / 03
Which production system most prominently uses a 2Q-style policy?
0/3 answered
Was this concept helpful?
Tell us what worked, or what to improve. We read every note.