---
title: 'Deployment Stamps and Geo-Replicas'
source: 'https://academia.sh/en/courses/resilience-patterns/deployment-stamps-and-geo-replicas'
course: 'Resilience and Reliability'
language: en
updated: '2026-08-23T07:01:31+00:00'
license: 'CC BY-SA 4.0'
---

# Deployment Stamps and Geo-Replicas

Splitting the service into self-contained, replicable units: counting the links that cross the stamp boundary, comparing each component's blast radius across three placements, showing that the scope of the single resource shared across stamps stays independent of stamp count, and converting the load per stamp into K01 numbers.

The previous lesson spread replicas across zones and regions, but what got spread was always the
same whole: one service, one dataset. Surviving capacity coming out to 66.7 percent or 50 percent
at the loss of one unit assumed that the lost unit carried a **fraction** of the service. In most
arrangements this assumption does not hold: the units keep something in common, and the loss of
that point takes all of them at once.

A **deployment stamp** is a self-contained, replicable copy of the service: the application, its
store, its cache, and its queue together. The same structure is called a **scale unit** when it is
used as a scaling unit — adding capacity means opening a new stamp. The term is not confused with
the `version stamp` from the Application Layer and Service Interaction course; that is a marker
showing which version a record was written in, while this is a deployment unit.

## What the Stamp Boundary Is

A stamp is self-contained only if no link crosses its boundary. Every link that crosses the
boundary requires something outside the stamp to stay up, and when that thing goes down, the
stamp's independence ends. This is why this lesson's first measure is **the number of links
crossing the stamp**.

The setup below is a **model**: nine components, three flows, and three placements. Components are
tagged as stamp-local or shared across stamps; a flow is the list of components it touches in
order. No real cluster, region, or container is set up.

```js
// stamp/model.mjs — a deployment stamp is a MODEL: components are tagged as stamp-local or
// shared across stamps, flows are component arrays. No real cluster is set up.
export const STAMPS = 4;
export const FLOW = {                          // components each flow touches, in order
  tracking: ["routing", "edge", "tracking-service", "cache", "state-store"],
  event: ["routing", "edge", "event-queue", "state-store", "number-dispenser"],
  billing: ["billing-job", "state-store", "tariff-store"],
};
export const LOAD = { tracking: 8, event: 2, billing: 1 };   // requests per round, close to K01's ratio

const ALL = [...new Set(Object.values(FLOW).flat())];
export const PLACEMENT = {                      // anything not marked shared is stamp-local
  "single-piece": ALL,
  "shared-stamp": ["routing", "cache", "number-dispenser", "tariff-store"],
  "self-contained": ["routing"],
};
export const COMPONENTS = ALL;

export const scope = (placement, c) => (PLACEMENT[placement].includes(c) ? "shared" : "stamp");

export function stampCount(placement) {         // the single-piece arrangement has no stamp concept
  return placement === "single-piece" ? 1 : STAMPS;
}

export function crossingLinks(placement) {      // how many times the stamp boundary is crossed
  let n = 0;
  for (const path of Object.values(FLOW))
    for (let i = 0; i + 1 < path.length; i += 1)
      if (scope(placement, path[i]) !== scope(placement, path[i + 1])) n += 1;
  return n;
}

export function blastRadius(placement, downComponent) {   // requests and stamps affected when one component drops
  const d = stampCount(placement);
  const total = Object.values(LOAD).reduce((a, b) => a + b, 0) * d;
  let affected = 0;
  for (const [name, path] of Object.entries(FLOW)) {
    if (!path.includes(downComponent)) continue;
    affected += scope(placement, downComponent) === "shared" ? LOAD[name] * d : LOAD[name];
  }
  const stamps = scope(placement, downComponent) === "shared" ? d : 1;
  return { affected, total, ratio: affected / total, stamps, d };
}
```

```js
// stamp/measure.mjs — crossing links in three placements and each component's blast radius
import { FLOW, PLACEMENT, COMPONENTS, crossingLinks, blastRadius, stampCount, scope } from "./model.mjs";

const s = (x, n) => String(x).padStart(n);
console.log(`${COMPONENTS.length} components, ${Object.keys(FLOW).length} flows, ` +
  `8 tracking + 2 event + 1 billing request per stamp.`);
console.log();
console.log(`${"placement".padEnd(15)} | ${s("stamps", 6)} | ${s("shared components", 18)} | ${s("stamp-local", 11)} | ${s("crossing links", 14)}`);
console.log(`${"-".repeat(15)}-|-${"-".repeat(6)}-|-${"-".repeat(18)}-|-${"-".repeat(11)}-|-${"-".repeat(14)}`);
for (const p of Object.keys(PLACEMENT)) {
  const shared = COMPONENTS.filter((c) => scope(p, c) === "shared").length;
  console.log(`${p.padEnd(15)} | ${s(stampCount(p), 6)} | ${s(shared, 18)} | ` +
    `${s(COMPONENTS.length - shared, 11)} | ${s(crossingLinks(p), 14)}`);
}

console.log();
console.log(`${"component down".padEnd(16)} | ${s("single-piece", 15)} | ${s("shared-stamp", 16)} | ${s("self-contained", 14)}`);
console.log(`${"-".repeat(16)}-|-${"-".repeat(15)}-|-${"-".repeat(16)}-|-${"-".repeat(14)}`);
for (const c of COMPONENTS) {
  const cell = Object.keys(PLACEMENT).map((p) => {
    const r = blastRadius(p, c);
    return `${(100 * r.ratio).toFixed(1)}% (${r.stamps}/${r.d})`;
  });
  console.log(`${c.padEnd(16)} | ${s(cell[0], 15)} | ${s(cell[1], 16)} | ${s(cell[2], 14)}`);
}
```

