Skip to content
academia.sh

Lesson 08 / 14

Managed Relational Database

An item-by-item comparison of a self-run relational database against the managed service: how many of 28 decisions go to the provider, how many stay with you, and how many become inaccessible altogether; how many of the kept items turn out to be one-way; the failover window split into the provider's fixed share and the application's share, with dropped requests measured across five cache lifetimes; and the data's exit costed as logical export time plus egress fee.

Published Updated

In this lesson

  • Splits 28 decisions in a managed relational database into three sets — delegated, kept, and inaccessible — and counts each
  • Separates which kept decisions cannot be reversed and places the major version upgrade and collation choice in that set
  • Splits the failover outage into the provider's fixed share and the application's variable share, and compares dropped requests across five settings
  • Computes how the snapshot format's inaccessibility forces the exit into a logical export, as time and egress fee
Contents

The previous topic settled where objects go and who can reach them, and for how long, with numbers. One thing was left unasked: the queried data — where meter readings are summarized, work orders change state, and billing documents are linked — still sits in a relational database set up by hand. That database’s machine may be rented, but the person taking the backup, applying the patch, and promoting the standby to primary during an outage is still a person.

This lesson’s question is not “is the managed service better.” What gets measured is narrower and more concrete: when the database is rented, which decisions change hands. A decision can go to one of three places — it is delegated to the provider, it stays with you, or it lands with no one at all: it becomes inaccessible. That third set is this lesson’s real subject, because a database you run yourself has no such set.

The Decision Splits into Three Sets

In a database you run by hand, all 28 items are kept: which disk the hardware runs on, what the kernel setting is. The managed service splits this list. Delegated items are now the provider’s job, and the provider answers for the outcome. Kept items remain as settings; you can change them. Inaccessible items are neither delegated nor kept: they have been removed from the settings surface, the provider has picked a number, and that number is not visible.

This split is not abstract. On an outage night, an item in the inaccessible column decides how long the night runs, and that item cannot be touched.

The catalog was built on a single criterion: what it takes to change the item. If a request changes it, it is counted as kept; if a provider notification changes it, it is counted as delegated; if nothing changes it, it is counted as inaccessible. The line is thin for some items — backup frequency is kept but how the backup is taken is delegated, growing storage is kept but shrinking it is inaccessible. This is not a flaw in the catalog; it is the thing being measured: a managed service does not hand over decisions as one block, it splits every item on its own.

Mechanics

MD1 — the fictional regional measurement network’s work-order database is 220 GB and takes 1,400 requests a second. All numbers are fictional. MD2 — the health check runs every 5 seconds, three consecutive failed checks trigger a failover, and the first check lands 2.4 seconds after the fault. MD3 — promoting the replica to primary takes 20 seconds. MD4 — on the application side, the endpoint name’s resolver cache lives for T seconds; the renewal moments of sixteen application instances are spread evenly across the T interval. MD5 — each instance carries a pool of 120 connections, one connection is established in 25 ms, and eight connections can be established concurrently. MD6 — the egress fee is a fictional unit: 0 within a region, 2 across regions, 9 units/GB to the internet. MD2 and MD3 sit on the provider’s side, MD4 and MD5 on the application’s; the measurement is built to expose this line.

// md/model.mjs — MANAGED RELATIONAL DATABASE model. The decision catalog and failover
// timeline defined over the fictional regional measurement network's work-order database.
// Not a real product or measurement; there is no randomness, every run yields the same numbers.
// Third column: who holds the decision in the managed service. Fourth column: is it reversible.
export const DECISION = [
  ["hardware selection and placement", "delegated", true],
  ["operating system patching", "delegated", true],
  ["minor version patching", "delegated", true],
  ["backup format", "delegated", true],
  ["snapshot storage", "delegated", true],
  ["point-in-time restore infrastructure", "delegated", true],
  ["buffer pool size", "delegated", true],
  ["replication layout", "delegated", true],
  ["major version upgrade decision", "kept", false],
  ["patch window", "kept", true],
  ["backup frequency", "kept", true],
  ["backup retention period", "kept", true],
  ["manual failover trigger", "kept", true],
  ["connection limit", "kept", true],
  ["planner cost settings", "kept", true],
  ["installing a listed extension", "kept", true],
  ["storage growth", "kept", false],
  ["placement region selection", "kept", false],
  ["character set and collation", "kept", false],
  ["shell access", "inaccessible", null],
  ["file system layout", "inaccessible", null],
  ["data directory path", "inaccessible", null],
  ["failover threshold", "inaccessible", null],
  ["write-ahead log threshold", "inaccessible", null],
  ["extension not on the list", "inaccessible", null],
  ["storage shrink", "inaccessible", null],
  ["direct access to log files", "inaccessible", null],
  ["kernel parameter", "inaccessible", null],
];

