Documentation
¶
Overview ¶
Package engine orchestrates branch lifecycle as sagas over the registry, cow planner, and runtime driver. The CLI (P1) and branchd (P2) both embed it.
Index ¶
- Constants
- Variables
- type Action
- type ActionKind
- type CopyUpOptions
- type DestroyError
- type DiffOption
- type DiffResult
- type Engine
- func (e *Engine) AddSource(ctx context.Context, s *registry.Source, password string) error
- func (e *Engine) ApplyReconcile(ctx context.Context, now time.Time, stuckTimeout time.Duration) (ReconcilePlan, error)
- func (e *Engine) BranchUsage(ctx context.Context, name string) (int64, error)
- func (e *Engine) CopyUpMode() cow.CopyUpMode
- func (e *Engine) CowModeCounts() map[string]int
- func (e *Engine) CreateBranch(ctx context.Context, name, sourceName string, ttl time.Duration) (_ *registry.Branch, err error)
- func (e *Engine) CreateBranchFrom(ctx context.Context, name, parentName string, ttl time.Duration) (_ *registry.Branch, err error)
- func (e *Engine) DestroyBranch(ctx context.Context, name string) (err error)
- func (e *Engine) DetectCopyUp(ctx context.Context, opts CopyUpOptions) (cow.ProbeResult, error)
- func (e *Engine) DiffBranch(ctx context.Context, name string, opts ...DiffOption) (_ *DiffResult, err error)
- func (e *Engine) PlanReconcile(ctx context.Context, now time.Time, stuckTimeout time.Duration) (ReconcilePlan, error)
- func (e *Engine) ReapExpired(ctx context.Context, now time.Time) (destroyed []string, err error)
- func (e *Engine) Reconcile(ctx context.Context, now time.Time, stuckTimeout time.Duration, ...) (ReconcilePlan, error)
- func (e *Engine) RecoverBranch(ctx context.Context, name string) (_ *registry.Branch, err error)
- func (e *Engine) RefreshBranchEndpoint(ctx context.Context, name string) (string, error)
- func (e *Engine) RefreshCowModes(ctx context.Context)
- func (e *Engine) RefreshSource(ctx context.Context, name, password string) error
- func (e *Engine) RemoveSource(ctx context.Context, name string) error
- func (e *Engine) ResetBranch(ctx context.Context, name string) (_ *registry.Branch, err error)
- func (e *Engine) RunReconcile(ctx context.Context, interval, stuckTimeout time.Duration, ...)
- type Option
- func WithCredentialRotation() Option
- func WithHeartbeatInterval(d time.Duration) Option
- func WithLazyRW(on bool) Option
- func WithMaxBranches(n int) Option
- func WithMaxLayerDepth(n int) Option
- func WithMetrics(m *metrics.Metrics) Option
- func WithSeedSettle(m pgctl.SettleMode) Option
- func WithTTLPolicy(defaultTTL, maxTTL time.Duration) Option
- func WithWALRecycle(on bool) Option
- type ReconcilePlan
- type TableDelta
Constants ¶
const CowModeUnknown = "unknown"
CowModeUnknown is what pgoverlay_branch_cow_mode counts a ready overlay branch under while its mode has not been read (branchd started after it) or could not be read.
const DefaultCowExtSize int64 = 16 << 10
DefaultCowExtSize is branchd's default XFS copy-on-write extent size hint: two Postgres pages, so a random page write into a cloned segment copies 16 KiB instead of 128 KiB.
const DefaultMaxLayerDepth = 100
DefaultMaxLayerDepth is the default cap on an overlay branch's frozen layer chain. Every branch-from-branch freezes the parent's writes into one more layer, and the parent keeps its whole chain until it is destroyed (reset keeps it too), so a parent forked N times stacks N layers. Each is an overlay lowerdir: lookups walk them all, and the mount option string must fit in one page (about 160 lowerdirs); the kernel stops at 500.
const MaxSampleRows = 500
MaxSampleRows is the largest per-table sample WithDataSample honours. The sample is buffered in memory (psql output, then JSON), so an unbounded n against a grown table could exhaust branchd's memory; a larger request is clamped, and the API rejects a larger ?data= with 400.
const UnknownRows int64 = -1
UnknownRows is the BaseRows/BranchRows value of a side whose row count is unknown (see TableDelta).
Variables ¶
var CowModes = []string{cow.CowModeLazyRW, cow.CowModeEager, cow.CowModeOff, CowModeUnknown}
CowModes lists the modes pgoverlay_branch_cow_mode reports, in order.
var ErrBaseGone = errors.New("branch base is gone")
ErrBaseGone is returned by ResetBranch and DiffBranch for a csi branch whose parent, and so its base volume, has been destroyed. The API maps it to 409.
var ErrInvalidName = errors.New("invalid name")
ErrInvalidName rejects branch and source names that cannot be used across runtimes (docker container names, k8s pod names — RFC 1123 after the pgoverlay-br- prefix). The error text names the kind ("invalid branch name", "invalid source name"). The API maps it to 400.
var ErrMaskingFailed = errors.New("masking script failed")
ErrMaskingFailed marks a branch provision that failed because one of the source's masking scripts failed inside the branch (bad SQL, a missing table): a problem with operator-supplied configuration, not with pgoverlay. The error message names the script and carries psql's output. Test with errors.Is; the API maps it to 422.
var ErrNotRecoverable = errors.New("branch not recoverable")
ErrNotRecoverable reports a recover request that the branch's state or its data cannot satisfy (not failed, or its volumes are gone). The API maps it to 409; ResetBranch or DestroyBranch are the ways forward then.
var ErrParentQuiesce = errors.New("diff would stop the parent branch")
ErrParentQuiesce is returned by DiffBranch for a csi branch created from another branch when the caller did not pass WithParentQuiesce: the diff would have to stop that parent briefly. The API maps it to 403.
var ErrQuotaExceeded = errors.New("branch quota exceeded")
ErrQuotaExceeded is returned by the create paths (and DiffBranch, whose throwaway is a branch too) when --max-branches is set and the live-branch count is already at the cap. The API maps it to 403.
var ErrSeedFailed = pgctl.ErrSeedFailed
ErrSeedFailed marks a source add/refresh whose seed command (pg_basebackup or pg_dump) failed against the source; see pgctl.ErrSeedFailed. The API maps it to 422 with the tool's message.
Functions ¶
This section is empty.
Types ¶
type Action ¶
type Action struct {
Kind ActionKind `json:"kind"`
Target string `json:"target"`
Reason string `json:"reason"`
}
Action is one intended convergence step. Target is the branch name, source name, container id, layer volume or volume name the action operates on; Reason is a human-readable justification. ReconcilePlan is a list of these.
type ActionKind ¶
type ActionKind string
ActionKind enumerates the convergence steps a reconcile pass can take. The values double as the {action} label on pgoverlay_reconcile_actions_total.
const ( // ActionReap destroys a branch whose TTL has passed. ActionReap ActionKind = "reap" // ActionFailStuck fails a branch wedged in creating/resetting past the // stuck timeout and cleans its half-built resources. ActionFailStuck ActionKind = "fail_stuck" // ActionFailStuckSource fails a source wedged in seeding: its seed has not // made progress (heartbeat) for the stuck timeout, so the process running // it died. Its half-seeded volume is removed. ActionFailStuckSource ActionKind = "fail_stuck_source" // ActionRetryDestroy re-runs the teardown of a branch wedged in destroying // (a destroy that failed or was interrupted part-way). ActionRetryDestroy ActionKind = "retry_destroy" // ActionRestartBranch recreates the container/pod of a ready branch that // is gone or stopped for good, on the branch's existing volumes, and // journals the repair (ready -> ready) in the branch's history. If it does // not become ready the branch is failed; its volumes are kept. ActionRestartBranch ActionKind = "restart_branch" // ActionUpdateEndpoint records, and journals, the address a ready // branch's running container/pod now has (a new pod IP, a re-published // port). ActionUpdateEndpoint ActionKind = "update_endpoint" // ActionRemoveOrphanContainer removes a managed container/pod with no live // registry row. ActionRemoveOrphanContainer ActionKind = "remove_orphan_container" // ActionRemoveOrphanHelper removes a finished helper container/pod that // the process which ran it never removed (it died first). ActionRemoveOrphanHelper ActionKind = "remove_orphan_helper" // ActionGCLayer removes a frozen layer (volume + row) whose refcount is 0. ActionGCLayer ActionKind = "gc_layer" // ActionGCVolume removes a managed volume owned by no live branch/source. ActionGCVolume ActionKind = "gc_volume" )
type CopyUpOptions ¶
type CopyUpOptions struct {
// Root is the host directory the runtime creates volumes under: the
// docker driver's volume root (branchd --volume-root) or the kube
// hostPath data root. "" when the runtime manages volume storage itself
// (docker named volumes). The XFS hint below is only ever set on Root.
Root string
// CowExtSize is the XFS copy-on-write extent size hint, in bytes, set on
// Root when copy-up clones on XFS: new volumes inherit it, so the first
// write to a cloned block copies this much rather than the filesystem
// default (128 KiB with 4 KiB blocks). 0 leaves the default.
CowExtSize int64
// ProbeSize is the probe file's size (0 = cow.DefaultProbeSize).
ProbeSize int64
}
CopyUpOptions tunes DetectCopyUp.
type DestroyError ¶
type DestroyError struct {
Branch string
Reason string
// InUse: the runtime refused to remove something another user still
// holds (a volume another container mounts, a busy zfs dataset); the
// retry succeeds once it is released.
InUse bool
// RuntimeUnavailable: the container runtime could not be reached or did
// not answer in time; the retry succeeds once it is back.
Err error
}
DestroyError is what DestroyBranch returns when the teardown itself failed. The branch stays in destroying with Reason journaled (`pgb history`), and destroying it again, or reconcile's retry_destroy, retries the teardown. Reason is the journaled text; InUse and RuntimeUnavailable classify the cause so the API can answer with a status the caller can act on. Error() is the underlying error's text.
func (*DestroyError) Error ¶
func (e *DestroyError) Error() string
func (*DestroyError) Unwrap ¶
func (e *DestroyError) Unwrap() error
type DiffOption ¶
type DiffOption func(*diffOptions)
DiffOption tunes DiffBranch.
func WithDataSample ¶
func WithDataSample(n int) DiffOption
WithDataSample turns on bounded data sampling: for each table that grew (TableDelta.Grew), DiffBranch returns up to n branch-only rows (matched by primary key) in TableDelta.SampleRows. A non-positive n uses the default cap (20); n above MaxSampleRows is clamped to it. Tables without a primary key are skipped. Off by default.
func WithParentQuiesce ¶
func WithParentQuiesce() DiffOption
WithParentQuiesce lets DiffBranch briefly stop a csi branch's parent. On the csi backend a branch created from another branch has its parent's live PVC as its base, and cloning an in-use PVC is not crash-safe, so the diff's base clone stops the parent around it (CHECKPOINT, stop, clone, restart), exactly like a reset of the child does, dropping the parent's connections. Without this option DiffBranch refuses such a diff with ErrParentQuiesce. The API passes it for the operator role and above only, the role a reset needs. It changes nothing on other backends or for branches of a source.
type DiffResult ¶
type DiffResult struct {
SchemaDiff string `json:"schema_diff"`
Tables []TableDelta `json:"tables"`
}
DiffResult is what changed in a branch relative to its base: a unified schema diff (pg_dump --schema-only of base vs branch; empty = identical) and per-table row-count deltas.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
func NewWithPlanner ¶
func NewWithPlanner(reg *registry.Registry, drv runtime.Driver, defaultImage string, planner cow.Planner, opts ...Option) *Engine
NewWithPlanner selects the copy-on-write backend (branchd --cow).
func (*Engine) ApplyReconcile ¶
func (e *Engine) ApplyReconcile(ctx context.Context, now time.Time, stuckTimeout time.Duration) (ReconcilePlan, error)
ApplyReconcile computes a plan and executes it, returning the actions taken. It re-checks every destructive action against the live registry immediately before acting (safety: a branch may have been provisioned, a layer referenced, a volume claimed between planning and applying) and only ever touches pgoverlay-managed resources. Best-effort: an action that fails is recorded as an error but does not abort the pass.
func (*Engine) BranchUsage ¶
BranchUsage measures a branch's copy-on-write layer in bytes (the branch's own writes, not the shared source data). Overlay: the rw volume's size, counted with `du -sb` where copy-up copies, and as exclusive bytes with pgoverlay-du where it clones extents (see DetectCopyUp and usageHelpers); zfs: the clone's `used` property (space unique to the clone). It is a helper-container roundtrip — cheap, but not free.
func (*Engine) CopyUpMode ¶
func (e *Engine) CopyUpMode() cow.CopyUpMode
CopyUpMode is the copy-up mode DetectCopyUp found: unknown before it ran, or when it failed.
func (*Engine) CowModeCounts ¶
CowModeCounts reports how many ready overlay branches run in each copy-on-write mode (CowModes; a branch whose mode is not known is counted as unknown), for pgoverlay_branch_cow_mode. nil when the registry cannot be read, or on the zfs and csi backends, which have no such mode.
func (*Engine) CreateBranch ¶
func (e *Engine) CreateBranch(ctx context.Context, name, sourceName string, ttl time.Duration) (_ *registry.Branch, err error)
CreateBranch is a saga: every step registers a compensation that runs (in reverse order) if a later step fails. No orphans, ever. ttl 0 means the branch never expires.
func (*Engine) CreateBranchFrom ¶
func (e *Engine) CreateBranchFrom(ctx context.Context, name, parentName string, ttl time.Duration) (_ *registry.Branch, err error)
CreateBranchFrom creates a branch whose base is another (ready) branch's current state — branch-from-branch.
Overlay backend: a freeze saga. The parent's rw volume cannot be shared writable, so it is frozen into an immutable layer:
CHECKPOINT parent -> stop parent -> fresh parent rw volume -> restart parent on [frozen rw, …its old chain…, source] (wait ready) -> start child on the same chain -> commit (layer row + parent rw swap, atomic) -> child ready.
The parent gets a new container (and so possibly a new host port; the wire router resolves live, so dbname@parent connections just reconnect). On any failure before the commit the parent is restored to its original rw volume and chain and restarted; if even that fails it is marked failed — never half-frozen. The layer row is committed only after both restarts succeeded.
ZFS backend: block-level CoW — snapshot the parent's clone and clone that. No freeze, no stop, no layer rows.
CSI backend: the child's PVC is a clone of the parent's PVC. No freeze or layer rows either, but the parent is briefly stopped around the clone for crash consistency (see provisionCSI).
func (*Engine) DestroyBranch ¶
DestroyBranch tears a branch down: its container, its writable layer, the destroyed tombstone, then GC of any frozen layers and old source generations it was the last reference to.
It is idempotent and retryable. A branch wedged in creating/resetting is forced to failed first (fast-pathing reconcile's stuck handling); ready and failed branches move to destroying; a row already in destroying — an earlier destroy that failed or was interrupted — re-runs the teardown, every step of which tolerates already-gone resources. A teardown failure leaves the row in destroying with the cause journaled (pgb history) for the next attempt: another destroy call, or reconcile's retry_destroy (Registry.ListStuckDestroyingBranches); the error is then a *DestroyError.
func (*Engine) DetectCopyUp ¶
func (e *Engine) DetectCopyUp(ctx context.Context, opts CopyUpOptions) (cow.ProbeResult, error)
DetectCopyUp probes what OverlayFS copy-up costs on the filesystem that holds this engine's volumes (cow.ProbeCopyUp), remembers the answer for BranchUsage and reports it through CopyUpMode. When copy-up clones on XFS and opts asks for it, it also sets the copy-on-write extent size hint on opts.Root. Overlay backend only. A failed probe leaves the mode unknown (usage keeps counting with du -sb) and returns the error.
func (*Engine) DiffBranch ¶
func (e *Engine) DiffBranch(ctx context.Context, name string, opts ...DiffOption) (_ *DiffResult, err error)
DiffBranch reports what changed in a ready branch relative to its base — the state a reset would return it to. It provisions an internal throwaway branch ("diff-<6 hex>") from the target's OWN base — the recorded source volume/generation and frozen-layer chain, not the source's current generation — then runs pg_dump --schema-only and a row-estimate query inside both instances over the local socket (no credentials involved, so rotated branch passwords don't matter) and diffs host-side. The throwaway is a normal registry row (TTL'd, so the reaper cleans strays if branchd dies mid-diff) and is destroyed before returning, success or not. It counts toward --max-branches, so at the cap DiffBranch returns ErrQuotaExceeded. Expect a few seconds of wall time: a full branch provision plus two dumps.
zfs and csi children base on their parent's live volume, so their diff compares against the parent's CURRENT state (what a reset would re-clone). A csi child's diff briefly stops the parent around the clone exactly like a reset does, so it needs WithParentQuiesce (ErrParentQuiesce otherwise). A csi child whose parent is gone cannot be diffed (ErrBaseGone).
func (*Engine) PlanReconcile ¶
func (e *Engine) PlanReconcile(ctx context.Context, now time.Time, stuckTimeout time.Duration) (ReconcilePlan, error)
PlanReconcile computes the convergence plan WITHOUT mutating anything: it is the read-only half of reconcile, backing pgb doctor and GET /v1/reconcile/plan. now and stuckTimeout drive the TTL-reap, stuck-row and volume-age checks; the rest compares the registry with the runtime in both directions.
func (*Engine) ReapExpired ¶
ReapExpired destroys every ready/failed branch whose TTL has passed and returns the names destroyed. Retained as a thin primitive over the unified reconcile loop (see reconcile.go) for callers that only want the TTL pass; now is injected for testability. The reconcile loop in branchd folds this in.
func (*Engine) Reconcile ¶
func (e *Engine) Reconcile(ctx context.Context, now time.Time, stuckTimeout time.Duration, logf func(format string, args ...any)) (ReconcilePlan, error)
Reconcile converges the registry with reality in one pass: reaps TTL-expired branches, fails rows stuck in a transient state past stuckTimeout, repairs ready branches whose container is gone, stopped or moved, removes orphaned managed containers and helpers, and GCs dangling layers/volumes. It is the unified loop body branchd runs on a ticker (and once at startup); the CLI/REST doctor (plan) and gc (apply) call PlanReconcile/ApplyReconcile directly. logf (nil = silent) receives a one-line summary per pass.
func (*Engine) RecoverBranch ¶
RecoverBranch restarts a failed branch on the data it still has: a new container over its recorded writable layer and base (source volume plus frozen layer chain), with no re-clone, no masking and no credential rotation — the data is already masked and carries the branch's password (failed -> resetting -> ready).
This is the way back for a branch that failed with its data intact: a freeze or clone parent interrupted by a crash, restart or cancelled request (reconcile fails such a parent but keeps its volume), or a branch whose container was lost. It refuses, changing nothing, when the branch is not failed, when a child is still being created from it, or when its writable layer or base volumes are gone (reset or destroy it then). A failed restart removes the new container and returns the branch to failed; the data is never touched.
func (*Engine) RefreshBranchEndpoint ¶
RefreshBranchEndpoint re-reads a ready branch's address from the runtime, records it when it moved (journaled, like reconcile's update_endpoint), and returns the current "host:port". The Postgres router calls it after a dial to the recorded address fails, so a branch whose pod came back with a new IP (or container on a new port) is reachable at once instead of after the next reconcile pass. Within endpointRefreshInterval of the previous check for the same branch it returns the recorded address without asking the runtime.
func (*Engine) RefreshCowModes ¶
RefreshCowModes reads the copy-on-write mode of every ready overlay branch whose mode this process has not seen yet: the branches that were already running when branchd started. branchd runs it once at startup, in the background.
func (*Engine) RefreshSource ¶
RefreshSource re-seeds a source into a fresh generation volume. Existing branches keep the volume they were created from; only new branches see the new generation. The previous generation's volume is GC'd once no live branch references it. A failed seed leaves the current generation intact.
func (*Engine) RemoveSource ¶
RemoveSource deletes a source's volume, its orphaned frozen layers, and the registry rows. Refused while any live branch still uses the source or (defensively) while any layer is still referenced.
func (*Engine) ResetBranch ¶
ResetBranch throws away a branch's writes and reprovisions it from its recorded base (source volume plus frozen layer chain) on the same registry row (ready|failed -> resetting -> ready; new container id and host port). Resetting a failed branch is how a failed create is retried, and how a branch whose data is gone is brought back; RecoverBranch instead restarts a failed branch on the data it still has.
func (*Engine) RunReconcile ¶
func (e *Engine) RunReconcile(ctx context.Context, interval, stuckTimeout time.Duration, logf func(format string, args ...any))
RunReconcile runs Reconcile on a ticker until ctx is done; branchd's single background loop. It runs one pass immediately so startup drift converges without waiting a full interval.
type Option ¶
type Option func(*Engine)
Option configures optional engine behavior at construction time.
func WithCredentialRotation ¶
func WithCredentialRotation() Option
WithCredentialRotation turns on per-branch credential rotation: every branch create and reset generates a fresh password, applies it inside the branch and stores it on the branch row (returned by the API as `password`).
func WithHeartbeatInterval ¶
WithHeartbeatInterval sets how often a running saga bumps its branch rows' (and a running seed its source row's) updated_at so reconcile never mistakes a slow-but-alive operation for an abandoned one. Keep it well under the stuck timeout (branchd uses a quarter of --stuck-timeout, capped at defaultHeartbeat). d <= 0 keeps the default.
func WithLazyRW ¶
WithLazyRW switches the lazyrw shim on (the default) or off for overlay branches: the entrypoint gets PGOVERLAY_LAZYRW=on|off. Branches pick a change up on their next start. branchd --lazyrw, pgb $PGOVERLAY_LAZYRW. zfs and csi branches never use it: their clones copy blocks, not files.
func WithMaxBranches ¶
WithMaxBranches caps the number of live (non-destroyed) branches. The create paths return ErrQuotaExceeded once the cap is reached. 0 (the default) is unlimited. branchd --max-branches / PGOVERLAY_MAX_BRANCHES.
func WithMaxLayerDepth ¶
WithMaxLayerDepth caps overlay layer chains at n frozen layers: branching from a branch whose chain is already that deep is refused with ErrQuotaExceeded. branchd --max-layer-depth. n <= 0 keeps the default.
func WithMetrics ¶
WithMetrics attaches a metrics sink the engine uses to observe saga durations/errors, masking duration, in-flight ops and reaper/reconcile counters. nil is accepted (every metric call is nil-safe).
func WithSeedSettle ¶
func WithSeedSettle(m pgctl.SettleMode) Option
WithSeedSettle sets how every seed (source add and refresh) is prepared before branches start from it: pgctl.SettleFreeze (the default) recovers the copy, freezes and analyzes it and shuts it down cleanly, pgctl.SettleRecover only recovers and shuts down, pgctl.SettleOff leaves it as the seed command wrote it. branchd --seed-settle, pgb $PGOVERLAY_SEED_SETTLE. "" keeps the default.
func WithTTLPolicy ¶
WithTTLPolicy sets the create-time TTL policy: defaultTTL is used when a create requests no TTL (0 = no default, never expires); maxTTL caps any requested TTL (0 = no cap). branchd --default-ttl / --max-ttl. The policy is applied in the engine create path so both API- and ghook-created branches inherit it.
func WithWALRecycle ¶
WithWALRecycle(false) starts overlay branches with -c wal_recycle=off, so a WAL segment that came from the seed is removed instead of being renamed (which copies it up) when a checkpoint recycles it. Experimental; on (the Postgres default) unless set. branchd --wal-recycle, pgb $PGOVERLAY_WAL_RECYCLE.
type ReconcilePlan ¶
type ReconcilePlan struct {
Actions []Action `json:"actions"`
}
ReconcilePlan is the set of convergence steps a pass intends (or, after apply, took). It is computed read-only and can be reported (pgb doctor / GET /v1/reconcile/plan) or applied (pgb gc / POST /v1/reconcile). Drift reports true when the plan is non-empty.
func (ReconcilePlan) Drift ¶
func (p ReconcilePlan) Drift() bool
Drift reports whether the plan found anything to converge.
type TableDelta ¶
type TableDelta struct {
// Schema is the table's schema and Table its name within it: same-named
// tables in different schemas are separate entries.
Schema string `json:"schema"`
Table string `json:"table"`
BaseRows int64 `json:"base_rows"`
BranchRows int64 `json:"branch_rows"`
Delta int64 `json:"delta"`
// RowsUnknown is set when either side's count is unknown (UnknownRows);
// Delta is then 0 and carries no information.
RowsUnknown bool `json:"rows_unknown,omitempty"`
// SampleRows is a bounded set of branch-only rows (present on the branch,
// absent on the base, matched by primary key) — populated only when the
// diff is requested with data sampling (engine.WithDataSample) and only for
// tables that grew (see Grew). Tables with no primary key are skipped
// (sampling needs a stable key to diff by).
SampleRows []map[string]any `json:"sample_rows,omitempty"`
}
TableDelta is one table's row-count comparison between a branch and its base. Counts are planner estimates (pg_class.reltuples), not exact counts, with one exception: a table the planner has no estimate for on either side (never analyzed or vacuumed, reltuples -1) is counted exactly on both sides where its heap is small (at most 64 MiB). A side that is still unknown after that reports UnknownRows, RowsUnknown is set and Delta is 0 — never a made-up 0 row count. A table present on one side only counts 0 on the other.
func (TableDelta) Cells ¶
func (t TableDelta) Cells() (base, branch, delta string)
Cells renders the counts for display: "?" for an unknown side or delta, "0" for no change, a signed delta otherwise.
func (TableDelta) Grew ¶
func (t TableDelta) Grew() bool
Grew reports whether the branch holds more rows than the base, as far as the counts can tell (unknown counts never classify as grown). Data sampling covers exactly these tables.
func (TableDelta) Name ¶
func (t TableDelta) Name() string
Name is the table's display name: bare in the public schema, otherwise schema-qualified.