---
title: 'Archive Storage'
source: 'https://academia.sh/en/courses/cloud-storage/archive-storage'
course: 'Storage and Data in the Cloud'
language: en
updated: '2026-09-13T05:07:36+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - "Scans the archive tier's trade-off between storage and retrieval across three speed options"
    - "Works out an audit request's total wait time through the batch and round calculation"
    - 'Counts the retrieval fee and the egress fee as separate items'
    - 'Measures the minimum retention period as an unrecoverable item and shows that early deletion gains nothing'
---

# Archive Storage

How the archive tier's storage fee drops while retrieval is both billed and delayed: scanning three retrieval speeds along with their time and fee, working out how many hours an audit request waits, the egress fee stacking on top of retrieval, and the minimum retention period making an early-deleted object pay the full period.

When access history was scanned in the second lesson, one family stood apart: 97% of field photos
were never opened all year. For such data the frequent class is an obvious waste, and the third
lesson's lifecycle rule can send it to archive automatically. What data sent to archive actually
becomes has not yet been measured.

The archive tier is a storage class, but it departs from the others at one point: data **cannot be
read right away**. Retrieving an object first requires a retrieval request, the request is
processed, and only then does the object become readable. This wait ranges from minutes to hours,
and it can be sped up — for a fee. The wait is not the rule for every archive class: some
providers' cheapest class is read in milliseconds and differs only in fee and minimum storage
duration; this lesson measures the one with the wait. This is the lesson's measure: where what
was gained in storage
gets paid back, and in which item.

- **ST31** — The archive tier, retrieval speeds, and fees are fictional; tiers are called by access
  frequency.
- **ST32** — There are three retrieval speeds; each one's first-response latency, streaming rate,
  byte fee, and request fee are written into the model.
- **ST33** — A retrieval request takes at most 10,000 objects, and four batches are processed at
  once.

```js
// archive/model.mjs — the archive tier, retrieval speeds, and the audit request are a MODEL. Fees are
// a fictional unit; tiers are called by access frequency, no commercial name is used.
export const CLASS = {                  // storage unit/GiB/month, transition unit/request, minDays: minimum retention
  frequent: { storage: 0.0230, transition: 0.0050 / 1000, minDays: 0 },
  archive: { storage: 0.0010, transition: 0.0500 / 1000, minDays: 180 },
};
// Retrieval speeds: first-response latency, streaming rate, byte and request fee.
export const SPEED = {
  slow: { firstHour: 12, gibPerHour: 250, perGib: 0.0025, request: 0.030 / 1000 },
  medium: { firstHour: 4, gibPerHour: 600, perGib: 0.0100, request: 0.060 / 1000 },
  fast: { firstHour: 5 / 60, gibPerHour: 1500, perGib: 0.0300, request: 0.120 / 1000 },
};
export const BATCH = 10_000, CONCURRENT = 4;        // 10,000 objects a request, 4 batches at once
export const EGRESS = { "in-region": 0, "cross-region": 0.020, internet: 0.085 };
// Fictional audit request: one province's three years of invoice documents and field photos.
export const AUDIT = { invoice: { count: 46_000, kib: 380 }, photo: { count: 80_000, kib: 3686 } };
export const gib = (bytes) => bytes / 1024 ** 3;
export const auditBytes = Object.values(AUDIT).reduce((a, k) => a + k.count * k.kib * 1024, 0);
export const auditCount = Object.values(AUDIT).reduce((a, k) => a + k.count, 0);
```

## Time and Fee, Two Ends of the Same Dial

- **ST34** — The fictional audit request covers one province's three years of invoice documents and
  field photos; counts and sizes are written into the model.