```
9 components, 3 flows, 8 tracking + 2 event + 1 billing request per stamp.

placement       | stamps |  shared components | stamp-local | crossing links
----------------|--------|--------------------|-------------|---------------
single-piece    |      1 |                  9 |           0 |              0
shared-stamp    |      4 |                  4 |           5 |              6
self-contained  |      4 |                  1 |           8 |              2

component down   |    single-piece |     shared-stamp | self-contained
-----------------|-----------------|------------------|---------------
routing          |     90.9% (1/1) |      90.9% (4/4) |    90.9% (4/4)
edge             |     90.9% (1/1) |      22.7% (1/4) |    22.7% (1/4)
tracking-service |     72.7% (1/1) |      18.2% (1/4) |    18.2% (1/4)
cache            |     72.7% (1/1) |      72.7% (4/4) |    18.2% (1/4)
state-store      |    100.0% (1/1) |      25.0% (1/4) |    25.0% (1/4)
event-queue      |     18.2% (1/1) |       4.5% (1/4) |     4.5% (1/4)
number-dispenser |     18.2% (1/1) |      18.2% (4/4) |     4.5% (1/4)
billing-job      |      9.1% (1/1) |       2.3% (1/4) |     2.3% (1/4)
tariff-store     |      9.1% (1/1) |       9.1% (4/4) |     2.3% (1/4)
```

## What the Blast Radius Is a Result Of

The first table gives stamping's measure on paper. The single-piece arrangement has no stamp
boundary, so it has no crossing link either — but it also has no independent unit. When four stamps
are opened, the shared component count drops to four and the crossing link count becomes 6; once
the shared components are moved into the stamp, the crossing link count drops to 2. **Stamp count
does not give independence; shared component count does.**

The second table counts the counterpart of this for each component separately. For stamp-local
components, the scope is divided by four: the edge component's loss goes from 90.9 percent to 22.7
percent, the state store's loss goes from 100 percent to 25 percent, and the affected stamp count
drops from 4 to 1. This column is the pattern's promise.

For shared components, stamping does nothing. When the cache stays shared, its loss is the same as
the single-piece arrangement: 72.7 percent and 4/4 stamps. Moved into the stamp, it becomes 18.2
percent and 1/4. The same pattern holds for the number dispenser (18.2 percent to 4.5 percent) and
the tariff store (9.1 percent to 2.3 percent). **As long as a component stays outside the stamp,
increasing stamp count does not change its blast radius.**

The routing map row is the same across all three columns: 90.9 percent and 4/4. This is the single
source that knows which shipment is in which stamp, and it cannot be moved into a stamp — if it
were, there would be no way to know which stamp a request should go to. This is the pattern's
limit: **stamping does not zero out the blast radius, it reduces it to the single point that
remains shared.** That point's service availability is the upper bound for the whole system, and
K01's weakest-link rule applies here without modification.

## What Stamp Count Changes

```js
// stamp/load.mjs — converting the stamp count into K01 loads and the failure-free day's cost
const PEAK_EDGE = 513.89;     // K01: peak requests/s at the edge
const PEAK_WRITE = 97.22;     // K01: peak write requests/s
const STORE_REQ = 138.89;     // K01: requests/s reaching the store
const SHIPMENTS_DAY = 400_000; // K01: daily shipments
const STAMP_LOCAL = 8;        // from the model: stamp-local component count in the self-contained placement
const b = (x, n = 2) => x.toFixed(n);

console.log(`${"stamps".padStart(6)}${"edge per stamp".padStart(17)}${"store per stamp".padStart(19)}` +
  `${"writes per stamp".padStart(20)}${"blast radius".padStart(17)}${"daily shipments".padStart(16)}`);
for (const n of [1, 2, 4, 8, 16]) {
  console.log(`${String(n).padStart(6)}${b(PEAK_EDGE / n).padStart(17)}${b(STORE_REQ / n).padStart(19)}` +
    `${b(PEAK_WRITE / n).padStart(20)}${(b(100 / n) + "%").padStart(17)}` +
    `${(SHIPMENTS_DAY / n).toLocaleString("en-US").padStart(16)}`);
}

console.log(`\n${"stamps".padStart(7)}${"total components".padStart(18)}${"with geo-replicas".padStart(19)}` +
  `${"stamps touched by billing".padStart(30)}`);
for (const n of [1, 2, 4, 8, 16]) {
  const components = STAMP_LOCAL * n + 1;        // +1: the shared routing map
  console.log(`${String(n).padStart(7)}${String(components).padStart(18)}` +
    `${String(STAMP_LOCAL * n * 2 + 1).padStart(19)}${String(n).padStart(30)}`);
}
console.log(`the shared single resource's blast radius is independent of stamp count: always 90.9%`);
```

```
stamps   edge per stamp    store per stamp    writes per stamp     blast radius daily shipments
     1           513.89             138.89               97.22          100.00%         400,000
     2           256.94              69.44               48.61           50.00%         200,000
     4           128.47              34.72               24.30           25.00%         100,000
     8            64.24              17.36               12.15           12.50%          50,000
    16            32.12               8.68                6.08            6.25%          25,000

 stamps  total components  with geo-replicas     stamps touched by billing
      1                 9                 17                             1
      2                17                 33                             2
      4                33                 65                             4
      8                65                129                             8
     16               129                257                            16
