Skip to content

feat(cli): cache the shadow database baseline as a PGDATA snapshot (CLI-2191) - #6184

Open
avallete wants to merge 91 commits into
developfrom
avallete/shadow-db-perf-6ad622
Open

feat(cli): cache the shadow database baseline as a PGDATA snapshot (CLI-2191)#6184
avallete wants to merge 91 commits into
developfrom
avallete/shadow-db-perf-6ad622

Conversation

@avallete

@avallete avallete commented Aug 13, 2026

Copy link
Copy Markdown
Member

db diff / db pull / declarative sync rebuild the shadow's platform baseline (initdb + init SQL + the PG15+ one-shot realtime/storage/auth jobs, ~15s) on every run, before user migrations even start. This PR makes that a once-per-config cost. Measured on a default-services PG17 project: ~3.3s warm provision vs ~15.5s cold.

What changed

  1. Readiness gate (fix): the shadow's docker healthcheck can't report before t+10s (10s interval, no start_period), so legacyWaitForShadowReady now polls container-state + a short authenticated connect instead. Same timeout budget, same error shape and log dump; the healthcheck config and the long-running db container's wait are untouched.
  2. exec'd postgres entrypoint (fix): PID 1 was sh, which swallowed SIGTERM, so every docker stop burned the 10s grace period. All three entrypoint variants now exec, and stops take ~1s (also benefits supabase stop). Deliberate divergence from Go's script; timing is outside the Go-parity surface (ADR 0016).
  3. Baseline snapshot cache (feat, default ON): right after the baseline and before any user migration, the cold path stops the shadow and streams its PGDATA to supabase/.temp/pgdelta/shadow-baseline-<key>.tar (~90MB, atomic rename, current key only). A key-matching run unpacks the tar into a fresh container before docker start — the entrypoint skips initdb, the baseline is skipped, migrations apply directly.

Design notes for review

  • Pristine by construction: every run gets a new container restored from the baseline's exact bytes, so there is no reset protocol, no metadata, no lock file, and no kept container — concurrency is settled by the atomic rename alone.
  • The generic export/restore primitives live in db-bootstrap/pgdata-snapshot.ts (container-agnostic, plain-file artifact) — reusable for a future live-stack savepoint or native (non-Docker) Postgres; a TODO there records the hot-save modes (docker pause freeze-copy, pg_backup_start/stop online copy).
  • The tar-stream form of docker cp is load-bearing: the directory form resets ownership to root and Postgres refuses the data dir.
  • The cache key hashes every input baked into the cluster (images incl. enabled-service job tags, jwtSecret, rootKey, db password/settings, jwtExpiry, auto_expose, roles.sql, vault names+values, shadowPort, major_version, JWKS when realtime on PG≥15).
  • One forced exception: the cold cache path drops --rm, because Docker destroys an AutoRemove container on docker stop (verified) — release still removes every shadow; a SIGKILLed CLI leaves at most one stopped project-labeled container, swept by supabase stop.
  • The cache never fails a run: warm anomalies delete the suspect tar and cold-provision; a failed export warns and continues uncached. Opt-out: SUPABASE_SHADOW_CACHE=false. Debug timings: SUPABASE_SHADOW_DEBUG=1.

Still cold-provisioning (documented follow-ups): db diff --use-pgadmin, db pull --declarative's raw shadow, migration squash.

🤖 Generated with Claude Code

avallete and others added 30 commits August 6, 2026 09:04
…lta-next

