---
title: 'Cost Model'
source: 'https://academia.sh/en/courses/cloud-governance/cost-model'
course: 'Cloud Governance, Security and Cost'
language: en
updated: '2026-09-13T05:07:34+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - "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'
---

# 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.

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.

```js
// 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);
};
```

```js
// 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.
