In the Shadow Copy Scheme, when a transaction updates data, it does not overwrite the original data immediately. Instead, it writes a shadow copy (a duplicate) of the data. The original data remains untouched until the transaction is completed successfully.
🔧 How It Works (Step-by-Step):
Start Transaction
The system creates a shadow copy of the data that needs to be changed.
Make Updates to Shadow Copy
All changes are applied to the shadow copy, not the original.
Commit Transaction
If the transaction succeeds, the system swaps the original copy with the updated shadow copy.
Abort Transaction
If the transaction fails, the shadow copy is discarded and the original remains unchanged.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shadow Paging — Try It Yourself</title>
<style>
.infographic-shadowpaging {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--bad: #dc2626;
--bad-soft: #fee2e2;
--good: #16a34a;
--good-soft: #dcfce7;
--on-accent: #ffffff;
--display-font: Georgia, 'Iowan Old Style', serif;
--body-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--mono-font: 'SF Mono', Consolas, monospace;
background: var(--paper);
color: var(--ink);
font-family: var(--body-font);
max-width: 900px;
margin: 0 auto;
box-sizing: border-box;
overflow-x: hidden;
}
.infographic-shadowpaging[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--bad: #f87171;
--bad-soft: #3a1414;
--good: #4ade80;
--good-soft: #123420;
--on-accent: #101418;
}
.infographic-shadowpaging * { box-sizing: border-box; }
.sp-head { text-align: center; margin-bottom: 0.25rem; }
.sp-head h2 {
font-family: var(--display-font);
font-size: 1.95rem;
margin: 0 0 0.5rem;
}
.sp-head p {
color: var(--ink-soft);
font-size: 1rem;
margin: 0 auto;
max-width: 580px;
line-height: 1.55;
}
.sp-diagram-scroll {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
background: var(--paper-alt);
border: 1px solid var(--line);
border-radius: 10px;
padding: 1rem 0.5rem;
margin-top: 1.75rem;
min-width: 0;
}
.sp-diagram-scroll svg { width: 100%; height: auto; display: block; }
.sp-pointer-box { fill: var(--accent); }
.sp-pointer-label { fill: var(--ink); font-size: 13px; font-weight: 700; font-family: var(--body-font); }
.sp-arrow { stroke: var(--ink); stroke-width: 2.5; fill: none; transition: d 0.3s; }
.sp-cyl-fill { fill: var(--accent); transition: fill 0.3s, opacity 0.3s; }
.sp-cyl-fill.is-faded { fill: var(--ink-soft); opacity: 0.35; }
.sp-cyl-fill.is-new { fill: var(--good); }
.sp-cyl-fill.is-hidden { opacity: 0; }
.sp-cyl-label { fill: var(--on-accent); font-size: 13px; font-weight: 600; font-family: var(--body-font); text-anchor: middle; transition: opacity 0.3s; }
.sp-cyl-label.is-hidden { opacity: 0; }
.sp-cyl-caption { fill: var(--ink-soft); font-size: 12px; font-family: var(--body-font); text-anchor: middle; }
.sp-frame {
background: var(--paper-alt);
border: 1px solid var(--line);
border-radius: 10px;
padding: 1.25rem;
margin-top: 1.25rem;
}
.sp-steps { display: flex; flex-direction: column; gap: 0.6rem; }
.sp-step {
display: flex;
align-items: flex-start;
gap: 0.7rem;
background: var(--paper);
border: 1px solid var(--line);
border-radius: 8px;
padding: 0.7rem 0.9rem;
opacity: 0.5;
transition: opacity 0.2s, border-color 0.2s;
}
.sp-step.is-available { opacity: 1; }
.sp-step.is-done { opacity: 1; border-color: var(--good); }
.sp-step-num {
flex-shrink: 0;
width: 26px;
height: 26px;
border-radius: 50%;
background: var(--paper-alt);
color: var(--ink-soft);
display: flex;
align-items: center;
justify-content: center;
font-size: 0.78rem;
font-weight: 700;
}
.sp-step.is-done .sp-step-num { background: var(--good); color: #fff; }
.sp-step-body { flex: 1; }
.sp-step-title { font-weight: 700; font-size: 0.88rem; margin: 0 0 0.2rem; }
.sp-step-desc { font-size: 0.82rem; color: var(--ink-soft); margin: 0 0 0.55rem; line-height: 1.4; }
.sp-step-btn {
font: inherit;
font-size: 0.8rem;
background: var(--accent);
color: var(--on-accent);
border: none;
border-radius: 6px;
padding: 0.4rem 0.8rem;
cursor: pointer;
}
.sp-step-btn:hover { opacity: 0.9; }
.sp-step-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.sp-step-btn.is-danger { background: var(--bad); }
.sp-choice-row { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.sp-msg {
margin-top: 1rem;
padding: 0.7rem 0.9rem;
border-radius: 8px;
font-size: 0.85rem;
line-height: 1.55;
display: none;
}
.sp-msg.is-visible { display: block; }
.sp-msg.msg-good { background: var(--good-soft); color: var(--good); }
.sp-msg.msg-bad { background: var(--bad-soft); color: var(--bad); }
.sp-reset-btn {
font: inherit;
font-size: 0.82rem;
background: var(--paper);
border: 1px solid var(--line);
border-radius: 7px;
padding: 0.45rem 0.9rem;
cursor: pointer;
color: var(--ink);
margin-top: 1.1rem;
}
.sp-reset-btn:hover { border-color: var(--accent); }
@media (prefers-reduced-motion: reduce) {
.infographic-shadowpaging *, .infographic-shadowpaging *::before, .infographic-shadowpaging *::after {
transition-duration: 0.001ms !important;
animation-duration: 0.001ms !important;
}
}
</style>
</head>
<body>
<div class="infographic-shadowpaging" id="shadowpaging">
<div class="sp-head">
<h2>Shadow Paging — Try It Yourself</h2>
<p>Instead of editing the database directly, updates happen on a shadow copy. The <code>DB_pointer</code> only switches over once everything succeeds.</p>
</div>
<div class="sp-diagram-scroll">
<svg viewBox="0 0 620 340" role="img" aria-label="Shadow paging diagram with DB pointer and cylinders">
<!-- pointer -->
<rect class="sp-pointer-box" x="195" y="20" width="30" height="30" rx="7"></rect>
<text class="sp-pointer-label" x="210" y="15" text-anchor="middle">DB_pointer</text>
<path class="sp-arrow" id="spArrow" d="M210,50 L210,90 L140,90 L140,138" marker-end="url(#spArrowHead)"></path>
<defs>
<marker id="spArrowHead" markerWidth="9" markerHeight="9" refX="4" refY="4" orient="auto">
<path d="M0,0 L8,4 L0,8 z" fill="var(--ink)"></path>
</marker>
</defs>
<!-- Original cylinder -->
<g id="spOriginal">
<ellipse class="sp-cyl-fill" id="spOrigBottom" cx="140" cy="280" rx="70" ry="18"></ellipse>
<rect class="sp-cyl-fill" id="spOrigBody" x="70" y="140" width="140" height="140"></rect>
<ellipse class="sp-cyl-fill" id="spOrigTop" cx="140" cy="140" rx="70" ry="18"></ellipse>
<text class="sp-cyl-label" id="spOrigLabel1" x="140" y="215"></text>
<text class="sp-cyl-label" id="spOrigLabel2" x="140" y="233"></text>
<text class="sp-cyl-caption" x="140" y="320">Original</text>
</g>
<!-- Shadow cylinder -->
<g id="spShadow" style="opacity:0">
<ellipse class="sp-cyl-fill" id="spShadowBottom" cx="420" cy="280" rx="70" ry="18"></ellipse>
<rect class="sp-cyl-fill" id="spShadowBody" x="350" y="140" width="140" height="140"></rect>
<ellipse class="sp-cyl-fill" id="spShadowTop" cx="420" cy="140" rx="70" ry="18"></ellipse>
<text class="sp-cyl-label" id="spShadowLabel1" x="420" y="215"></text>
<text class="sp-cyl-label" id="spShadowLabel2" x="420" y="233"></text>
<text class="sp-cyl-caption" x="420" y="320">Shadow</text>
</g>
</svg>
</div>
<div class="sp-frame">
<div class="sp-steps">
<div class="sp-step" data-step="1">
<div class="sp-step-num">1</div>
<div class="sp-step-body">
<p class="sp-step-title">Start Transaction</p>
<p class="sp-step-desc">The system creates a shadow copy of the data that needs to change.</p>
<button class="sp-step-btn" id="spStep1" type="button">Create shadow copy</button>
</div>
</div>
<div class="sp-step" data-step="2">
<div class="sp-step-num">2</div>
<div class="sp-step-body">
<p class="sp-step-title">Make Updates to Shadow Copy</p>
<p class="sp-step-desc">All changes are applied to the shadow copy — the original is untouched.</p>
<button class="sp-step-btn" id="spStep2" type="button" disabled>Apply updates to shadow</button>
</div>
</div>
<div class="sp-step" data-step="3">
<div class="sp-step-num">3</div>
<div class="sp-step-body">
<p class="sp-step-title">Commit or Abort</p>
<p class="sp-step-desc">Succeed, and the pointer swaps to the shadow copy. Fail, and the shadow copy is simply discarded.</p>
<div class="sp-choice-row">
<button class="sp-step-btn" id="spCommit" type="button" disabled>✔️ Commit — swap pointer</button>
<button class="sp-step-btn is-danger" id="spAbort" type="button" disabled>✕ Abort — discard shadow</button>
</div>
</div>
</div>
</div>
<div class="sp-msg" id="spMsg"></div>
<button class="sp-reset-btn" id="spReset" type="button">Reset</button>
</div>
</div>
<script>
(function () {
const wrapper = document.getElementById('shadowpaging');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
(function () {
let state = 'idle'; // idle -> created -> updated -> committed | aborted
const arrow = document.getElementById('spArrow');
const shadowGroup = document.getElementById('spShadow');
const origLabel1 = document.getElementById('spOrigLabel1');
const origLabel2 = document.getElementById('spOrigLabel2');
const shadowLabel1 = document.getElementById('spShadowLabel1');
const shadowLabel2 = document.getElementById('spShadowLabel2');
const origParts = ['spOrigTop', 'spOrigBody', 'spOrigBottom'].map(id => document.getElementById(id));
const shadowParts = ['spShadowTop', 'spShadowBody', 'spShadowBottom'].map(id => document.getElementById(id));
const steps = { 1: document.querySelector('.sp-step[data-step="1"]'), 2: document.querySelector('.sp-step[data-step="2"]'), 3: document.querySelector('.sp-step[data-step="3"]') };
const btn1 = document.getElementById('spStep1');
const btn2 = document.getElementById('spStep2');
const btnCommit = document.getElementById('spCommit');
const btnAbort = document.getElementById('spAbort');
const msg = document.getElementById('spMsg');
const ARROW_TO_ORIGINAL = 'M210,50 L210,90 L140,90 L140,138';
const ARROW_TO_SHADOW = 'M210,50 L210,90 L420,90 L420,138';
function showMsg(text, type) {
msg.textContent = text;
msg.className = 'sp-msg is-visible msg-' + type;
}
function render() {
steps[1].classList.toggle('is-available', state === 'idle');
steps[1].classList.toggle('is-done', state !== 'idle');
steps[2].classList.toggle('is-available', state === 'created');
steps[2].classList.toggle('is-done', state === 'updated' || state === 'committed' || state === 'aborted');
steps[3].classList.toggle('is-available', state === 'updated');
steps[3].classList.toggle('is-done', state === 'committed' || state === 'aborted');
btn1.disabled = state !== 'idle';
btn2.disabled = state !== 'created';
btnCommit.disabled = state !== 'updated';
btnAbort.disabled = state !== 'updated';
origLabel1.textContent = '';
origLabel2.textContent = '';
shadowLabel1.textContent = '';
shadowLabel2.textContent = '';
origParts.forEach(p => p.classList.remove('is-faded', 'is-new'));
if (state === 'idle') {
origLabel1.textContent = 'Original Copy';
origLabel2.textContent = 'of Database';
shadowGroup.style.opacity = '0';
arrow.setAttribute('d', ARROW_TO_ORIGINAL);
} else if (state === 'created') {
origLabel1.textContent = 'Original Copy';
origLabel2.textContent = 'of Database';
shadowGroup.style.opacity = '1';
shadowLabel1.textContent = 'Shadow Copy';
shadowLabel2.textContent = '(just created)';
arrow.setAttribute('d', ARROW_TO_ORIGINAL);
} else if (state === 'updated') {
origLabel1.textContent = 'Original Copy';
origLabel2.textContent = '(unchanged)';
shadowGroup.style.opacity = '1';
shadowLabel1.textContent = 'Shadow Copy';
shadowLabel2.textContent = '(updated)';
arrow.setAttribute('d', ARROW_TO_ORIGINAL);
} else if (state === 'committed') {
origParts.forEach(p => p.classList.add('is-faded'));
origLabel1.textContent = 'Old copy —';
origLabel2.textContent = 'to be deleted';
shadowGroup.style.opacity = '1';
shadowParts.forEach(p => p.classList.add('is-new'));
shadowLabel1.textContent = 'New Copy';
shadowLabel2.textContent = 'of Database';
arrow.setAttribute('d', ARROW_TO_SHADOW);
} else if (state === 'aborted') {
origLabel1.textContent = 'Original Copy';
origLabel2.textContent = 'of Database';
shadowGroup.style.opacity = '0';
arrow.setAttribute('d', ARROW_TO_ORIGINAL);
}
}
btn1.addEventListener('click', () => {
state = 'created';
render();
showMsg('A shadow copy now sits beside the original. DB_pointer still points to the original — nothing about the live database has changed yet.', 'good');
});
btn2.addEventListener('click', () => {
state = 'updated';
render();
showMsg('The updates are written only to the shadow copy. The original is completely untouched, so it\u2019s always safe to back out.', 'good');
});
btnCommit.addEventListener('click', () => {
state = 'committed';
render();
showMsg('✔️ Commit: DB_pointer swaps to the shadow copy in one atomic step — it\u2019s now the official database. The old copy is simply discarded.', 'good');
});
btnAbort.addEventListener('click', () => {
state = 'aborted';
render();
showMsg('✕ Abort: the shadow copy is discarded. DB_pointer never moved, so the original database was never at risk.', 'bad');
});
document.getElementById('spReset').addEventListener('click', () => {
state = 'idle';
render();
msg.className = 'sp-msg';
});
render();
})();
</script>
</body>
</html>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});✅ Advantages:
Atomicity: Either all changes are made, or none are.
Durability: Once committed, changes persist even after a crash.
No Need for Undo: Since original data is never overwritten until commit.
❌ Disadvantages:
High Storage Use: Requires extra space to store shadow copies.
Slow with Large Data: Copying large data structures can be inefficient.
Not Suitable for High-Concurrency Systems: Locks entire data blocks during transactions.
📘 Usage:
File Systems: Windows uses "Volume Shadow Copy" to back up files without interrupting the use of the system.
Databases: Simple DBMS or log-based systems may use this scheme for transaction safety.
🧠 Summary:
Feature | Description |
|---|---|
Safe Updates | Modifies data in shadow, not original |
Crash Recovery | Easy, just use original if transaction fails |
Performance | Slower, more storage needed |