the shared single resource's blast radius is independent of stamp count: always 90.9%
```

The first table is stamping's scaling face: at four stamps, the edge load per stamp is 128.47
requests/s, the load reaching the store is 34.72 requests/s, and the daily shipment count is
100,000. All of K01's calculations become re-readable per stamp, and once a single stamp's
capacity is known, the number of stamps needed follows by division. The blast radius column is the
same division's resilience face, and the two are the same number — the scale unit and the failure
isolation unit coincide here.

The second table gives **the failure-free day's cost**. Total component count grows linearly with
stamp count: 9 at one stamp, 129 at sixteen stamps. If a **geo-replica** of each stamp is kept in a
second region, the count doubles: 257. A geo-replica is the whole stamp replicated in another
region; the mechanics of replication were established in the Scaling the Data Layer course and are
not repeated here — the novelty here is that what gets replicated is not a record but a
**deployment unit**.

The last column is stamping's least visible cost. End-of-day billing must touch every shipment, and
shipments are split across stamps; at sixteen stamps, the job has to go to sixteen places and merge
the results. While the load per stamp shrinks, distribution is a cost for work that **has to
cross** the stamp. This job depends on K01's four-hour window, and the window does not change; what
changes is how many pieces the job is assembled from.

## The Numbers for Two Days

**The failure-free day's cost** is three line items. Component count climbs from 9 to 33 (four
stamps), to 65 with geo-replicas — each one a unit set up, upgraded, and monitored separately.
End-of-day billing goes to four places instead of one. And a query that concerns two stamps at once
becomes an operation with no place in the design, because it crosses the stamp boundary; in the
self-contained placement, only 2 links remained crossing the boundary, and both of them went to the
routing map.

**The failing day's gain** is in the second table's stamp-local rows. The same failure — the state
store's loss — affects 100 percent of requests in the single-piece arrangement, 25 percent in the
four-stamp arrangement, and 6.25 percent in the sixteen-stamp arrangement. The edge component's
loss goes from 90.9 percent to 22.7 percent. A failure's duration does not change; what changes is
how many users are affected during that duration, and K01's request-based availability measure
counts exactly that.

Comparing the two days also gives a limit. Increasing stamp count shrinks the blast radius by $1/n$
while growing component count by $n$. The single shared resource's scope, though, stays at 90.9
percent and never shrinks; the pattern's gain is limited to the share left above this floor.

## Summary

- A deployment stamp is a self-contained, replicable copy of the service; it is called a scale unit
  when used as a scaling unit, and it is a separate concept from the version stamp.
- The measure of independence is not stamp count but the number of links crossing the stamp: 6 in
  the four-stamp arrangement with shared components, 2 once the shared components are moved into
  the stamp.
- For stamp-local components, blast radius is divided by four (the state store goes from 100
  percent to 25 percent); for shared components, it does not change at all (the cache stays at 72.7
  percent, 4/4 stamps).
- The routing map cannot be moved into a stamp, and its scope is 90.9 percent across all three
  placements: stamping does not zero out the blast radius, it reduces it to the single point that
  remains shared.
- Stamp count directly divides K01's loads: at four stamps, 128.47 requests/s edge load, 34.72
  requests/s store load, and 100,000 daily shipments per stamp; blast radius is the same division.
- The failure-free day's cost is component count: from 9 to 33 at four stamps, to 65 with
  geo-replicas; end-of-day billing also has to go to as many places as there are stamps.

## Next Step

This topic built a recovery arrangement over five lessons: the failover threshold and failback
policy, the health endpoint's content and interval, the recovery time and data loss objectives,
zone and region placement, stamps and geo-replicas. Every lesson's numbers came from a model or
arithmetic, and all of them assumed one thing: **the plan will work as written.** The failover
threshold will trigger correctly, the backup replica will really be current, the second region will
carry the load, every component in the stamp will really sit inside the stamp. None of these
assumptions has been tested. The next lesson tests the plan with a controlled failure trial: how
much room the drill itself takes from the outage budget, what the gap is between the targeted
recovery time and what the drill measures, and which assumptions an untested arrangement's first
real failure breaks all at once.
