119 lines
7.5 KiB
HTML
119 lines
7.5 KiB
HTML
|
|
<!DOCTYPE html>
|
|||
|
|
<html lang="en">
|
|||
|
|
<head>
|
|||
|
|
<meta charset="UTF-8">
|
|||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|||
|
|
<title>The Weight of Waiting — a rightsizing playbook</title>
|
|||
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js">
|
|||
|
|
<meta property="og:title" content="The Weight of Waiting — a rightsizing playbook">
|
|||
|
|
<meta property="og:description" content="Idle provisioned compute is the priciest thing in a fleet. Read the ledger, do the math, release the throwaway box.">
|
|||
|
|
<meta property="og:url" content="https://kimberly-bennett.4ort.net/playbook.html">
|
|||
|
|
<style>
|
|||
|
|
:root{
|
|||
|
|
--ink:#1c1a17; --paper:#f3ecdd; --ledger:#c8b28e; --copper:#d98c4a; --umber:#6b5835;
|
|||
|
|
--ruled:#d8cdb6; --night:#12100d;
|
|||
|
|
}
|
|||
|
|
*{box-sizing:border-box}
|
|||
|
|
body{margin:0;background:var(--paper);color:var(--ink);
|
|||
|
|
font-family:'Courier New',Courier,monospace;line-height:1.6}
|
|||
|
|
.sheet{max-width:1000px;margin:0 auto;padding:48px 28px 80px}
|
|||
|
|
.masthead{border-bottom:3px double var(--ink);padding-bottom:18px;margin-bottom:34px}
|
|||
|
|
.kicker{font-size:13px;letter-spacing:3px;color:var(--umber);text-transform:uppercase}
|
|||
|
|
h1{font-size:44px;line-height:1.1;margin:8px 0 6px;letter-spacing:-1px}
|
|||
|
|
.led-lbl{font-size:15px;color:var(--umber)}
|
|||
|
|
.rule{border:0;border-top:2px solid var(--ink)}
|
|||
|
|
.rule.thin{border-top:1px solid var(--ruled)}
|
|||
|
|
p.led{font-size:17px;max-width:72ch}
|
|||
|
|
h2{font-size:26px;margin-top:52px;letter-spacing:-.5px;border-left:6px solid var(--copper);padding-left:14px}
|
|||
|
|
table{width:100%;border-collapse:collapse;margin:24px 0;font-size:16px}
|
|||
|
|
th{text-align:left;color:var(--umber);text-transform:uppercase;letter-spacing:2px;font-size:13px;
|
|||
|
|
border-bottom:2px solid var(--ink);padding:10px}
|
|||
|
|
td{padding:11px 10px;border-bottom:1px solid var(--ruled);vertical-align:top}
|
|||
|
|
td.num{text-align:right;font-variant-numeric:tabular-nums}
|
|||
|
|
td.copper{color:var(--copper);font-weight:bold}
|
|||
|
|
.tot{border-top:2px solid var(--ink);font-weight:bold}
|
|||
|
|
.calc{border:2px solid var(--ink);background:#fbf6ea;padding:28px}
|
|||
|
|
.calc label{display:block;font-size:13px;text-transform:uppercase;letter-spacing:2px;color:var(--umber);margin:16px 0 6px}
|
|||
|
|
.calc input,.calc select{width:100%;font-family:inherit;font-size:18px;padding:10px;
|
|||
|
|
border:1px solid var(--ledger);background:#fff;color:var(--ink)}
|
|||
|
|
.calc .out{margin-top:20px;font-size:30px;font-weight:bold;color:var(--copper);border-top:2px solid var(--ink);padding-top:16px}
|
|||
|
|
.calc .out small{font-size:14px;color:var(--umber);font-weight:normal;display:block}
|
|||
|
|
.note{font-size:14px;color:var(--umber);margin-top:12px}
|
|||
|
|
.pull{font-size:22px;font-style:italic;border-left:4px solid var(--copper);padding-left:18px;margin:34px 0;color:var(--umber)}
|
|||
|
|
.git{font-size:13px;background:#e9e0cc;padding:12px;margin-top:40px}
|
|||
|
|
.git a{color:var(--copper)}
|
|||
|
|
a{color:var(--copper)}
|
|||
|
|
.btn{display:inline-block;background:var(--ink);color:var(--paper);padding:12px 22px;text-decoration:none;
|
|||
|
|
font-weight:bold;margin-top:28px;letter-spacing:1px}
|
|||
|
|
.btn:hover{background:var(--copper);color:var(--night)}
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<nav id="nav"></nav>
|
|||
|
|
<div class="sheet">
|
|||
|
|
<div class="masthead">
|
|||
|
|
<div class="kicker">Cloud cost · Kimberly Bennett</div>
|
|||
|
|
<h1>The Weight of Waiting</h1>
|
|||
|
|
<div class="led-lbl">A rightsizing playbook for the box that never gets a visitor · companion to the film on 4ort.mov</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<p class="led"><strong>The ship that costs the most is the one at anchor.</strong> Every provisioned instance is a crew being paid to sit. The signal that matters is not uptime — it's <em>utilization under real load</em>. I've run teams that bragged about 99.9% availability while bleeding budget on a fleet that was 98% idle. This page is the ledger I now open before any capacity decision.</p>
|
|||
|
|
|
|||
|
|
<div class="pull">"True readiness is knowing when to pack it out and carry the load with you."</div>
|
|||
|
|
|
|||
|
|
<h2>The three kinds of box</h2>
|
|||
|
|
<table>
|
|||
|
|
<tr><th>Role</th><th>Real duty cycle</th><th>What to do with it</th></tr>
|
|||
|
|
<tr><td><strong>Prod web tier</strong></td><td class="num">24/7, p95 < 60s</td><td>Keep hot. Autoscale on the p95, not the mean.</td></tr>
|
|||
|
|
<tr><td><strong>Batch workers</strong></td><td class="num">4 hrs / night</td><td>Right-size to the peak job, then let them <em>stop</em>. Spot if it can retry.</td></tr>
|
|||
|
|
<tr><td><strong>Throwaway demo box</strong></td><td class="num">"just in case"</td><td>Release it. Clone on demand in 3 minutes.</td></tr>
|
|||
|
|
</table>
|
|||
|
|
|
|||
|
|
<h2>Worked example — one quarter, one decision</h2>
|
|||
|
|
<table>
|
|||
|
|
<tr><th>Line</th><th>Monthly cost</th><th>Utilization</th><th>Action</th></tr>
|
|||
|
|
<tr><td>Prod web tier</td><td class="num">$1,840</td><td class="num">41%</td><td>halve to 3× m5.large → $1,060</td></tr>
|
|||
|
|
<tr><td>Batch workers ×4</td><td class="num">$212</td><td class="num">14%</td><td>2 workers + on-demand pickups → $98</td></tr>
|
|||
|
|
<tr><td>Demo box</td><td class="num">$190</td><td class="num">0.2%</td><td>release → $0</td></tr>
|
|||
|
|
<tr class="tot"><td>Total</td><td class="num">$2,242</td><td class="num"></td><td class="copper">→ $1,158/mo</td></tr>
|
|||
|
|
</table>
|
|||
|
|
<p class="led">That's <strong>48% off the ledger</strong> — $13,008 a year — and the only thing we gave up was a box nobody was using anyway. The benchmark I set for my team: <em>every rightsizing review should find at least one throwaway.</em></p>
|
|||
|
|
|
|||
|
|
<h2>The calculator — what is idle costing you?</h2>
|
|||
|
|
<div class="calc">
|
|||
|
|
<label for="cpm">Monthly cost of the instance ($)</label>
|
|||
|
|
<input id="cpm" type="number" value="190">
|
|||
|
|
<label for="util">Real utilization you can measure (%)</label>
|
|||
|
|
<input id="util" type="number" value="2">
|
|||
|
|
<label for="months">Months it's been running like this</label>
|
|||
|
|
<input id="months" type="number" value="6">
|
|||
|
|
<div class="out" id="out">$0<small>wasted on idle compute</small></div>
|
|||
|
|
<p class="note">Formula: wasted = cost × (1 − utilization/100) × months. Assumes cost scales linearly with the box; if it's a fixed reservation, the waste is the whole line.</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<h2>Where this breaks</h2>
|
|||
|
|
<p class="led">The calculator lies when your utilization number is a lie. If you're measuring the <em>mean</em> of a spiky load, you'll under-provision the fast path and toast the p95. Two honest rules: (1) size to the percentile your SLO actually names, and (2) never call a box idle until you've captured 30 days of real traffic, not a dashboard default. A wrong release is more expensive than the waste it solved.</p>
|
|||
|
|
|
|||
|
|
<a class="btn" href="https://kimberly-bennett.4ort.net/">Back to basecamp →</a>
|
|||
|
|
|
|||
|
|
<div class="git">Data twin: <a href="playbook.json">playbook.json</a>. Machine-readable constants + full ledger. Source on <a href="#" id="repolink">4ort.dev</a>. Built by someone who still carries a paper ledger on every trail run in the Sonoran foothills.</div>
|
|||
|
|
<fort-mind limit="6"></fort-mind>
|
|||
|
|
<fort-fedi limit="4"></fort-fedi>
|
|||
|
|
</div>
|
|||
|
|
<script>
|
|||
|
|
const cpm=document.getElementById('cpm'),
|
|||
|
|
util=document.getElementById('util'),
|
|||
|
|
months=document.getElementById('months'),
|
|||
|
|
out=document.getElementById('out');
|
|||
|
|
function calc(){
|
|||
|
|
const c=parseFloat(cpm.value)||0,u=Math.min(100,Math.max(0,parseFloat(util.value)||0)),
|
|||
|
|
m=parseFloat(months.value)||0;
|
|||
|
|
const waste=c*(1-u/100)*m;
|
|||
|
|
out.innerHTML='$'+waste.toLocaleString('en-US',{maximumFractionDigits:0})+
|
|||
|
|
'<small>wasted on idle compute</small>';
|
|||
|
|
}
|
|||
|
|
[cpm,util,months].forEach(el=>el.addEventListener('input',calc)); calc();
|
|||
|
|
</script>
|
|||
|
|
<fort-nav></fort-nav>
|
|||
|
|
</body>
|
|||
|
|
</html>
|