// Failover timeline. The provider's two steps are fixed; the third step depends on the
// application's resolver-cache lifetime (T) and VARIES PER INSTANCE.
export const HEALTH_INTERVAL = 5, HEALTH_PHASE = 2.4, THRESHOLD = 3, UPGRADE = 20;
export const INSTANCES = 16, REQUESTS = 1400, POOL = 120, CONNECT_MS = 25, CONCURRENT = 8;

export const detect = () => HEALTH_PHASE + (THRESHOLD - 1) * HEALTH_INTERVAL;
export const poolSetup = () => (POOL * CONNECT_MS) / CONCURRENT / 1000;

// Each application instance renews its own cache at a different moment, spread evenly
// across T. An instance learns the new endpoint at the FIRST renewal after the upgrade ends.
export function failover(T) {
  const readiness = detect() + UPGRADE;
  const instances = [];
  for (let i = 0; i < INSTANCES; i += 1) {
    const phase = (i * T) / INSTANCES;
    const learn = phase + T * Math.max(0, Math.ceil((readiness - phase) / T));
    instances.push(learn + poolSetup());
  }
  const dropped = instances.reduce((t, x) => t + x * (REQUESTS / INSTANCES), 0);
  return { readiness, worst: Math.max(...instances),
    average: instances.reduce((a, b) => a + b, 0) / INSTANCES, dropped: Math.round(dropped) };
}
// md/measure.mjs — delegated decision count and the failover window
import { DECISION, failover, detect, poolSetup, INSTANCES, REQUESTS, POOL, UPGRADE } from "./model.mjs";

const s = (x, n) => String(x).padStart(n);
const count = (k) => DECISION.filter(([, v]) => v === k).length;
console.log(`decision catalog: ${DECISION.length} items. In a database you run yourself, all are kept.\n`);
console.log("in the managed service  | items | share | example");
console.log("------------------------|-------|-------|--------------------------------------------");
for (const k of ["delegated", "kept", "inaccessible"]) {
  const list = DECISION.filter(([, v]) => v === k).map(([a]) => a);
  console.log(`${k.padEnd(23)} | ${s(list.length, 5)} | ` +
    `${s((100 * list.length / DECISION.length).toFixed(1) + "%", 5)} | ${list.slice(0, 2).join(", ")}`);
}
const irreversible = DECISION.filter(([, v, g]) => v === "kept" && g === false);
console.log(`\nof the ${count("kept")} kept items, ${irreversible.length} are one-way:`);
for (const [a] of irreversible) console.log(`  - ${a}`);
console.log(`${count("inaccessible")} inaccessible items are settings when you run it yourself; here there are none.\n`);

console.log(`failover: health check detection ${detect().toFixed(1)} s, upgrade ` +
  `${UPGRADE} s -> provider share ${(detect() + UPGRADE).toFixed(1)} s (fixed).`);
console.log(`pool setup ${(poolSetup() * 1000).toFixed(0)} ms, ${INSTANCES} instances x ${POOL} ` +
  `connections = ${INSTANCES * POOL} new connections hit the new primary at once.`);
console.log(`request rate ${REQUESTS}/s; all of it drops during the failover.\n`);
console.log("cache TTL T     | provider share | app share     | outage (avg)  | outage (worst)    | dropped req");
console.log("----------------|----------------|---------------|---------------|-------------------|-------------");
for (const T of [5, 15, 30, 60, 120]) {
  const r = failover(T);
  console.log(`${s(T + " s", 15)} | ${s(r.readiness.toFixed(1) + " s", 14)} | ` +
    `${s((r.average - r.readiness).toFixed(1) + " s", 13)} | ${s(r.average.toFixed(1) + " s", 13)} | ` +
    `${s(r.worst.toFixed(1) + " s", 17)} | ${s(r.dropped, 11)}`);
}
const a = failover(5), b = failover(120);
console.log(`\nrun-independent: provider share fixed at ${a.readiness.toFixed(1)} s; as T goes ` +
  `from 5 to 120, outage rises ${a.average.toFixed(1)} -> ${b.average.toFixed(1)} s, dropped requests ` +
  `${a.dropped} -> ${b.dropped}.`);
console.log(`the failover threshold is inaccessible, so detection time cannot be shortened; the only ` +
  `thing that can be shortened is T on the application side.`);

// Exit: because the snapshot format is inaccessible, data can leave only through a LOGICAL export.
const SIZE_GB = 220, READ_MBS = 40, LOAD_MBS = 26, RATE = { "within region": 0, "cross-region": 2, "internet": 9 };
const hours = (mbs) => (SIZE_GB * 1024) / mbs / 3600;
console.log(`\nexit: ${SIZE_GB} GB of work-order data, logical export at ${READ_MBS} MB/s, ` +
  `load at ${LOAD_MBS} MB/s (indexes are rebuilt).`);
console.log(`time: export ${hours(READ_MBS).toFixed(2)} h + load ${hours(LOAD_MBS).toFixed(2)} h = ` +
  `${(hours(READ_MBS) + hours(LOAD_MBS)).toFixed(2)} h. The snapshot cannot travel: the format sits in the inaccessible column.`);
console.log("destination      | egress fee (fictional unit/GB) | total (fictional unit)");
console.log("-----------------|--------------------------------|------------------------");
for (const [name, rate] of Object.entries(RATE))
  console.log(`${name.padEnd(16)} | ${s(rate, 30)} | ${s(rate * SIZE_GB, 20)}`);
decision catalog: 28 items. In a database you run yourself, all are kept.

in the managed service  | items | share | example
------------------------|-------|-------|--------------------------------------------
delegated               |     8 | 28.6% | hardware selection and placement, operating system patching
kept                    |    11 | 39.3% | major version upgrade decision, patch window
inaccessible            |     9 | 32.1% | shell access, file system layout

of the 11 kept items, 4 are one-way:
  - major version upgrade decision
  - storage growth
  - placement region selection
  - character set and collation
9 inaccessible items are settings when you run it yourself; here there are none.

failover: health check detection 12.4 s, upgrade 20 s -> provider share 32.4 s (fixed).
pool setup 375 ms, 16 instances x 120 connections = 1920 new connections hit the new primary at once.
request rate 1400/s; all of it drops during the failover.

cache TTL T     | provider share | app share     | outage (avg)  | outage (worst)    | dropped req
----------------|----------------|---------------|---------------|-------------------|-------------
            5 s |         32.4 s |         2.8 s |        35.2 s |            37.6 s |       49306
           15 s |         32.4 s |         7.8 s |        40.2 s |            47.3 s |       56306
           30 s |         32.4 s |        15.8 s |        48.2 s |            62.3 s |       67463
           60 s |         32.4 s |        29.9 s |        62.3 s |            90.4 s |       87150
          120 s |         32.4 s |        61.7 s |        94.1 s |           150.4 s |      131775

run-independent: provider share fixed at 32.4 s; as T goes from 5 to 120, outage rises 35.2 -> 94.1 s, dropped requests 49306 -> 131775.
the failover threshold is inaccessible, so detection time cannot be shortened; the only thing that can be shortened is T on the application side.

exit: 220 GB of work-order data, logical export at 40 MB/s, load at 26 MB/s (indexes are rebuilt).
time: export 1.56 h + load 2.41 h = 3.97 h. The snapshot cannot travel: the format sits in the inaccessible column.
destination      | egress fee (fictional unit/GB) | total (fictional unit)
-----------------|--------------------------------|------------------------
within region    |                              0 |                    0
cross-region     |                              2 |                  440
internet         |                              9 |                 1980

The Inaccessible Column Is the Second-Largest Set

The delegated count is 8, or 28.6% of the catalog. This is the column the managed service is selling, and what sits in it genuinely cuts workload: operating system patching, minor version patching, backup format, and replication layout are now someone else’s night shift.

