Below are two replicas of a single value, A and B, that normally stay in sync. Hit Cut the network to partition them. Now write a new value to A — and try to read it from B. B never received the write.
<p class="hint">Two replicas, <b>A</b> and <b>B</b>, hold the same value. Cut the
network, write a new value to A, then read from B. While partitioned, B can stay
<b>consistent</b> (refuse the read) or <b>available</b> (serve a stale value) — never both.</p>
<div class="mode">
<span>Mode:</span>
<button id="cp" type="button" class="active">CP · consistency</button>
<button id="ap" type="button">AP · availability</button>
</div>
<div class="net" id="net">
<div class="node" id="nodeA">
<div class="tag">Replica A</div>
<div class="val" id="valA">v0</div>
</div>
<div class="link" id="link">— in sync —</div>
<div class="node" id="nodeB">
<div class="tag">Replica B</div>
<div class="val" id="valB">v0</div>
</div>
</div>
<div class="status" id="status">Network healthy. A and B agree.</div>
<div class="btns">
<button id="cut" type="button">Cut the network</button>
<button id="write" type="button">Write v1 to A</button>
<button id="read" type="button">Read from B</button>
<button id="heal" type="button" class="ghost">Heal & reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.mode { display: flex; align-items: center; gap: .5rem; margin: .2rem 0 .8rem; font-size: .9rem; flex-wrap: wrap; }
.mode button { background: #fff; color: #1d3557; border: 1px solid #1d3557; padding: .3rem .7rem;
border-radius: 8px; font: 600 13px system-ui, sans-serif; cursor: pointer; }
.mode button.active { background: #1d3557; color: #fff; }
.net { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: .6rem; margin: .6rem 0; }
.node { background: #e8eef3; border: 1px solid #cdd9e3; border-radius: 10px; padding: .7rem; text-align: center; }
.tag { font-size: .78rem; color: #5a7088; font-weight: 600; margin-bottom: .3rem; }
.val { font: 700 24px ui-monospace, monospace; color: #1d3557; }
.val.stale { color: #c98a00; }
.link { font-size: .8rem; color: #0a7d33; font-weight: 600; text-align: center; white-space: nowrap; }
.link.cut { color: #c92f3c; }
.net.cut { position: relative; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.warn { color: #c98a00; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// CAP theorem (Gilbert & Lynch, 2002): during a network partition a replicated
// store cannot be both Consistent (every read sees the latest write) and
// Available (every request gets a non-error answer). This sim makes you feel it.
let mode = 'CP'; // ’CP’ = consistency, ’AP’ = availability
let partitioned = false; // is the network cut?
let valA = 'v0'; // authoritative value on A
let valB = 'v0'; // last value B knows about (only updated when connected)
const $ = id => document.getElementById(id);
function setStatus(t, cls){ const s = $('status'); s.textContent = t; s.className = 'status ' + (cls||''); }
function render(){
$('valA').textContent = valA;
const bStale = valB !== valA;
$('valB').textContent = valB;
$('valB').className = 'val' + (bStale ? ' stale' : '');
$('link').textContent = partitioned ? '— ✂ partition —' : '— in sync —';
$('link').className = 'link' + (partitioned ? ' cut' : '');
$('net').className = 'net' + (partitioned ? ' cut' : '');
$('cp').className = mode === 'CP' ? 'active' : '';
$('ap').className = mode === 'AP' ? 'active' : '';
}
function setMode(m){ mode = m; render();
setStatus(m === 'CP' ? 'CP: B will reject reads while partitioned.'
: 'AP: B will serve stale reads while partitioned.', ''); }
function cut(){ partitioned = true; render();
setStatus('Network cut. A and B can no longer talk.', 'bad'); }
function write(){
valA = 'v1';
// The write only reaches B if a message can cross the network.
if(!partitioned){ valB = valA; setStatus('Wrote v1 to A; replicated to B.', 'ok'); }
else setStatus('Wrote v1 to A. B has NOT received it (partitioned).', 'warn');
render();
}
// The heart of CAP: read from the *other* replica during a partition.
function read(){
if(!partitioned){ setStatus(fmt('Read from B → %valB% (fresh, network healthy).', { valB: valB }), 'ok'); return; }
if(mode === 'CP'){
// Consistency: refuse rather than risk a stale answer -> sacrifice Availability.
setStatus('CP → read REJECTED: B can’t confirm it is current. (sacrificed Availability)', 'bad');
} else {
// Availability: answer with whatever B has -> may be stale -> sacrifice Consistency.
const stale = valB !== valA;
setStatus(fmt('AP → read returns %valB%%v%', { valB: valB, v: stale ? ' — STALE! (sacrificed Consistency)' : ' (happens to match)' }), stale ? 'warn' : 'ok');
}
}
function heal(){ partitioned = false; mode = mode; valA = 'v0'; valB = 'v0'; render();
setStatus('Network healed and reset. A and B agree.', 'ok'); }
$('cp').addEventListener('click', () => setMode('CP'));
$('ap').addEventListener('click', () => setMode('AP'));
$('cut').addEventListener('click', cut);
$('write').addEventListener('click', write);
$('read').addEventListener('click', read);
$('heal').addEventListener('click', heal);
render();
<p class="hint">Two replicas, <b>A</b> and <b>B</b>, hold the same value. Cut the
network, write a new value to A, then read from B. While partitioned, B can stay
<b>consistent</b> (refuse the read) or <b>available</b> (serve a stale value) — never both.</p>
<div class="mode">
<span>Mode:</span>
<button id="cp" type="button" class="active">CP · consistency</button>
<button id="ap" type="button">AP · availability</button>
</div>
<div class="net" id="net">
<div class="node" id="nodeA">
<div class="tag">Replica A</div>
<div class="val" id="valA">v0</div>
</div>
<div class="link" id="link">— in sync —</div>
<div class="node" id="nodeB">
<div class="tag">Replica B</div>
<div class="val" id="valB">v0</div>
</div>
</div>
<div class="status" id="status">Network healthy. A and B agree.</div>
<div class="btns">
<button id="cut" type="button">Cut the network</button>
<button id="write" type="button">Write v1 to A</button>
<button id="read" type="button">Read from B</button>
<button id="heal" type="button" class="ghost">Heal & reset</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .7rem; line-height: 1.45; }
.mode { display: flex; align-items: center; gap: .5rem; margin: .2rem 0 .8rem; font-size: .9rem; flex-wrap: wrap; }
.mode button { background: #fff; color: #1d3557; border: 1px solid #1d3557; padding: .3rem .7rem;
border-radius: 8px; font: 600 13px system-ui, sans-serif; cursor: pointer; }
.mode button.active { background: #1d3557; color: #fff; }
.net { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: .6rem; margin: .6rem 0; }
.node { background: #e8eef3; border: 1px solid #cdd9e3; border-radius: 10px; padding: .7rem; text-align: center; }
.tag { font-size: .78rem; color: #5a7088; font-weight: 600; margin-bottom: .3rem; }
.val { font: 700 24px ui-monospace, monospace; color: #1d3557; }
.val.stale { color: #c98a00; }
.link { font-size: .8rem; color: #0a7d33; font-weight: 600; text-align: center; white-space: nowrap; }
.link.cut { color: #c92f3c; }
.net.cut { position: relative; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c92f3c; }
.status.warn { color: #c98a00; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// CAP theorem (Gilbert & Lynch, 2002): during a network partition a replicated
// store cannot be both Consistent (every read sees the latest write) and
// Available (every request gets a non-error answer). This sim makes you feel it.
let mode = 'CP'; // ’CP’ = consistency, ’AP’ = availability
let partitioned = false; // is the network cut?
let valA = 'v0'; // authoritative value on A
let valB = 'v0'; // last value B knows about (only updated when connected)
const $ = id => document.getElementById(id);
function setStatus(t, cls){ const s = $('status'); s.textContent = t; s.className = 'status ' + (cls||''); }
function render(){
$('valA').textContent = valA;
const bStale = valB !== valA;
$('valB').textContent = valB;
$('valB').className = 'val' + (bStale ? ' stale' : '');
$('link').textContent = partitioned ? '— ✂ partition —' : '— in sync —';
$('link').className = 'link' + (partitioned ? ' cut' : '');
$('net').className = 'net' + (partitioned ? ' cut' : '');
$('cp').className = mode === 'CP' ? 'active' : '';
$('ap').className = mode === 'AP' ? 'active' : '';
}
function setMode(m){ mode = m; render();
setStatus(m === 'CP' ? 'CP: B will reject reads while partitioned.'
: 'AP: B will serve stale reads while partitioned.', ''); }
function cut(){ partitioned = true; render();
setStatus('Network cut. A and B can no longer talk.', 'bad'); }
function write(){
valA = 'v1';
// The write only reaches B if a message can cross the network.
if(!partitioned){ valB = valA; setStatus('Wrote v1 to A; replicated to B.', 'ok'); }
else setStatus('Wrote v1 to A. B has NOT received it (partitioned).', 'warn');
render();
}
// The heart of CAP: read from the *other* replica during a partition.
function read(){
if(!partitioned){ setStatus(fmt('Read from B → %valB% (fresh, network healthy).', { valB: valB }), 'ok'); return; }
if(mode === 'CP'){
// Consistency: refuse rather than risk a stale answer -> sacrifice Availability.
setStatus('CP → read REJECTED: B can’t confirm it is current. (sacrificed Availability)', 'bad');
} else {
// Availability: answer with whatever B has -> may be stale -> sacrifice Consistency.
const stale = valB !== valA;
setStatus(fmt('AP → read returns %valB%%v%', { valB: valB, v: stale ? ' — STALE! (sacrificed Consistency)' : ' (happens to match)' }), stale ? 'warn' : 'ok');
}
}
function heal(){ partitioned = false; mode = mode; valA = 'v0'; valB = 'v0'; render();
setStatus('Network healed and reset. A and B agree.', 'ok'); }
$('cp').addEventListener('click', () => setMode('CP'));
$('ap').addEventListener('click', () => setMode('AP'));
$('cut').addEventListener('click', cut);
$('write').addEventListener('click', write);
$('read').addEventListener('click', read);
$('heal').addEventListener('click', heal);
render();
Pick a mode before you read. In CP (consistency over availability), B refuses to answer while partitioned — your data is never wrong, but the read fails. In AP (availability over consistency), B answers immediately with whatever it last knew — the read succeeds, but the value is stale. There is no third button that gives you a fresh, correct answer from B: the write simply hasn't crossed the cut. Heal the partition and the replicas reconcile.
Comments
Loading comments...