sentinel

package
v0.17.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 6, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package sentinel implements the "sentinel table" cutover gate shared by the migration and move runners.

When a migration/move is run with deferred cutover, spirit creates a small marker table (the sentinel) and then blocks the final cutover until an operator drops it. While blocked, a continuous checksum re-verifies the copied data so a long human-paced wait does not let the shadow copy drift.

Both runners previously carried near-identical copies of this logic; the only differences were which connection/schema the sentinel lives on and the two runner-specific callbacks (RunChecksum, InvalidateWatermark), which are injected into Wait rather than reimplemented here.

Index

Constants

View Source
const TableName = "_spirit_sentinel"

TableName is the fixed name of the sentinel table. It is intentionally a constant (not derived from the migrated table) so an operator always knows which table to drop to release a deferred cutover.

Variables

View Source
var (
	WaitLimit     = 48 * time.Hour
	CheckInterval = 1 * time.Second
)

WaitLimit bounds how long Wait blocks for the sentinel to be dropped before giving up; CheckInterval is the existence-probe period. They are package vars (not consts) only so tests can shorten them; production never overrides them. Keeping the timing here makes the cutover wait identical across every caller (migrate, move).

Functions

func Create

func Create(ctx context.Context, db *sql.DB) error

Create creates the sentinel table in db's currently-selected schema if it does not already exist. The schema is taken from the connection (the table name is unqualified) rather than passed in, so this works under Vitess where a fixed schema name isn't meaningful — point db at the right keyspace/schema. Creation must be idempotent: the name is a constant, a resumed migration recreates it, and TestSentinelCreateNeverObservedAbsent relies on CREATE IF NOT EXISTS so a concurrent existence probe never sees it missing.

func Exists

func Exists(ctx context.Context, db *sql.DB) (bool, error)

Exists reports whether the sentinel table is present in db's currently-selected schema (DATABASE()), so it tracks Create's unqualified target rather than a passed-in schema name.

func Wait

func Wait(ctx context.Context, cfg WaitConfig) (retErr error)

Wait blocks until the sentinel table is dropped (proceed with cutover), WaitLimit elapses (error), or the continuous checksum exits on its own (error). It returns nil only when it is safe to proceed with cutover. If the sentinel is already absent on entry, it returns nil immediately — no checksum goroutine is spawned and InvalidateWatermark is not called.

Otherwise it runs RunChecksum in the background for the lifetime of the wait, and on every subsequent return path (sentinel dropped, timeout, checksum failure, or parent-context cancellation) it stops that goroutine and then calls InvalidateWatermark: if the continuous checksum repaired a chunk, the run is about to abort, and the persisted checksum_watermark must be blanked so a resume re-verifies rather than trusting a watermark recorded just before the difference was found.

Exists, RunChecksum and InvalidateWatermark are required (Wait returns an error if any is nil); a nil Logger defaults to slog.Default().

Types

type WaitConfig

type WaitConfig struct {
	// Exists probes whether the sentinel table still exists. It is called once
	// up front (an absent sentinel means "proceed immediately") and then once
	// per CheckInterval.
	Exists func(ctx context.Context) (bool, error)

	// RunChecksum runs the continuous checksum for the duration of the wait.
	// It is spawned in its own goroutine with a child context that Wait
	// cancels on return, and is expected to filter a benign cancellation of
	// that context to a nil error; any non-nil return is treated as a real
	// verification failure and aborts the wait.
	RunChecksum func(ctx context.Context) error

	// InvalidateWatermark is invoked exactly once, after the continuous
	// checksum goroutine has fully stopped, to blank any persisted checksum
	// watermark if the checksum repaired a difference (so a resume re-verifies
	// from the start of the checksum phase). It runs even when the parent
	// context was cancelled.
	InvalidateWatermark func(ctx context.Context) error

	Logger *slog.Logger
}

WaitConfig holds the dependencies of Wait. Exists, RunChecksum and InvalidateWatermark are required; Logger is optional (a nil Logger defaults to slog.Default()). The poll/timeout timing comes from the package-level WaitLimit / CheckInterval so it is identical across callers.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL