Commit fd96d5e
authored
UI-driven resharding: metadata-store migrations with live operation log (#914)
* Backfill pinned cnpg shard into Duckling spec (spec.metadataStore.cnpgShard)
Which cnpg shard a tenant's metadata lives on is currently only a
composition-pinned status stamp (status.metadataStore.assignedShard) —
not explicit anywhere in spec or the config store. charts PR #12918 adds
an optional, schema-validated spec.metadataStore.cnpgShard field whose
precedence is: spec override > pinned status > cnpg.activeShard.
The provisioner now backfills that field on ready cnpg-shard ducklings
with the CR's own pinned assignedShard — the same derived-output →
durable-input move as the existing bucket-name backfill, and a pure
no-op for the composition since the value equals the pin. Once every
duckling carries it, the tenant's shard is explicit spec: the
precondition for shard-migration cutovers, which patch this field to a
DIFFERENT shard (an explicitly operator-driven step, never done by the
backfill; an existing spec value is never overwritten).
On a cluster whose Duckling XRD predates the field, the API server
silently prunes the patch; the post-patch read-back detects that and
latches the backfill off (one WARN, no per-tick patch churn) until the
control plane restarts.
e2e: new duckling_shard_backfill assertion (cnpg lane) waits for the
spec field to match the pinned shard; on an old XRD it probes field
support with its own patch and SKIPs loudly instead of failing, so it
goes live the moment charts #12918 deploys to mw-dev.
* Add UI-driven resharding: metadata-store migrations with live op log
Operator-driven migration of an org's DuckLake catalog between metadata
stores, in three directions: cnpg shard -> cnpg shard, external(RDS) ->
cnpg, and cnpg -> external as the emergency escape hatch. Only the
Postgres catalog moves; S3 parquet never does. Full design:
docs/design/resharding.md.
Config store: reshard operation rows + a verbose operator-facing log
table (goose 000018), claim/heartbeat/epoch-fenced writes, cancel flag,
and the LOAD-BEARING connection barrier: the lease-grant transaction
refuses orgs whose warehouse is resharding, checked under the same
per-org advisory lock the ready->resharding CAS takes. The connect-time
57P03 gates (PG wire + Flight) are UX only - a lease could otherwise be
granted up to a queue-timeout after the gate ran.
Runner (provisioner, every CP replica; single winner per op via claim
CAS): block -> drain (leases+queue zero, then zero live workers - each
runs a catalog-writing DuckLakeCheckpointer; lingering hot-idle workers
retire via the standard CAS path, never raw pod deletes; parked Flight
sessions are destroyed locally per CP or they hold leases for the token
TTL) -> pause compaction (explicit spec patch, key-presence recorded and
restored exactly) -> flip -> copy -> verify -> cleanup -> finalize with
an end-of-op report (maintenance-mode duration, per-op tables/rows/bytes).
cnpg->ext inverts copy and flip: the type flip deletes the cnpg source
role/DB, so it IS the cleanup and only runs after verify; its target
password is ephemeral (request -> in-process stash -> runner memory,
never persisted). Rollback always patches the source shard VALUE back
(never removes the key); ext->cnpg rollback nulls cnpgShard (XRD CEL).
Cancel is honored between steps and inside every wait loop. External
stores are never modified.
Catalog copier: one REPEATABLE READ read-only snapshot, faithful DDL
from pg_catalog introspection, raw binary COPY passthrough, constraints
then non-constraint indexes (PK-backed excluded), per-table verify plus
an outside-tx source-stability recheck that catches concurrent writers
(e.g. an in-flight compaction job), fenced by a target-DB advisory lock.
Admin API (RoleGate/audited): POST /orgs/:id/reshard, GET
/orgs/:id/reshards, GET /reshards/:id{,/log?after_id=}, POST
/reshards/:id/cancel. Console: Reshard button on org detail -> target
form (shard select or external + once-only password) -> operation page
with header (org, from->to, status/step, started/finished, maintenance
duration, copy counters) + live incremental log with auto-scroll and a
Cancel button. Drift checker treats resharding as expected.
Tests: configstore postgres suite (claim/fencing/cancel/log pagination +
the grant-gate), runner unit tests (all three directions, rollbacks,
cancel, ephemeral-password takeover loss), admin handler tests
(validation, secrets never persisted), migration asserts to v18, and
four e2e assertions incl. the REAL ext->cnpg positive path off the
harness RDS and a real bogus-shard rollback
(DUCKGRES_RESHARD_FLIP_TIMEOUT=90s in the CI manifests).
* e2e: treat post-reshard connect gate as transient; one-shot negative check
The reshard assertions reconnect immediately after the resharding->ready
flip, but the connect-time gate reads the polled config snapshot — up to
one poll interval of lag during which connects still get the 57P03
'reshard in progress, please retry shortly' rejection. Add that message
to _pg_exec's transient-retry list (it says retry, so the harness
should), and switch the mid-drain negative connect check to a one-shot
psql so it observes the rejection instead of retrying through it.
* catalog copy: cast pg_constraint.contype to text
contype is the internal "char" type (OID 18), which pgx cannot scan in
binary format into a string — the ext->cnpg e2e run streamed the whole
catalog and then failed exactly here (and rolled back cleanly).
* Reshard: per-op cutover_timeout_seconds instead of a CI-wide short flip timeout
The e2e's CI-wide DUCKGRES_RESHARD_FLIP_TIMEOUT=90s (added for the
bogus-shard rollback assertion) also bounded the LEGITIMATE ext->cnpg
cutover, which needs minutes: the endpoint converged in ~20s but
Ready=True (provider-sql role/DB creation + cnpg SASL credential
propagation) takes longer, so the real cutover rolled back at 90s.
Make the cutover wait per-operation: new cutover_timeout_seconds on the
op row/API/UI form (0 = 15m default; DUCKGRES_RESHARD_FLIP_TIMEOUT now
only overrides that default). The e2e rollback assertion passes 90s on
its own op, the ext->cnpg positive path passes 600s, and the CI env
knob is gone from the manifests.1 parent 6c00303 commit fd96d5e
31 files changed
Lines changed: 5157 additions & 15 deletions
File tree
- controlplane
- admin
- ui/src
- components
- hooks
- lib
- pages
- types
- configstore
- migrations
- provisioner
- docs/design
- server/flightsqlingress
- tests
- configstore
- e2e-mw-dev
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
528 | 528 | | |
529 | 529 | | |
530 | 530 | | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
531 | 572 | | |
532 | 573 | | |
533 | 574 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
201 | 201 | | |
202 | 202 | | |
203 | 203 | | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
204 | 207 | | |
205 | 208 | | |
206 | 209 | | |
207 | 210 | | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
208 | 217 | | |
209 | 218 | | |
210 | 219 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
108 | 108 | | |
109 | 109 | | |
110 | 110 | | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
111 | 116 | | |
112 | 117 | | |
113 | 118 | | |
| |||
0 commit comments