Lesson 16 / 21
Cost Model
The bill for the rented layer splits into four items: of the monthly 1,542 fictional units, 37.7% is compute, 31.8% is transfer, 20.2% is request, and only 10.3% is storage; the request fee is 1.95 times the storage fee. When the same bill is mapped to eleven design decisions, more than half of the decisions (50.8%) belong to the platform, and four spread across more than one item. In the raw measurement stream, the point where the request fee overtakes storage comes out at 693,370 items/month (11,364 bytes); the network writes 152 bytes per item, sitting 74.8 times past that point, and pays 299.43 units of request fee against 6.48 units of storage.
Published Updated
In this lesson
- Splits the monthly bill into compute, storage, transfer, and request items and works out each item's share
- Maps every bill line to the design decision that caused it: counts the decision's owner and how many items the decision spreads across
- Finds the point where the per-request fee becomes dominant: scans the item count and measures the item size at which the request fee overtakes the storage fee
Contents
The previous topic narrowed access, encrypted data, and produced evidence. All three consumed a resource: the key management service was called, an audit log was written, private endpoints were opened, a secret was rotated. None of this was free, and none of it was counted.
This topic reads the bill. The first question is the plainest one: what items make up the monthly total, which design decision does each item come from, and does the bill name that decision?
Four Items and the Decision Behind Them
The example is the fictional regional measurement network: five teams, three environments, a
rented machine, network, store, function, and edge layer. Items, quantities, and unit prices are
fictional; the currency is fictional too and is called unit, corresponding to no real price
list.
CC1: the bill carries four item types — compute, storage, transfer, request — and gives fourteen lines. CC2: every line has a measure, a monthly quantity, and a price per measure. CC3: every line comes from a design decision with an owner; the bill carries neither field, both are kept in the model. CC4: the raw measurement stream’s body volume is fixed — the number of items it is split into does not change the volume, only the item metadata and request count. CC5: data written in a given month is paid for across its retention period. CC6: the bill comes at month and line-item granularity; it carries no split by resource, team, or environment.
The bill, items, and stream are a process-internal model; there is no real bill.
// cost/bill.mjs — monthly bill model: no real bill. Items, quantities, and unit prices are a // data structure; the currency is fictional and is called `unit`, corresponding to no real // price list. Each row is tied to a *bill item*, and also to the *design decision* that caused // it and the party who made that decision; the bill never carries this second link at all. // [item type, line name, measure, monthly quantity, price per measure (unit), decision, decision owner] export const BILL = [ ["compute", "rented machine", "machine-hour", 8640, 0.062, "all three environments are always on", "platform"], ["compute", "function call duration", "thousand GB-seconds", 41.6, 0.850, "a function per event", "application"], ["compute", "edge layer processing", "thousand calls", 930, 0.011, "pre-processing at the edge", "application"], ["storage", "raw measurement body", "GB-month", 189.1, 0.021, "raw measurement kept 24 months", "governance"], ["storage", "item metadata", "GB-month", 119.4, 0.021, "each read writes a separate item", "application"], ["storage", "processed series", "GB-month", 1420, 0.019, "every total is precomputed", "reporting"], ["storage", "snapshot and backup", "GB-month", 2140, 0.048, "daily snapshot, 35 days", "governance"], ["storage", "record store", "GB-month", 1360, 0.017, "every request is logged", "governance"], ["transfer", "out-of-region egress", "GB", 2960, 0.082, "reports are pulled from outside the region", "reporting"], ["transfer", "cross-region copy", "GB", 4400, 0.019, "continuous copy to a second region", "platform"], ["transfer", "within-region cross-zone", "GB", 18200, 0.009, "every write fans out to three regions", "platform"], ["request", "object write", "million requests", 51.84, 4.300, "each read writes a separate item", "application"], ["request", "object read", "million requests", 212.544, 0.360, "reports are pulled from outside the region", "reporting"], ["request", "function call", "million requests", 62.1, 0.190, "a function per event", "application"], ]; export const TYPE = ["compute", "storage", "transfer", "request"]; export const unit = (r) => r[3] * r[4]; export const sum = (v) => v.reduce((a, b) => a + b, 0); // raw measurement stream: volume fixed, the only thing that changes is how many *items* it is split into (CC5) export const STREAM = { counter: 18000, readsPerDay: 96, days: 30, // monthly read count derives from this bodyBytes: 152, metadataBytes: 96, // every item also carries metadata retentionMonths: 24, storageUnit: 0.021, // this month's data is paid for 24 months writeUnit: 4.30, readUnit: 0.36, readMultiple: 4.1, }; export const bodyGB = () => STREAM.counter * STREAM.readsPerDay * STREAM.days * STREAM.bodyBytes / 1e9; // M: monthly item count. Body volume is independent of M; metadata grows with M. export const storageUnits = (M) => (bodyGB() + M * STREAM.metadataBytes / 1e9) * STREAM.retentionMonths * STREAM.storageUnit; export const requestUnits = (M) => M / 1e6 * (STREAM.writeUnit + STREAM.readMultiple * STREAM.readUnit); // both lines are linear in M: the crossover comes out in closed form export const crossover = () => { const a = (STREAM.writeUnit + STREAM.readMultiple * STREAM.readUnit) / 1e6; const b = STREAM.metadataBytes / 1e9 * STREAM.retentionMonths * STREAM.storageUnit; return bodyGB() * STREAM.retentionMonths * STREAM.storageUnit / (a - b); };
// cost/measure.mjs — split into four items, each item mapped to the decision that caused it, // and the point where the request fee overtakes the storage fee. What is measured is not the // bill itself, it is the bill's *resolution*. import { BILL, TYPE, STREAM, unit, sum, bodyGB, storageUnits, requestUnits, crossover } from "./bill.mjs"; const write = (w, ...s) => console.log(s.map((v, i) => (w[i] < 0 ? String(v).padEnd(-w[i]) : String(v).padStart(w[i]))).join("")); const t0 = process.hrtime.bigint(); const T = sum(BILL.map(unit)), share = (x) => `${(100 * x / T).toFixed(1)}%`; console.log(`${BILL.length} bill lines, monthly ${T.toFixed(0)} units (fictional currency)`); console.log("\n1. four items: what the bill says"); const A = [-26, -21, 11, 12, 11, 8]; write(A, "line", "measure", "qty", "price/unit", "monthly", "share"); for (const t of TYPE) { const s = BILL.filter((r) => r[0] === t); for (const r of s) write(A, r[1], r[2], r[3], r[4].toFixed(3), unit(r).toFixed(1), share(unit(r))); const subtotal = sum(s.map(unit)); write(A, ` ${t} total`, "-", "-", "-", subtotal.toFixed(1), share(subtotal)); } const T4 = TYPE.map((t) => sum(BILL.filter((r) => r[0] === t).map(unit))); console.log(` request ${T4[3].toFixed(1)} units, storage ${T4[1].toFixed(1)} units: ` + `ratio ${(T4[3] / T4[1]).toFixed(2)}`); console.log("\n2. the same bill mapped to decisions: a line item does not name a decision"); const K = new Map(); for (const r of BILL) { const k = K.get(r[5]) ?? { unit: 0, types: new Set(), owner: r[6] }; k.unit += unit(r); k.types.add(r[0]); K.set(r[5], k); } const B = [-44, -13, 22, 11, 7]; write(B, "decision", "owner", "which items", "monthly", "share"); const sorted = [...K.entries()].sort((a, b) => b[1].unit - a[1].unit); for (const [name, k] of sorted) write(B, name, k.owner, [...k.types].join("+"), k.unit.toFixed(1), share(k.unit)); const multi = sorted.filter(([, k]) => k.types.size > 1); console.log(` ${K.size} decisions, ${BILL.length} bill lines; ${multi.length} decisions spread ` + `across more than one item type`); const OWNER = new Map(); for (const [, k] of K) OWNER.set(k.owner, (OWNER.get(k.owner) ?? 0) + k.unit); const ownerSorted = [...OWNER.entries()].sort((a, b) => b[1] - a[1]); const ownerLines = ownerSorted.map(([o, v]) => `${o} ${v.toFixed(1)} units (${share(v)})`); console.log(` by decision owner: ${ownerLines.slice(0, 2).join(", ")},`); console.log(` ${ownerLines.slice(2).join(", ")}`); console.log("\n3. where the request fee overtakes the storage fee (raw measurement stream)"); const actualReads = STREAM.counter * STREAM.readsPerDay * STREAM.days; console.log(` volume fixed: ${bodyGB().toFixed(2)} GB/month body, ` + `${(actualReads / 1e6).toFixed(2)} million reads/month`); const C = [15, 12, 12, 12, 10, 11]; write(C, "items/month", "bytes/item", "reads/item", "storage", "request", "dominant"); for (const M of [1e4, 1e5, 3e5, 693372, 1e6, 5e6, 51.84e6, 2e8]) { const d = storageUnits(M), i = requestUnits(M); write(C, M.toLocaleString("en-US"), (bodyGB() * 1e9 / M).toFixed(0), (actualReads / M).toFixed(2), d.toFixed(2), i.toFixed(2), i > d ? "request" : "storage"); } const M0 = crossover(); console.log(` crossover ${Math.round(M0).toLocaleString("en-US")} items/month: ` + `${(bodyGB() * 1e9 / M0).toFixed(0)} bytes/item, ` + `${(actualReads / M0).toFixed(1)} reads/item`); const Mv = actualReads; console.log(` the network's choice, a separate item per read: ${(Mv / M0).toFixed(1)}x past the crossover;`); console.log(` storage ${storageUnits(Mv).toFixed(2)} units, request ${requestUnits(Mv).toFixed(2)} units, ` + `ratio ${(requestUnits(Mv) / storageUnits(Mv)).toFixed(1)}`); console.log("\n4. the bill's resolution: how many splits it gives, how many are needed"); const D = [-36, 8, 18]; const TEAM = 5, ENV = 3, RESOURCE = 6; write(D, "split", "count", "source"); write(D, "bill lines (monthly)", BILL.length, "bill"); write(D, "item types", TYPE.length, "bill"); write(D, "design decisions", K.size, "not in the bill"); write(D, "deciding party", OWNER.size, "not in the bill"); write(D, "team x environment x resource type", TEAM * ENV * RESOURCE, "not in the bill"); const remaining = TEAM * ENV * RESOURCE - BILL.length; console.log(` the bill gives ${BILL.length} splits, the network requires ${TEAM * ENV * RESOURCE} units ` + `of separation: ${remaining} units cannot be read from the bill`); const ms = Number(process.hrtime.bigint() - t0) / 1e6; // raw measurement is not printed, only the band is checked console.log(`\nfour tables within the 2000 ms band: ${ms < 2000 ? "yes" : "no"}`);
14 bill lines, monthly 1542 units (fictional currency)
1. four items: what the bill says
line measure qty price/unit monthly share
rented machine machine-hour 8640 0.062 535.7 34.7%
function call duration thousand GB-seconds 41.6 0.850 35.4 2.3%
edge layer processing thousand calls 930 0.011 10.2 0.7%
compute total - - - 581.3 37.7%
raw measurement body GB-month 189.1 0.021 4.0 0.3%
item metadata GB-month 119.4 0.021 2.5 0.2%
processed series GB-month 1420 0.019 27.0 1.7%
snapshot and backup GB-month 2140 0.048 102.7 6.7%
record store GB-month 1360 0.017 23.1 1.5%
storage total - - - 159.3 10.3%
out-of-region egress GB 2960 0.082 242.7 15.7%
cross-region copy GB 4400 0.019 83.6 5.4%
within-region cross-zone GB 18200 0.009 163.8 10.6%
transfer total - - - 490.1 31.8%
object write million requests 51.84 4.300 222.9 14.5%
object read million requests 212.544 0.360 76.5 5.0%
function call million requests 62.1 0.190 11.8 0.8%
request total - - - 311.2 20.2%
request 311.2 units, storage 159.3 units: ratio 1.95
2. the same bill mapped to decisions: a line item does not name a decision
decision owner which items monthly share
all three environments are always on platform compute 535.7 34.7%
reports are pulled from outside the region reporting transfer+request 319.2 20.7%
each read writes a separate item application storage+request 225.4 14.6%
every write fans out to three regions platform transfer 163.8 10.6%
daily snapshot, 35 days governance storage 102.7 6.7%
continuous copy to a second region platform transfer 83.6 5.4%
a function per event application compute+request 47.2 3.1%
every total is precomputed reporting storage 27.0 1.7%
every request is logged governance storage 23.1 1.5%
pre-processing at the edge application compute 10.2 0.7%
raw measurement kept 24 months governance storage 4.0 0.3%
11 decisions, 14 bill lines; 3 decisions spread across more than one item type
by decision owner: platform 783.1 units (50.8%), reporting 346.2 units (22.5%),
application 282.8 units (18.3%), governance 129.8 units (8.4%)
3. where the request fee overtakes the storage fee (raw measurement stream)
volume fixed: 7.88 GB/month body, 51.84 million reads/month
items/month bytes/item reads/item storage request dominant
10,000 787968 5184.00 3.97 0.06 storage
100,000 78797 518.40 3.98 0.58 storage
300,000 26266 172.80 3.99 1.73 storage
693,372 11364 74.77 4.00 4.00 request
1,000,000 7880 51.84 4.02 5.78 request
5,000,000 1576 10.37 4.21 28.88 request
51,840,000 152 1.00 6.48 299.43 request
200,000,000 39 0.26 13.65 1155.20 request
crossover 693,370 items/month: 11364 bytes/item, 74.8 reads/item
the network's choice, a separate item per read: 74.8x past the crossover;
storage 6.48 units, request 299.43 units, ratio 46.2
4. the bill's resolution: how many splits it gives, how many are needed
split count source
bill lines (monthly) 14 bill
item types 4 bill
design decisions 11 not in the bill
deciding party 4 not in the bill
team x environment x resource type 90 not in the bill
the bill gives 14 splits, the network requires 90 units of separation: 76 units cannot be read from the bill
four tables within the 2000 ms band: yes
The numbers belong to the measurement class; their inputs are the assumptions above.
The Bill’s Shape
The first table splits the monthly 1,542 units into four items. Compute is the largest at 37.7%, and almost all of it comes from a single line: the three environments’ rented machines, 535.7 units, 34.7% of the bill. Transfer is second at 31.8%. Request is 20.2%, and storage is only 10.3%.
The ranking flips an expectation: in a network that stores data, the smallest item should be storage, and instead the request fee is 1.95 times storage. The object-write line alone runs 222.9 units; storage’s five lines together total 159.3 units.
A small unit price does not mean a small item either. Within-region cross-zone transfer, at 0.009 units per GB, is the cheapest unit price on the bill; at 18,200 GB it holds 10.6% of the bill — more than the whole of storage. What is cheap gets used heavily, and heavy use grows.
A Bill Item Does Not Name a Decision
The second table re-totals the same amount by the decision that caused each line. Fourteen bill lines collapse to eleven decisions, and three of them do not fit inside a single item type: “reports are pulled from outside the region” shows up in both transfer and request (319.2 units), “each read writes a separate item” in both storage and request (225.4 units), “a function per event” in both compute and request (47.2 units).
This is why reading the bill does not produce a decision. Read as 20.2%, the request item is not one thing: it holds the residue of three decisions, each of which also marks another item. There is nothing inside the request item itself that shrinks it; the decision has to change.
By decision owner the total sharpens further. 50.8% of the bill comes from three decisions made by the platform: machines staying continuously on, every write spreading across three regions, the continuous copy to a second region. Reporting is 22.5%, application 18.3%, governance 8.4%. Showing the bill to the teams and expecting cuts starts from a place where half the total is no team’s decision.
The Point Where the Small Fee Turns Dominant
The third table isolates a single stream. The raw measurement stream produces 51.84 million reads and 7.88 GB of body a month; that volume is fixed. The only thing that changes is how many items it is split into.
The scan lays two curves side by side. Storage is nearly flat: 3.97 units at ten thousand items, 13.65 units at two hundred million — the growth is all item metadata. Request, by contrast, is linear and starts at zero: 0.06 units at ten thousand items, 1,155.20 at two hundred million.
The two curves cross at 693,370 items/month. That is 11,364 bytes per item, roughly 74.8 reads. Below that point storage dominates and the request fee is noise; above it, the reverse holds.
The network writes every read as a separate item: 152 bytes per item, 74.8 times past the crossover. There, storage is 6.48 units, request is 299.43 units — forty-six times as much. The bill writes the per-request fee as 4.30 units/million, too small to measure for one operation; it becomes dominant not from price but from count. The design decision behind this fee does not look like a storage decision, it is a splitting decision, and the bill never uses that name.
The Bill’s Resolution
The fourth table counts the limit. The bill gives fourteen lines a month and four item types. The network, with five teams, three environments, and six resource types, requires separating 90 units. No mapping takes fourteen lines to ninety units: 76 units cannot be read from the bill. Neither the deciding party nor the decision itself is on the bill; both are known only in the model.
As it stands, the bill is not an accounting document, it is a total; if ownership is not on it, ownership must come from elsewhere.
Summary
- The fictional network’s monthly bill of 1,542 units splits into four items: compute 37.7%, transfer 31.8%, request 20.2%, storage 10.3%. In a network that stores data, the request fee is 1.95 times storage.
- A small unit price does not mean a small item: the cheapest transfer line, at 0.009 units per GB, holds 10.6% of the bill at 18,200 GB.
- Fourteen bill lines come from eleven design decisions, and three spread across more than one item; shrinking an item takes work on the decision, not the item.
- 50.8% of the bill comes from three platform decisions; application teams’ decisions are 18.3%. Expecting the cut from teams leaves out half the total.
- In the fixed-volume raw measurement stream, the request fee overtakes storage at 693,370 items/month — 11,364 bytes per item. The network writes 152-byte items, sitting 74.8 times past the crossover, and pays 299.43 units of request fee against 6.48 units of storage.
- The bill gives fourteen splits, the network requires 90 units of separation: 76 units cannot be read from the bill.
Next Step
The bill’s total has been read and its items split, but no item yet has an owner. The next lesson brings ownership in from outside the bill: resources get tagged, and cost is distributed to teams from that tag. What gets measured is not the distribution itself, it is where the distribution does not hold — the share held by an untagged resource, which rule splits a shared resource, and what happens to the past bill once tagging becomes mandatory.
To keep your progress and take notes, Log in
My notes
Log in to take notes.