# Conflicts:
#	apps/cli-go/cmd/db.go
#	apps/cli/docs/go-cli-porting-status.md
#	apps/cli/src/legacy/commands/db/diff/SIDE_EFFECTS.md
#	apps/cli/src/legacy/commands/db/diff/diff.handler.ts
#	apps/cli/src/legacy/commands/db/diff/diff.integration.test.ts
#	apps/cli/src/legacy/commands/db/diff/diff.layers.ts
#	apps/cli/src/legacy/commands/db/pull/SIDE_EFFECTS.md
#	apps/cli/src/legacy/commands/db/pull/pull.handler.ts
#	apps/cli/src/legacy/commands/db/pull/pull.integration.test.ts
#	apps/cli/src/legacy/commands/db/pull/pull.layers.ts
#	apps/cli/src/legacy/commands/db/push/SIDE_EFFECTS.md
#	apps/cli/src/legacy/commands/db/reset/SIDE_EFFECTS.md
#	apps/cli/src/legacy/commands/db/schema/declarative/declarative.orchestrate.integration.test.ts
#	apps/cli/src/legacy/commands/db/schema/declarative/declarative.orchestrate.ts
#	apps/cli/src/legacy/commands/db/schema/declarative/generate/generate.handler.ts
#	apps/cli/src/legacy/commands/db/schema/declarative/generate/generate.integration.test.ts
#	apps/cli/src/legacy/commands/db/schema/declarative/sync/SIDE_EFFECTS.md
#	apps/cli/src/legacy/commands/db/schema/declarative/sync/sync.handler.ts
#	apps/cli/src/legacy/commands/db/schema/declarative/sync/sync.integration.test.ts
#	apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.seam.layer.ts
#	apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.seam.service.ts
#	apps/cli/src/legacy/shared/db-bootstrap/db-setup.ts
#	apps/cli/src/legacy/shared/legacy-db-connection.sql-pg.layer.ts
#	apps/cli/src/legacy/shared/legacy-http-errors.ts
#	apps/cli/src/legacy/shared/legacy-migration-apply.ts
#	apps/cli/src/legacy/shared/legacy-migration-apply.unit.test.ts
#	apps/cli/src/legacy/shared/legacy-pgdelta.cache.ts
#	packages/api/src/effect.ts
#	packages/api/src/internal/client.ts
#	packages/api/src/internal/client.unit.test.ts
…lta-next