```js
// archive/retrieval.mjs — retrieval speeds are scanned: time and fee are counted together
import { SPEED, BATCH, CONCURRENT, EGRESS, gib, auditBytes, auditCount, AUDIT } from "./model.mjs";

const g = (x, n, d = 2) => x.toFixed(d).padStart(n);
const s = (x, n) => String(x).padStart(n);
const auditGib = gib(auditBytes), batches = Math.ceil(auditCount / BATCH), rounds = Math.ceil(batches / CONCURRENT);
console.log(`fictional audit request: ${Object.entries(AUDIT).map(([a, k]) => `${k.count} ${a}`).join(" + ")} = ` +
  `${auditCount.toLocaleString("en-US")} objects, ${g(auditGib, 0, 1)} GiB.`);
console.log(`the request is split into ${BATCH}-object batches: ${batches} batches, ${CONCURRENT} at once, ${rounds} rounds.`);
console.log("\nspeed  | first resp | stream GiB/hr | one batch (hr) | total wait (hr)  | retrieval fee");
console.log("-------|------------|---------------|----------------|------------------|--------------");
const FEE = {}, WAIT = {};
for (const [name, sp] of Object.entries(SPEED)) {
  const batchGib = auditGib / batches, roundHours = sp.firstHour + (batchGib * CONCURRENT) / sp.gibPerHour;
  const fee = FEE[name] = auditGib * sp.perGib + auditCount * sp.request;
  WAIT[name] = rounds * roundHours;
  console.log(`${name.padEnd(6)} | ${g(sp.firstHour, 10, 2)} | ${s(sp.gibPerHour, 13)} | ${g(sp.firstHour + batchGib / sp.gibPerHour, 14, 2)} | ` +
    `${g(WAIT[name], 16, 1)} | ${g(fee, 13, 2)}`);
}
console.log(`same request: wait shrinks ${(WAIT.slow / WAIT.fast).toFixed(0)}x, fee ` +
  `grows ${(FEE.fast / FEE.slow).toFixed(1)}x. Time and fee are two ends of the same dial.`);
console.log(`the request fee dominates: ${auditCount.toLocaleString("en-US")} objects at slow speed cost ` +
  `${g(auditCount * SPEED.slow.request, 0, 2)} units, the byte fee for the same bytes is ${g(auditGib * SPEED.slow.perGib, 0, 2)} units.`);
console.log("\nonce retrieval finishes, egress is paid separately (same bytes):");
for (const [where, rate] of Object.entries(EGRESS))
  console.log(`  ${where.padEnd(15)} ${g(auditGib * rate, 7)} units`);
console.log(`downloading to the internet costs ${g(auditGib * EGRESS.internet / FEE.slow, 0, 1)}x the cheapest retrieval: ` +
  `archive's cheapness only lasts until the data leaves.`);

// Decision: the cheapest speed that meets a given response-time commitment. What narrowing the request changes.
const solve = (count, bytes, limitHours) => {
  const G = gib(bytes), p = Math.ceil(count / BATCH), t = Math.ceil(p / CONCURRENT);
  let best = null;
  for (const [name, sp] of Object.entries(SPEED)) {
    const hours = t * (sp.firstHour + (G / p * CONCURRENT) / sp.gibPerHour), fee = G * sp.perGib + count * sp.request;
    if (hours <= limitHours && (best === null || fee < best.fee)) best = { name, hours, fee };
  }
  return best;
};
console.log("\ncheapest speed that meets a response-time commitment:");
console.log("commitment | full request (126,000 objects)    | invoices only (46,000 objects)");
console.log("-----------|-----------------------------------|-------------------------------");
for (const limit of [1, 6, 24, 72]) {
  const a = solve(auditCount, auditBytes, limit);
  const f = solve(AUDIT.invoice.count, AUDIT.invoice.count * AUDIT.invoice.kib * 1024, limit);
  const format = (x) => x ? `${x.name.padEnd(6)} ${g(x.hours, 5, 1)} hr ${g(x.fee, 7)} units` : "infeasible";
  console.log(`${s(limit + " hr", 10)} | ${format(a).padEnd(33)} | ${format(f)}`);
}
```

```
fictional audit request: 46000 invoice + 80000 photo = 126,000 objects, 297.9 GiB.
the request is split into 10000-object batches: 13 batches, 4 at once, 4 rounds.

speed  | first resp | stream GiB/hr | one batch (hr) | total wait (hr)  | retrieval fee
-------|------------|---------------|----------------|------------------|--------------
slow   |      12.00 |           250 |          12.09 |             49.5 |          4.52
medium |       4.00 |           600 |           4.04 |             16.6 |         10.54
fast   |       0.08 |          1500 |           0.10 |              0.6 |         24.06
same request: wait shrinks 86x, fee grows 5.3x. Time and fee are two ends of the same dial.
the request fee dominates: 126,000 objects at slow speed cost 3.78 units, the byte fee for the same bytes is 0.74 units.

once retrieval finishes, egress is paid separately (same bytes):
  in-region          0.00 units
  cross-region       5.96 units
  internet          25.32 units
downloading to the internet costs 5.6x the cheapest retrieval: archive's cheapness only lasts until the data leaves.

cheapest speed that meets a response-time commitment:
commitment | full request (126,000 objects)    | invoices only (46,000 objects)
-----------|-----------------------------------|-------------------------------
      1 hr | fast     0.6 hr   24.06 units     | fast     0.2 hr    6.02 units
      6 hr | fast     0.6 hr   24.06 units     | fast     0.2 hr    6.02 units
     24 hr | medium  16.6 hr   10.54 units     | medium   8.0 hr    2.93 units
     72 hr | slow    49.5 hr    4.52 units     | slow    24.1 hr    1.42 units
