Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ jobs:
# Compiles the integration-tagged files without running them; those and the
# Compose end-to-end suites need PostgreSQL and are run by hand.
- run: make test
- name: Controller JavaScript regression tests
run: make ui-test
# Apply, the copy workers, and the CDC handoff are concurrent, and a race
# between them is not something anyone reproduces by hand twice.
- run: make race
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
GO ?= go
GOFLAGS ?=
NODE ?= node

.PHONY: fmt vet test race integration bench cdc-bench e2e controller-e2e restart-e2e crash-e2e
.PHONY: fmt vet test race ui-test integration bench cdc-bench e2e controller-e2e restart-e2e crash-e2e

fmt:
$(GO) $(GOFLAGS) fmt ./...
Expand All @@ -15,6 +16,9 @@ test:
race:
$(GO) $(GOFLAGS) test -race ./...

ui-test:
$(NODE) --test internal/controller/progress_test.cjs

integration:
$(GO) $(GOFLAGS) test -tags=integration ./...

Expand Down
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,40 @@ first durable part completion. The lifecycle bar is stage progress, not an
elapsed-time estimate; the object and verification bars use the recorded
completed and total work.

The **WAL positions and gaps** panel compares current primary WAL with the
recorded durable capture and apply checkpoints. It shows source minus captured
(not yet captured), captured minus applied (staged replay), and source minus
applied (end-to-end gap). LSNs and decimal byte differences preserve all 64 bits.
These are WAL-byte distances, not row counts or CDC file sizes. Source WAL is
sampled after the local checkpoints, so live gaps are conservative and include
sampling delay. A zero staged gap or `follow` phase does **not** establish that
the source has been caught up. Even zero end-to-end gap is not data validation
or authorization to cut over.

Source generation, capture, and replay rates are measured separately over a
rolling window; end-to-end ETA uses replay minus actual source generation and
is shown only when that net rate is positive. Missing, inconsistent, or stale
samples show unavailable, not zero. The source identity must match the durable
migration before its LSN is compared.

The **VACUUM and index-build progress** panel reads PostgreSQL's progress views
in each configured database, including autovacuum, `CREATE INDEX`/`REINDEX`
and their concurrent variants, and `VACUUM FULL`. It shows table/index, current
phase, PID, elapsed time, waits and phase-specific block/tuple/locker counters.
A phase bar reaching 100% is not overall completion; some phases have no
measurable total and vacuum phases may repeat. Completed jobs disappear from
the live views. See [PostgreSQL progress reporting](https://www.postgresql.org/docs/current/progress-reporting.html).

These authenticated, read-only diagnostics use `GET /api/diagnostics`, separate
from durable status and replay. All tabs share a five-second sample; collection
has a three-second deadline, at most two database connections per sample, 1.5-second SQL
timeouts, and at most 100 maintenance jobs per database. No table data or query
text is read, and no maintenance commands are issued. `pg_read_all_stats` is
needed to see other users' job details; source identity checking also needs
permission to execute `pg_control_system()`. Insufficient permissions remain
visible as unavailable/restricted results; the controller never grants them.
Source and target monitoring failures are independent and do not stop replay.

The lifecycle is also rendered as an ordered, numbered ten-step path from
preflight through completion, with cutover marked CLI-only. Findings are
collapsed by default and classified as blockers, accepted risks,
Expand Down
Binary file added docs/images/controller-index-progress.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/controller-vacuum-progress.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/controller-wal-progress.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion internal/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const (
outputLimit = 64 << 10
)

//go:embed ui.html
//go:embed ui.html progress.js
var assets embed.FS

// Action is one operation the controller may supervise.
Expand Down Expand Up @@ -78,6 +78,7 @@ type Server struct {
configRevision uint64
configurationPath string
copySample copySample
diagnostics diagnosticsCache
}

type copySample struct {
Expand Down Expand Up @@ -318,6 +319,8 @@ func (s *Server) Handler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("GET /", s.index)
mux.HandleFunc("GET /api/status", s.status)
mux.HandleFunc("GET /api/diagnostics", s.serveDiagnostics)
mux.HandleFunc("GET /progress.js", s.progressScript)
mux.HandleFunc("GET /api/config", s.getConfiguration)
mux.HandleFunc("PUT /api/config", s.putConfiguration)
mux.HandleFunc("POST /api/actions/{action}", s.action)
Expand Down
Loading