# Conflicts:
#	apps/cli/src/legacy/commands/db/diff/SIDE_EFFECTS.md
#	apps/cli/src/legacy/commands/db/diff/diff.handler.ts
#	apps/cli/src/legacy/commands/db/diff/diff.integration.test.ts
#	apps/cli/src/legacy/commands/db/pull/SIDE_EFFECTS.md
#	apps/cli/src/legacy/commands/db/shared/legacy-shadow-source.ts
Legacy migrate, next migrate, and next declarative bake different pg_net
states into the same cluster recipe. Hash that policy so they cannot
share a snapshot, honor --no-cache on the remaining catalog exports, and
document the global cache's LRU/TTL side effects.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7a8fa09b16

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/src/legacy/shared/db-bootstrap/shadow-cache.ts
# Conflicts:
#	apps/cli/src/legacy/commands/db/diff/SIDE_EFFECTS.md

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 788e9186d7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/src/legacy/commands/db/schema/declarative/generate/SIDE_EFFECTS.md Outdated
Comment thread apps/cli/src/legacy/commands/db/schema/declarative/generate/SIDE_EFFECTS.md Outdated
Comment thread apps/cli/src/legacy/shared/db-bootstrap/shadow-cache.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 09d8c6ccc9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/src/legacy/commands/db/shared/legacy-pgdelta-next-shadow.layer.ts Outdated
avallete pushed a commit that referenced this pull request Aug 16, 2026
The stale-peek refresh comment listed the remote JWKS among the inputs a
delayed acquire re-resolves, but that resolver is deliberately memoized
per shadow input (shadow-database.ts, review: Codex on #6184) so the
cache key and the baked baseline always carry the same value and cannot
diverge — a delayed acquire keeps the command-start JWKS, well inside
the staleness the snapshot cache accepts by design (a warm hit serves a
tar up to 14 days old under its matching key). Scope the comment to
roles.sql, the one input the refresh actually re-reads, and state the
JWKS exemption explicitly.

Review: Codex on #6215.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NYVv1jjXdrTzqDTVrU4Had
Base automatically changed from feat/upgrade-pg-delta-next to develop August 17, 2026 11:33
avallete and others added 5 commits August 17, 2026 14:03
…cli-pr-conflicts-529506

# Conflicts:
#	apps/cli/docs/go-cli-divergences.md
#	apps/cli/package.json
#	apps/cli/src/legacy/commands/db/diff/SIDE_EFFECTS.md
#	apps/cli/src/legacy/commands/db/diff/diff.handler.ts
#	apps/cli/src/legacy/commands/db/diff/diff.integration.test.ts
#	apps/cli/src/legacy/commands/db/pull/SIDE_EFFECTS.md
#	apps/cli/src/legacy/commands/db/pull/pull.handler.ts
#	apps/cli/src/legacy/commands/db/pull/pull.integration.test.ts
#	apps/cli/src/legacy/commands/db/schema/declarative/declarative.flow.ts
#	apps/cli/src/legacy/commands/db/schema/declarative/declarative.flow.unit.test.ts
#	apps/cli/src/legacy/commands/db/schema/declarative/declarative.orchestrate.ts
#	apps/cli/src/legacy/commands/db/schema/declarative/generate/SIDE_EFFECTS.md
#	apps/cli/src/legacy/commands/db/schema/declarative/generate/generate.command.ts
#	apps/cli/src/legacy/commands/db/schema/declarative/generate/generate.handler.ts
#	apps/cli/src/legacy/commands/db/schema/declarative/generate/generate.integration.test.ts
#	apps/cli/src/legacy/commands/db/schema/declarative/sync/SIDE_EFFECTS.md
#	apps/cli/src/legacy/commands/db/schema/declarative/sync/sync.handler.ts
#	apps/cli/src/legacy/commands/db/schema/declarative/sync/sync.integration.test.ts
#	apps/cli/src/legacy/commands/db/shared/legacy-pgdelta-engine.layer.ts
#	apps/cli/src/legacy/commands/db/shared/legacy-pgdelta-engine.layer.unit.test.ts
#	apps/cli/src/legacy/commands/db/shared/legacy-pgdelta-engine.next.layer.integration.test.ts
#	apps/cli/src/legacy/commands/db/shared/legacy-pgdelta-engine.next.layer.ts
#	apps/cli/src/legacy/commands/db/shared/legacy-pgdelta-engine.next.layer.unit.test.ts
#	apps/cli/src/legacy/commands/db/shared/legacy-pgdelta-next-adapter.layer.ts
#	apps/cli/src/legacy/commands/db/shared/legacy-pgdelta-next-adapter.unit.test.ts
#	apps/cli/src/legacy/commands/db/shared/legacy-pgdelta-next-artifacts.ts
#	apps/cli/src/legacy/commands/db/shared/legacy-pgdelta-next-artifacts.unit.test.ts
#	apps/cli/src/legacy/commands/db/shared/legacy-pgdelta-next-shadow.layer.ts
#	apps/cli/src/legacy/commands/db/shared/legacy-pgdelta-next-shadow.layer.unit.test.ts
#	apps/cli/src/legacy/commands/db/shared/legacy-pgdelta-next-shadow.service.ts
#	apps/cli/src/legacy/commands/db/shared/legacy-pgdelta.write.unit.test.ts
#	apps/cli/src/legacy/commands/db/shared/legacy-shadow-source.ts
#	apps/cli/src/legacy/shared/db-bootstrap/db-setup.ts
#	apps/cli/src/legacy/shared/db-bootstrap/db-setup.unit.test.ts
#	apps/cli/src/legacy/shared/db-bootstrap/shadow-database.ts
#	apps/cli/src/legacy/shared/legacy-db-push-core.ts
#	apps/cli/src/legacy/shared/legacy-pgdelta-next-flag.ts
#	apps/cli/src/legacy/shared/legacy-pgdelta-next-flag.unit.test.ts
#	apps/cli/tests/fixtures/compiled-libpg-query.ts
#	pnpm-lock.yaml
#	pnpm-workspace.yaml
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Bypass pg-delta's same-identity guard by snapshot lineage (same cache
  key), not by requiring both plan shadows to be warm restores — the
  first cold plan exports the tar its declarative sibling restores, and
  the guard rejected that clone (Codex P1).
