Skip to main content

Migration

Both paths live → readers move one at a time → the grant is revoked

Nothing is migrated in one step and nothing is migrated all at once. A dataset moves across on its own, and the estate is done when the last one has.

reader appowning servicedatabasecredential pathboth paths live — API published, direct SQL still workingreader switches its calls to the contractdirect SQL traffic observed at zerogrant revoked — this is what completes ita new grant can no longer be issued

The five steps​

StepDone when
01The owner publishes the contract and serves itthe API answers, and the direct path is untouched
02Readers move across, one at a timeeach reader's calls go through the contract
03Direct traffic is observed at zerothe database side shows no queries from that credential
04The grant is revokedthe credential no longer exists
05New grants cannot be issuedcredentials come from the secrets engine, to the owner only

Step 03 is measured, not asserted. "We think everything moved" and "the log shows nothing for two weeks" are different claims, and only the second one supports step 04.

Why the revoked grant is the completion criterion​

An API running beside a still-open direct path has added a component and removed no coupling. Every cost on page 02 is still being paid: the schema is still pinned by whoever is still reading it directly, the audit trail still names a connection, the reader set is still unknown.

Worse, the half-migrated state is stable. It looks like progress, it demos well, and nothing forces it forward. A migration measured in shipped APIs reaches a hundred percent of APIs and zero percent of the benefit.

So the milestone completes on the revoked grant, and the roadmap says so out loud.

Order: lowest blast radius first​

Waves are ordered by what breaks if the cutover goes wrong, smallest first. This is the same rule the IaC rebuild (item 02) and the Kubernetes migration (item 26) already follow: prove the pattern on something recoverable before the data everything depends on.

A first dataset is a good one if it has few readers, a clear owner, and no invariant that spans into another dataset.

When not to use an API​

Not everything becomes a synchronous call, and pretending otherwise is how this gets abandoned after the first serious request.

The readWhere it goesWhy
Bulk or historical — a report, a backfill, a join across millions of rowsItem 27 — the Trino warehouseAn OLTP database should not serve it and neither should an API in front of one. A paginated HTTP call fetching a million rows is a slower, more expensive table scan with extra failure modes.
Tolerates staleness — a cache, a projection, another service's local copyItem 28 — the event busThe owner publishes what changed; consumers keep their own copy. No synchronous dependency, and the owner does not carry the read load.
Needs the current value, nowThe contractThis is what an API is for.

Drawing this line at contract-design time is a task on milestone 30.2 for a reason: undrawn, API-first turns batch queries into HTTP and takes the database down with it — and the conclusion drawn will be that API-first does not work.

Preventing regrowth​

The last step is the one that keeps the work done. Database credentials are issued only through the dynamic secrets engine (roadmap item 12), and only to a dataset's owning service. Without a single issuing path, direct access grows back quietly and the inventory has to be run again.

An exception process here is the mechanism by which the estate returns to where it started.