Skip to content

sluice v0.99.246

Choose a tag to compare

@github-actions github-actions released this 15 Jul 06:41
· 2081 commits to main since this release

sluice v0.99.246

New: two unattended-operation hardening features — slot-health alerts that page you before a Postgres replication slot invalidates, and a concurrent-writer guard that stops two backup writers from silently corrupting a chain's lineage catalog. Drop-in upgrade: both are additive, no existing flag, default, or output changes shape. Both were filed from the 2026-07-15 burnside-project prior-art review (roadmap item 64; the addendum in docs/research/sluice-as-analytics-source.md).

Added

  • Slot-health alerts: the replication-slot pre-warnings now page the notification sinks (ADR-0059 implementation note, roadmap item 64a). sluice has long computed the three slot-health crossings on a Postgres logical-replication source — WAL retention pressure at 70% (warning) and 85% (critical) of max_slot_wal_keep_size, and 30 minutes of slot inactivity (warning, the "is the consumer dead?" framing) — but they surfaced only as structured slog WARNs, invisible on an unattended sync until the slot invalidates (wal_status = 'lost' forces a full re-snapshot). Those crossings now also fire to the same webhook / Slack / SMTP sinks the metrics alerter and schema-drift alerts use. It is one firing mechanism, not two: a notification fires exactly when the slog WARN fires — state transitions (clean→warn, warn→critical, cleared-then-re-entered) page immediately, and a sustained condition re-pages at the existing 5-minute rate-limit window — and the body reuses the slog hint verbatim plus the raw slot facts, so the page and the log line can never disagree (the 85% remediation now also names dropping an abandoned slot). Default on, inert unless a --notify-* sink is configured; --notify-slot-health=false opts out (and per-sync notify-slot-health in a sync run fleet spec — omitted means enabled). Postgres logical-replication sources only; the slog WARNs are unchanged and fire regardless of the flag. Advisory and failure-isolated: a dead sink is logged and swallowed, never touching the sync.

  • Backup-chain concurrent-writer guard: a lost catalog write is now a loud, coded refusal (ADR-0160, roadmap item 64b). lineage.json — the chain's lineage catalog — is the one read-modify-write object every chain writer shares: full-backup finalize, incrementals, backup stream rollovers, the rotation commit, backup compact, backup prune, reconcile, and backup verify --rebuild-catalog. Nothing arbitrated between two concurrent writers, so a duplicate cron entry running backup incremental twice, or a backup racing a compact/prune, could interleave the catalog: the last Put won and the loser's structural update silently vanished. Every guarded write now claims the chain's next write-generation via a conditional create (lineage.gen/g-<N> — S3 If-None-Match, GCS generation, Azure ETag, local O_EXCL, all through gocloud's IfNotExist) observed before the catalog read; a lost claim means another writer advanced the chain, and the operation refuses loudly with the new SLUICE-E-BACKUP-CHAIN-CONFLICT having written zero catalog change — the winning writer's marker records its host/pid/claim-time so the message points you at the competitor, and a clean re-run is the whole remediation. There is no lockfile and no TTL: a claim orphaned by a crash is just the next observation base, never a stuck chain. backup prune was also reordered to commit-then-sweep (the order compaction already used), so a refused — or crashed — prune leaves the chain byte-untouched instead of a catalog referencing deleted manifests. Honest scope: the claim and the catalog Put are still two operations, so the guard shrinks the silently-vulnerable window from the whole seconds-wide read-modify-write span to the milliseconds between claim and Put — it does not eliminate it (true closure needs conditional overwrite, which the portable storage layer does not expose; documented in ADR-0160's residuals). Stores without conditional-create support — or an S3-compatible endpoint whose conditional PUT fails — degrade to the previous unguarded behavior with a WARN rather than blocking backups. The guard is pinned end-to-end against real MinIO, including the interleaved-writer conflict, the prune-refusal-leaves-chain-intact shape, orphaned-claim liveness, and the degrade paths.

Compatibility

  • Both additive; no breaking changes. No existing flag, default, exit code, or output changes. Two operational notes: (1) on stores with conditional-create support, small lineage.gen/ marker objects (forensic JSON: claim time, sluice version, host, pid) now appear at chain roots, garbage-collected to a trailing window of 8 — they are inert to restore, verify, and older sluice binaries reading the same chain; (2) slot-health alerts default on, so an operator who already has a --notify-* sink configured on a Postgres-source sync may start receiving pages for conditions that previously only hit the logs — that is the feature working; --notify-slot-health=false restores the WARN-only behavior.

Who needs this

No action required for anyone — this release contains no fixes, so there is nothing to re-verify or re-run. Pick it up if you run long-lived unattended syncs from Postgres (you now get paged before a slot invalidates and forces a full re-snapshot, not after) or if more than one process can ever write the same backup chain — overlapping crons, a backup schedule that can collide with a compact or prune, an operator double-start: the guard turns a silently lost catalog update into a loud coded refusal whose remediation is simply to let the other writer finish and re-run.


Install / upgrade: see the README. Verify downloads against checksums.txt.