- Fold LEGACY_START_ENABLE_DATABASE_WEBHOOKS_SQL into the baseline SQL
  digest so editing it invalidates stale pg_net baselines.
- Bound legacyWaitForShadowReady by elapsed time (+ one connect
  allowance) so hung dials cannot stretch the wait ~3x its budget.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eview findings

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0430203364

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/src/legacy/commands/db/diff/diff.handler.ts Outdated
Comment thread apps/cli/src/legacy/shared/legacy-pgdelta.cache.ts Outdated
avallete and others added 3 commits August 17, 2026 16:15
…l run

db diff/db pull hardcoded webhooks: "enabled" into the cache key even
when pgdelta-next's config-following migrate would run, and db diff's
explicit migrations-catalog path omitted the policy entirely while its
provisioner forces pg_net on — either mismatch lets the two engines
restore each other's tars. Derive the key from migrationMode at the
diff/pull seams, declare "enabled" at the explicit catalog call, and
drop exportViaShadowCatalog's opts default so callers must state their
policy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rapid-fire publishes can land within the filesystem's timestamp
granularity, making the "oldest" tar ambiguous and the eviction pick
arbitrary (CI-only flake). Age the first tar explicitly — the test
asserts the keep-cap, not tie-breaking.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
db diff and db pull each gain a cache-enabled scenario proving a
legacy-engine baseline tar is never restored into a pg-delta-next run
(and vice versa each publishes its own key). Mutation-verified:
reverting either call site to a hardcoded webhooks: "enabled" fails
the new test at the warm-restore assertion. The shadow-cache suite's
stateful Docker model is hoisted into tests/helpers for reuse; the
stateless shadow spawner mock stays byte-identical for existing
callers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9970ce432d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/docs/go-cli-divergences.md Outdated
Comment thread apps/cli/src/legacy/shared/db-bootstrap/shadow-cache.ts Outdated
Comment thread apps/cli/src/legacy/shared/db-bootstrap/shadow-cache.ts
…key token

- A warm hit now scans the tar's entry headers for the cluster marker
  (data/PG_VERSION) before restoring: an empty or foreign-but-valid tar
  no longer initdbs a bare cluster that reports baselinePresent and
  skips setup — it is discarded as suspect and the run cold-provisions.
- The cache key hashes the EFFECTIVE api.auto_expose_new_tables
  behavior (grants kept vs revoked): unset and explicit false execute
  identical revoke SQL, so they no longer force separate ~90MB tars.
- Docs: generate joins the cache divergence entry; roles.sql cache-key
  reads listed across diff/pull/sync/generate SIDE_EFFECTS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9baed89bbc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/src/legacy/shared/db-bootstrap/shadow-cache.ts Outdated
The export now writes data/SUPABASE_BASELINE into the stopped shadow's
PGDATA right before the outbound docker cp, and warm-hit validation
requires it alongside data/PG_VERSION — a valid bare-cluster tar is
discarded as suspect instead of restoring into baselinePresent: true
and skipping setup. Only snapshotBaseline stamps, strictly after the
platform baseline, so a snapshot taken too early can never carry the
marker (regression guard). Uppercase name keeps the entry at the front
of docker cp's sorted tar so warm validations stay a first-blocks read.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4d494f4d96

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/src/legacy/shared/db-bootstrap/pgdata-snapshot.ts
Comment thread apps/cli/src/legacy/shared/db-bootstrap/shadow-cache.ts
…tants

The baseline marker's content is now the snapshot's own cache key and
warm validation verifies it, so a valid snapshot copied over another
key's filename is discarded (wrong-key verdict, distinct from a missing
marker) instead of restoring a mismatched baseline. The baseline digest
(renamed baseline_embedded_digest) now also hashes the Realtime seed
constants persisted by the one-shot job (tenant id, encryption key,
db user/name/port), so editing them without an image bump invalidates
stale tars.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b382f94904

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/cli/src/legacy/shared/db-bootstrap/shadow-database.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants