Keep daemon state on persistent storage, monitor lag and backfills, and use pause only for bounded maintenance windows
walshadow-stream ctl statusPrimary fields:
| Field | Meaning |
|---|---|
paused |
source consumption intentionally frozen |
dbname |
source database [source] names |
databases |
every replicated database and its selected tables |
rows_synced |
rows sent since process start |
backfills_pending |
tables still loading existing rows |
lag_bytes, lag_seconds |
shadow replay distance from source |
source_received |
newest source position received |
drain |
newest committed position decoded |
emitter_ack |
newest contiguous position acknowledged by ClickHouse |
shadow_replay |
newest position applied by managed shadow |
source_swap_pending |
requested source endpoint has not completed handoff |
crossing_blocked_on |
timeline crossing is parked on named proof |
Each replicated relation is one [[tables]] entry:
| Field | Meaning |
|---|---|
source_table |
<dbname>.<schema>.<table> on the source |
destination_table |
<database>.<table> in ClickHouse, after target_database / target_table overrides |
initial_load |
configured mode: none, copy, base_backup, object_store |
cdc |
relation is in CDC scope |
Healthy steady state has drain, emitter_ack, and shadow_replay moving
toward source_received. Short differences are expected while batches flush
Set --metrics-bind to expose OpenMetrics text metrics, which Prometheus
scrapes directly. Counter series carry _total. Docker deployment uses port
9484 and includes optional provisioned dashboard
docker compose -f docker/docker-compose.yml \
-f docker/docker-compose.grafana.yml up -dWatch:
- source receive and shadow apply lag
- ClickHouse acknowledgement backlog
- row and byte throughput
- decoder queue depth
- resident memory and spill usage
- pending backfills
- timeline crossing and endpoint swap failures
Metrics for work a source database owns carry a database= label: bridge
requests, descriptor log and capture counters, and config overlay counters.
Sum them when a panel wants one number for the daemon. Cluster WAL positions,
transaction buffer, insert pipeline, and process resource metrics stay
unlabelled, they are shared by every database
Alert on sustained failure to advance, not just process availability. Shadow replay lag measures catalog progress; ClickHouse acknowledgement backlog measures destination progress. Either can stall while daemon still answers status requests Choose thresholds from workload's normal lag and retained-WAL budget
Grafana reads walshadow metrics only. It does not connect to source or ClickHouse
walshadow-stream ctl pause
walshadow-stream ctl status
walshadow-stream ctl resumePause stops new WAL consumption while already accepted work can drain. Source keeps producing WAL, so replication slot or archive usage can grow throughout pause. Keep maintenance window bounded and verify available storage before pausing a busy source
Pause persists in config and survives restart
Edit base config or operator-owned fragment, then run:
walshadow-stream ctl reloadSIGHUP performs same reload. Prefer ctl apply when several values must change
atomically or validation rollback is useful
Normal stop and start resumes from persisted manifest. Keep together:
- managed shadow data directory
- filtered WAL directory
- spill directory and manifests
- config and fragment directory
Keep descriptor history, backfill ledgers, and deferred TOAST retirement state alongside manifest. Do not clean spill directory wholesale between starts Daemon removes transient transaction spill itself and reconstructs it from WAL
Filtered-WAL cleanup also preserves shadow restartpoint. Do not delete segments solely because current replay position is newer; restarted shadow may still need them. Disabling configured retention leaves filtered segments on disk
Do not reuse state against unrelated PostgreSQL cluster. Source system-ID mismatch fails startup
--ignore-cursor and --start-lsn intentionally discard normal resume
position. Reserve them for recovery drills or operator-directed rebuilds
Configure a physical replication slot for routine deployments:
SELECT pg_create_physical_replication_slot('walshadow');Then include slot in source URL or config
[source]
slot = "walshadow"At startup walshadow creates configured slot when absent and reserves WAL
immediately. init reports SQL above so slot can exist before daemon starts
Live source moves never create target slot because a new slot cannot protect earlier resume position. Pre-create target slot before planned switchover
Without slot, ensure wal_keep_size or continuous archive covers worst-case
outage and backlog. Missing source WAL stops replication rather than skipping
data
walshadow retries bounded ClickHouse failures with reconnect and backoff. Once retry budget expires, daemon exits and relies on process supervisor restart
Restart replays from durable floor, and generated ReplacingMergeTree tables
converge duplicate row versions by _lsn
An outage can grow source slot retention, local filtered WAL, and spill usage Monitor all three alongside destination backlog. If required source WAL is lost, recovery needs retained archive coverage or a fresh baseline
If ClickHouse remains unavailable:
- Keep source WAL available through slot or archive
- Restore ClickHouse connectivity
- Restart walshadow if supervisor stopped retrying
- Confirm
emitter_ackadvances - Confirm lag returns toward zero
Keep shadow in recovery. Do not promote it, vacuum it locally, or write into its catalogs. Source catalog vacuum and maintenance arrive through filtered WAL Shadow is not a data-bearing source replica
Shadow's replication connection to walshadow requires a live stream for catalog boundary capture; archive files provide recovery fallback, not an equivalent startup mode. Keep connection timeout enabled and investigate attachment failure instead of bypassing it
Walshadow's shadow-facing sender currently trusts its client and lacks TLS/SCRAM Keep that listener on loopback or an otherwise isolated local deployment Source PostgreSQL and ClickHouse connection security are separate settings
Avoid long-running diagnostic queries on shadow. Sender ignores hot-standby feedback, so query conflicts can still be canceled by recovery. Type conversion pins output settings, but matching extension and timezone-data versions remains a deployment responsibility
| Message | Action |
|---|---|
| PostgreSQL version below 16 | upgrade source or use compatible walshadow release |
wal_level is not logical |
change setting and restart PostgreSQL |
| no usable row key | add primary key, choose replica identity index, or use FULL |
| slot missing | create named physical slot or remove slot setting |
| shadow major mismatch | rebuild image with matching PG_MAJOR |
| source system ID mismatch | restore correct state/source pairing or perform explicit rebuild |
| source WAL missing | restore archive coverage or rebuild from fresh baseline |
| unsupported source type change | migrate ClickHouse column and set type override |