sluice

Objects sluice creates in your databases

The full inventory of sluice's bookkeeping tables, slots, publications, and triggers — what each is for, when it appears, and how to remove it.

To make migrations resumable and continuous sync durable, sluice creates a small, predictable set of bookkeeping objects in your source and target databases. Every one is prefixed sluice_ so you can always find them, and the schema readers exclude them from schema diff and verify (ADR-0029) so they never register as drift or count against a row comparison. Nothing here is hidden — this page is the complete list of what sluice writes, which command writes it, why, and how to clean it up.

Where they live. On Postgres targets the bookkeeping tables are created in the target DSN's schema parameter (default public) — they follow --target-schema, they are not hardcoded to public. On MySQL targets they live in the connection's default database. The source-side object names (sluice_slot, sluice_pub, sluice_heartbeat) are defaults and all overridable. Every object below is created idempotently (IF NOT EXISTS / CREATE OR REPLACE), so a re-run never errors on an existing one.

Target database — bookkeeping tables #

These hold the state that makes migrate --resume and sync start warm-resume work. They persist between runs by design (that's the durable resume frontier); the only built-in way to drop them is the destructive --reset-target-data recovery path, which clears the relevant state and the tables sluice manages.

ObjectCreated byWhen & whyCleaned up by
sluice_cdc_statesync startAt CDC stream open. One row per --stream-id: the durable CDC source position, slot name, source-DSN fingerprint, and stop flag — the warm-resume frontier.--reset-target-data (clears the row); otherwise persists.
sluice_migrate_statemigrateAt bulk-copy start. One header row per --migration-id for resumable bulk migration (ADR-0082).--reset-target-data; otherwise persists.
sluice_migrate_table_progressmigrateAt bulk-copy start. One row per table — per-table progress / keyset checkpoint so --resume picks up mid-copy (ADR-0082).--reset-target-data; otherwise persists.
sluice_cdc_schema_historysync startAt CDC stream open; rows written only at a real DDL/schema-delta boundary. Position-anchored schema versions so each event decodes in the schema in effect at its position — resume-after-DDL without a re-snapshot (ADR-0049). Grows with DDL count (tiny).Compacted on demand below the retention floor by backup prune; --reset-target-data.
sluice_target_metrics_historysync start (telemetry only)Only when PlanetScale telemetry is configured (--planetscale-org). A bounded rolling history of polled target-health snapshots (CPU/mem/storage/lag) so diagnose can show the recent trend (ADR-0107). Advisory — never affects the sync.Rows auto-pruned to a rolling window; table via --reset-target-data. Disable with --suppress-target-metrics-history.
sluice_shard_consolidation_leasesync start (consolidation only)Only when consolidating a multi-shard Vitess/PlanetScale source onto one target with cross-shard DDL coordination (ADR-0054). One row per consolidated table records which shard-stream owns applying a coordinated DDL.Lease rows GC-swept automatically; table via --reset-target-data.
--reset-target-data is destructive: it clears the relevant state row(s) and drops every source-schema table sluice manages on that target, then cold-starts. Other tables on the target are untouched. See the migrate reference and ADR-0023.

Source database — Postgres logical CDC #

The native postgres CDC engine reads the WAL through a logical replication slot. It creates two persistent server objects plus two optional/transient ones. Full operational detail — failover, slot invalidation, sizing — is in the Postgres source-prep guide.

ObjectKindWhen & whyCleaned up by
sluice_slotreplication slotCreated lazily on the first CDC connect (cold-start). Pins WAL and holds the resume LSN (confirmed_flush_lsn). pgoutput plugin; failover-aware on PG 17+.Never auto-dropped — explicit sluice slot drop <name>. (Auto-dropped only if cold-start setup itself fails.)
sluice_pubpublicationEnsured on demand when missing, by migrate and sync start. Defines the table set pgoutput streams — scoped FOR TABLE … by default (ADR-0021), FOR ALL TABLES for multi-schema CDC. A MISSING publication is recreated at every stream open, warm resume included, and when no table scope is supplied it comes back FOR ALL TABLES — so dropping it by hand and resuming can silently widen a scoped stream to the whole database. The scope-conflict refusal does not catch this: it guards a rescope that REMOVES tables, not a create-from-absent.No dedicated command — manual DROP PUBLICATION (a DROP SCHEMA won't remove it). sluice rescopes/recreates it itself.
sluice_heartbeattableOpt-in via --source-heartbeat-interval (default off). A periodic INSERT generates WAL so the consumer position keeps advancing on an idle source — preventing slot-invalidation / binlog-purge silent loss. Also created on a MySQL source under the same flag.Rows auto-pruned (--source-heartbeat-prune-window, default 1h); the table itself is left in place — drop manually.
sluice_backup_anchor_<ts>temporary slotCreated by backup at snapshot start to pin a consistent export point for the run.Transient — the server auto-drops it when the session closes (even on crash). Legacy leaked anchors are auto-swept on the next backup.

MySQL source: native MySQL CDC reads the binlog and creates nothing on the source except the opt-in sluice_heartbeat table above — there is no slot or publication concept.

Source database — trigger-based CDC #

The slot-less trigger engines capture changes with database triggers instead of a log stream. trigger setup installs every object below; trigger teardown removes all of them (pass --keep-data to retain the change-log for forensics), and trigger prune reaps applied change-log rows. They live in the source schema (--schema, default public on Postgres).

Postgres trigger engine (postgres-trigger, ADR-0066) #

ObjectKindWhy
sluice_change_log + sluice_change_log_metatables (+ indexes)Append-only captured-change log (txid, op, PK + before/after JSONB) and a singleton per-install record. The meta table started as a bare schema-version pin and has since grown the rest of the install's identity: the capture_replicated_writes posture (v3, ADR-0185), the three setup-evidence columns the DDL-suppression privilege boundary is bound to (v4 — the firing backend's PID, a nonce, and a timestamp, armed and disarmed inside setup's own transaction), and capture_fn_digest (v5), the provenance that lets a CDC open tell an old capture function from an edited one. Every column is added by ADD COLUMN IF NOT EXISTS, so re-running trigger setup is the migration and older installs read fine until then.
sluice_change_log_consumerstablePer-stream applied-frontier registry (roadmap item 115) — every sync records how far it has consumed the shared change log, so the auto-prune / trigger prune cut is taken at the minimum across registered consumers.
sluice_capture_change(), sluice_capture_truncate_fn(), sluice_capture_ddl(), sluice_capture_drop()functionsRow-capture (payload mode set by --capture-payload), TRUNCATE companion, the ddl_command_end handler, and — since v0.136.0 — the sql_drop handler. The last two are installed only on the event-trigger tier (an --allow-polled-fingerprint install has neither).
sluice_capture, sluice_capture_truncate (per table); sluice_capture_ddl_trg, sluice_capture_drop_trgtriggersOne combined AFTER INSERT/UPDATE/DELETE trigger and a TRUNCATE trigger per table, plus two database-wide event triggers. Two, because PostgreSQL reports DDL through two mutually exclusive context functions: pg_event_trigger_ddl_commands() returns zero rows for a DROP, so a ddl_command_end trigger alone fires on DROP TABLE and records nothing — the stream carries on as though the table still existed. The dedicated sql_drop pair records it instead, filtered on the dropped-object set rather than a command-tag list (so DROP SCHEMA … CASCADE over a synced table is caught too; DROP INDEX stays deliberately uncaptured). Installs predating v0.136.0 have only the ddl_command_end half and warn DROP-CAPTURE-ABSENT at every CDC open until trigger setup is re-run.

SQLite / Cloudflare-D1 trigger engines (sqlite-trigger / d1-trigger, ADR-0135/0136) #

ObjectKindWhy
sluice_change_log + sluice_change_log_metatablesCaptured-change log with a monotonic id watermark, and a schema-version pin.
sluice_change_log_consumerstablePer-stream applied-frontier registry (roadmap item 115) — every sync records how far it has consumed the shared change log, so the auto-prune / trigger prune cut is taken at the minimum across registered consumers and no stream is ever starved of its resume window.
sluice_change_log_columnstableCaptured-column fingerprint — since SQLite/D1 have no DDL triggers, a source ALTER is caught here and sync start refuses loudly rather than dropping a new column silently.
sluice_capture_<table>_<ins|upd|del>triggersThree per table (SQLite has no combined-event trigger form), each writing into the change-log.
The two families differ in trigger naming: postgres-trigger uses one combined trigger literally named sluice_capture per table, whereas sqlite-trigger/d1-trigger use three separate sluice_capture_<table>_<op> triggers. Both are fully removed by trigger teardown.

Cleanup quick reference #

CommandRemoves
sluice slot drop <name>The PG source replication slot (the one object sluice never drops on its own).
sluice trigger teardownEvery trigger-engine object on the source; --keep-data retains the change-log.
sluice trigger prune / backup pruneOld change-log rows / below-floor sluice_cdc_schema_history rows (the tables stay).
sluice sync start --reset-target-dataThe target bookkeeping state + every source-schema table sluice manages on the target (destructive recovery).
manualsluice_pub (DROP PUBLICATION — but see the warning below), and the sluice_heartbeat table once heartbeats are no longer needed. Do not drop sluice_pub while any stream over that source still exists. The next open recreates it, and with no table scope to hand it recreates it FOR ALL TABLES — which stops Postgres accepting UPDATE and DELETE on every table in the database that has no replica identity, including ones the stream never touched. Retire the stream first with sluice sync decommission --stream-id <id> --yes, which drops the slot and the per-stream publication together.