```

The audit request is 126,000 objects and 297.9 GiB. None of the three speeds is wrong; their
difference collects into two columns. At slow speed the total wait is 49.5 hours — more than two
days — and the retrieval fee is 4.52 units. At fast speed the wait drops to 0.6 hours, the fee rises
to 24.06 units. The wait shrinks 86 times, the fee grows 5.3 times. In an audit request the real
decision is not which speed is "good," it is **how many hours of waiting is acceptable**; without
that answer, no speed can be chosen.

The reason the total wait exceeds the first-response latency is the batch calculation. The request is
split into 10,000-object batches: 126,000 objects makes 13 batches, four batches are processed at
once, so four rounds are needed. At slow speed a single batch takes 12.09 hours, but the total is
49.5 hours. **First-response latency is paid again on every round**, and as object count grows the
wait grows not with bytes but with rounds.

The line below shows the price side of the same structure: 126,000 objects' request fee at slow
speed is 3.78 units, while the same bytes' byte fee is 0.74 units. Four-fifths of the retrieval bill
comes **from object count**. This is archive's counterpart to the rule from the first and second
lessons — small, numerous objects cost through the request fee at every tier.

Egress is a separate item. Retrieval makes data readable; it does not **move** it anywhere. The same
297.9 GiB incurs no egress fee if it stays in the region, costs 5.96 units across regions, and 25.32
units downloaded to the internet. Downloading to the internet is 5.6 times the cheapest retrieval:
archive's cheapness lasts only until the data leaves.

The last table ties the decision to a response-time commitment and reduces the choice to a single
line. At a one-hour commitment only the fast speed suffices, costing 24.06 units; at a
twenty-four-hour commitment the medium speed drops to 10.54 units; at a seventy-two-hour commitment
the slow speed suffices at 4.52 units. The right column opens a second lever: when the audit request
is narrowed to only invoice documents, both time and fee drop at every commitment — 2.93 units
instead of 10.54 units at the twenty-four-hour commitment. **Narrowing the request is cheaper than
raising the speed;** the first thing to measure in archive is what actually needs to be retrieved.

## Minimum Retention Period

- **ST35** — The archive tier's minimum retention period is 180 days, and it starts the moment the
  object enters the class.
- **ST36** — The lifecycle rule transitions the object to archive on day 30 and deletes it when its
  lifetime ends.

```js
// archive/minimum.mjs — minimum retention: an object deleted early is still billed for the full period
import { CLASS, gib } from "./model.mjs";

const DAYS_PER_MONTH = 30, archive = CLASS.archive, frequent = CLASS.frequent;
const minMonths = archive.minDays / DAYS_PER_MONTH;
// Two families, two lifetimes: a small, short-lived document and a large, long-lived photo.
const FAMILIES = [
  { name: "invoice-documents", count: 86_000, kib: 380, lifeMonths: 3 },
  { name: "field-photos", count: 42_000, kib: 3686, lifeMonths: 60 },
];
const TRANSITION_MONTH = 1;                          // the rule moves to archive on day 30
const g = (x, n, d = 2) => x.toFixed(d).padStart(n);
const s = (x, n) => String(x).padStart(n);
console.log(`the archive tier's minimum retention period is ${archive.minDays} days = ${minMonths} months. ` +
  `The rule transitions to archive in month ${TRANSITION_MONTH}, deletes when the lifetime ends.`);
console.log("\nfamily            |  life | in archive | billed | transition fee  | no archive | archive | ratio");
console.log("------------------|-------|------------|--------|-----------------|------------|---------|------");
for (const f of FAMILIES) {
  const dataGib = gib(f.count * f.kib * 1024), inArchive = f.lifeMonths - TRANSITION_MONTH;
  const billed = Math.max(inArchive, minMonths);
  const transitionFee = f.count * archive.transition;
  const withoutArchive = dataGib * frequent.storage * f.lifeMonths;
  const withArchive = dataGib * frequent.storage * TRANSITION_MONTH + transitionFee + dataGib * archive.storage * billed;
  console.log(`${f.name.padEnd(17)} | ${s(f.lifeMonths + " mo", 5)} | ${s(inArchive + " mo", 10)} | ${s(billed + " mo", 6)} | ` +
    `${g(transitionFee, 15)} | ${g(withoutArchive, 10)} | ${g(withArchive, 7)} | ${g(withArchive / withoutArchive, 5, 2)}`);
}

// Early deletion: even if the object is deleted on day 45, the 180-day debt stands.
const photo = FAMILIES[1], dataGib = gib(photo.count * photo.kib * 1024), DELETE_DAY = 45;
const used = DELETE_DAY / DAYS_PER_MONTH, owed = minMonths - used;
console.log(`\nearly deletion: ${photo.count.toLocaleString("en-US")} photos moved to archive, deleted on day ${DELETE_DAY}.`);
console.log(`  used ${g(used, 0, 1)} months, billed ${minMonths} months, paid for ${g(owed, 0, 1)} idle months.`);
console.log(`  the bill for deleting on day ${DELETE_DAY} is ${g(dataGib * archive.storage * minMonths, 0, 3)} units; ` +
  `never deleting and holding it for ${minMonths} months bills the same ${g(dataGib * archive.storage * minMonths, 0, 3)} units.`);
console.log(`  the ${g(owed, 0, 1)} months in between, ${g(dataGib * archive.storage * owed, 0, 3)} units, are paid for objects ` +
  `that no longer exist: early deletion gains nothing.`);
console.log(`archive is not a storage class for data that will live under ${minMonths} months, it is a debt paid up front.`);
```

```
the archive tier's minimum retention period is 180 days = 6 months. The rule transitions to archive in month 1, deletes when the lifetime ends.

family            |  life | in archive | billed | transition fee  | no archive | archive | ratio
------------------|-------|------------|--------|-----------------|------------|---------|------
invoice-documents |  3 mo |       2 mo |   6 mo |            4.30 |       2.15 |    5.20 |  2.42
field-photos      | 60 mo |      59 mo |  59 mo |            2.10 |     203.74 |   14.21 |  0.07

early deletion: 42,000 photos moved to archive, deleted on day 45.
  used 1.5 months, billed 6 months, paid for 4.5 idle months.
  the bill for deleting on day 45 is 0.886 units; never deleting and holding it for 6 months bills the same 0.886 units.
  the 4.5 months in between, 0.664 units, are paid for objects that no longer exist: early deletion gains nothing.
archive is not a storage class for data that will live under 6 months, it is a debt paid up front.
```

The table applies a single rule to two families and gets opposite results. Field photos live sixty
months; the fifty-nine months spent in archive are far above the minimum retention period, so the
billed period is also fifty-nine months. The family that costs 203.74 units without archive drops to
14.21 units with it — a ratio of 0.07, a fourteenfold gain. Invoice documents live three months; they
spend two months in archive but are **billed for six months**. The family that costs 2.15 units
without archive rises to 5.20 units with it: a 2.42x loss. Same rule, same tier, opposite result. Two
things drive the difference — the object's lifetime and the object's size. The transition fee for
86,000 small documents alone is 4.30 units, twice the archive-free total.

The early-deletion block names the item. Forty-two thousand photos are placed in archive and deleted
on day forty-five: 1.5 months used, 6 months billed. The bill for deleting is 0.886 units, and the
bill for never deleting and holding it six months is also 0.886 units — identical. The 0.664 units
paid for the 4.5 months in between are for objects that no longer exist. **The minimum retention
period is this lesson's unrecoverable item:** the debt is born the moment the object enters the
class, and neither
deletion nor a class change closes it.

The delegated-decision side is at its widest here. In the archive tier, durability, placement,
storage medium, repair, and keeping the data readable all belong entirely to the provider; none of it
is visible, none of it has a dial. What stays comes down to three numbers: which object enters
archive and when, how long it lives, and at what speed it gets retrieved when needed. All three are
decided before the data is written, because all three are paid for afterward.

## Summary

- Storage in the archive tier is cheap, but data cannot be read right away: the same audit request
  waits 49.5 hours and costs 4.52 units at slow speed, waits 0.6 hours and costs 24.06 units at fast
  speed.
- The wait grows with rounds, not bytes: 126,000 objects split into 10,000-object batches make four
  rounds, and first-response latency is paid again on every round.
- Four-fifths of the retrieval bill comes from object count: 126,000 objects' request fee is 3.78
  units, the same bytes' byte fee is 0.74 units.
- Retrieval makes data readable, it does not move it: the same 297.9 GiB, downloaded to the internet,
  adds a 25.32-unit egress fee on top of retrieval, 5.6 times the cheapest retrieval.
- The minimum retention period is an unrecoverable item: archive gains fourteen times for photos that
  live sixty months, and costs 2.42 times more for documents that live three months; an object
  deleted on day forty-five still pays for six months.

## Next Step

Six lessons measured **where** data goes: object, block, shared file, and archive. None of them asked
a single question — who can read this data. In the fictional measurement network, field teams open
photos from their phones, auditors request invoice documents from outside, and neither group has
bucket access. Granting an identity opens a permanent door; what is usually wanted, though, is a
**time-limited and narrow** door. The next lesson measures this: a presigned URL is generated, and
its expiry and scope are actually verified. Three things will be measured — the regeneration load
that grows as the link's lifetime shortens, the leak window that grows as it lengthens, and the
object count a single link opens as scope widens.