The unexpected part is the third column: 9 items, 32.1%. These items were not delegated; they vanished. There is no shell access, no data directory path, no direct look at the log files. Some of these go unnoticed in their absence. Some are noticed the day they are lost: the failover threshold sits in this column. How many seconds the health check runs at and how many failures trigger a failover cannot be read or adjusted. The same column holds the write-ahead log threshold and an extension not on the list: if an extension the application needs is not on the provider’s list, there is no way to install it — in a database you run yourself, that is a package-install job.

The 11 kept items offer a misleading comfort. Four of them are one-way: a major version upgrade cannot be undone, storage that has grown cannot shrink, the chosen placement region does not change without moving the data, and the character-set-and- collation choice is not fixed without rewriting tables. All four are dropdown menus on the setup screen, and their consequences last the database’s whole lifetime.

What Gets Measured Is Not the Outage, but Who Owns It

Failover is the most talked-about feature of a managed service, and it is usually cited as a single number. The table shows that single number is made of two parts.

The provider’s share is 32.4 seconds in all five rows. Detection is 12.4 seconds (a 2.4-second phase plus two check intervals), the upgrade is 20 seconds. No setting changes this number, because the item that produces it sits in the inaccessible column.

The application’s share climbs from 2.8 seconds to 61.7 seconds. The whole difference comes from the endpoint name’s resolver-cache lifetime. At T = 5 seconds, the average outage is 35.2 seconds and 49,306 requests drop; at T = 120 seconds, the average is 94.1 seconds and 131,775 requests drop. Same failover, same provider, 2.67 times the loss. The worst-case column makes it even plainer: at T = 120, one application instance spends 150.4 seconds trying to connect to a dead address.

The rule that follows draws the boundary of the delegated decision: the failover itself is delegated, the moment it finishes inside the application is not. The pool row points at the same thing — when sixteen instances come back up, the new primary faces 1,920 connection requests at once; the connection limit is a kept item, and if it sits below that number, the failover turns into an outage that outlasts the failover itself.

The Exit’s Cost Is the Format Being Locked

The last block applies the course’s recurring item to this lesson. When data needs to move somewhere else, the snapshot is useless: it is taken in the delegated column and becomes unreadable in the inaccessible column. Only one path is left, logical export: 220 GB of data comes out at 40 MB/s in 1.56 hours, and loads at the destination at 26 MB/s in 2.41 hours — loading is slower than reading because indexes are rebuilt. The total is 3.97 hours, plus an egress fee: 0 within a region, 440 across regions, 1,980 fictional units over the internet.

The ratio among these three numbers stays the same for the rest of the course. Data paid no fee coming in; the same data got charged the moment it crossed a region boundary going out. The time itself is also a cost: through those 3.97 hours, either two systems write in parallel and the gap between them is closed by hand, or the work-order records freeze. That is why placement region is counted among the one-way items — changing regions means paying this four-hour window all over again.

Summary

  • Of 28 decisions, 8 were delegated to the provider, 11 stayed kept, and 9 became inaccessible; that third set does not exist in a database you run yourself.
  • Of the 11 kept items, 4 are one-way: the major version upgrade, storage growth, placement region, and collation choice are not reversed without rewriting the data.
  • 32.4 seconds of the failover outage is the provider’s fixed share; it cannot be shortened because the failover threshold is inaccessible.
  • The application’s share can climb from 2.8 to 61.7 seconds: within the same failover, dropped requests rise from 49,306 to 131,775, and the worst-case outage rises from 37.6 to 150.4 seconds.
  • Because the snapshot format is inaccessible, exiting forces a logical export: 3.97 hours for 220 GB, plus a 1,980 fictional units egress fee over the internet.

Next Step

This lesson measured under the assumption of a single primary database: no other copy could absorb the load at the moment of failover, the second machine only stood by. But most of the work-order database’s request mix is reads — meter-reading summaries, billing queries, field-photo lookups. Opening a standby copy to reads spreads the load, but that copy lags behind the primary. The next lesson scans that lag and counts how many reads come back stale at each value; it then separates out requests with a read-your-writes requirement, and measures how many requests routing them to the primary saves, and how much it adds to the primary’s load.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close