Documentation
¶
Overview ¶
Package checksum verifies that a copy of a table matches its source while the source keeps taking writes. It is not in the row/ package because it requires a change feed to be passed in, which would cause a circular dependency.
There are two checkers. LocklessChecker (CheckerConfig.Lockless) takes no locks, spans servers, and is what move and sync use. SingleChecker, the default, compares two tables on one server under a snapshot taken behind a brief table lock. The intent is for lockless to replace it, so Single-only code is kept in the single*.go files, which can then be deleted whole.
SingleChecker compares two tables on the same MySQL server under a REPEATABLE READ snapshot taken behind a brief table lock. It is the default checker (CheckerConfig.Lockless unset), and is expected to be replaced by LocklessChecker. Everything only it uses lives in the single*.go files so that removing it is a matter of deleting them and the branch in NewChecker.
Index ¶
- Constants
- Variables
- func IsReproducible(err error) bool
- func StatusRow(c Checker) string
- func StatusSuffix(c Checker) string
- func StatusSummary(c Checker) string
- type AutoscaleConfig
- type Checker
- type CheckerConfig
- type ChecksumStatus
- type LocklessChecker
- func (c *LocklessChecker) ChecksumStatus() ChecksumStatus
- func (c *LocklessChecker) ContinuousActive() bool
- func (c *LocklessChecker) DifferencesFound() uint64
- func (c *LocklessChecker) ExecTime() time.Duration
- func (c *LocklessChecker) FirstCleanPass() <-chan struct{}
- func (c *LocklessChecker) GetProgress() status.ChecksumProgress
- func (c *LocklessChecker) ResumeWatermark() (string, error)
- func (c *LocklessChecker) Run(ctx context.Context) error
- func (c *LocklessChecker) RunContinuous(ctx context.Context) error
- func (c *LocklessChecker) RunUntilClean(ctx context.Context) error
- func (c *LocklessChecker) SetThrottler(t throttler.Throttler)
- func (c *LocklessChecker) StartTime() time.Time
- func (c *LocklessChecker) Stats() LocklessCheckerStats
- type LocklessCheckerStats
- type MockChecker
- func (*MockChecker) ContinuousActive() bool
- func (m *MockChecker) DifferencesFound() uint64
- func (*MockChecker) ExecTime() time.Duration
- func (*MockChecker) GetProgress() status.ChecksumProgress
- func (m *MockChecker) ResumeWatermark() (string, error)
- func (m *MockChecker) Run(context.Context) error
- func (m *MockChecker) RunContinuous(ctx context.Context) error
- func (m *MockChecker) SetDifferencesFound(n uint64)
- func (*MockChecker) SetThrottler(throttler.Throttler)
- func (*MockChecker) StartTime() time.Time
- type Paced
- type Recopier
- type SingleChecker
- func (c *SingleChecker) ChecksumChunk(ctx context.Context, trxPool *dbconn.TrxPool, chunk *table.Chunk) error
- func (c *SingleChecker) ChunkSize() uint64
- func (c *SingleChecker) ContinuousActive() bool
- func (c *SingleChecker) DifferencesFound() uint64
- func (c *SingleChecker) ExecTime() time.Duration
- func (c *SingleChecker) GetProgress() status.ChecksumProgress
- func (c *SingleChecker) IsThrottled() bool
- func (c *SingleChecker) ResumeWatermark() (string, error)
- func (c *SingleChecker) Run(ctx context.Context) error
- func (c *SingleChecker) RunContinuous(ctx context.Context) error
- func (c *SingleChecker) SetThrottler(t throttler.Throttler)
- func (c *SingleChecker) StartTime() time.Time
- func (c *SingleChecker) Threads() int
- type StatusReporter
Constants ¶
const ( DefaultLocklessMaxQueueSize = 1024 // DefaultLocklessMaxHotAttempts bounds how long one continuously // changing chunk can hold a pass open. The initial read counts as attempt // one. A deferred hot chunk makes the pass ineligible to be clean and is // visited again from a fresh chunk walk on the next pass. DefaultLocklessMaxHotAttempts = 10 // DefaultLocklessMaxPasses bounds RunUntilClean. Ten full walks is well // past the point where a table that is going to converge has converged — // the common case is one pass, and a repair costs one more — so reaching // the bound means the table is not converging rather than that it needs // longer. Only the finite gate is bounded; continuous Run passes forever // by design. DefaultLocklessMaxPasses = 10 )
Default values applied for zero-valued config fields. Exported so callers can reference them when tuning.
const ( // DefaultConcurrency is the worker count every checker starts at when the // caller does not choose one. Four readers is enough to keep a chunk in // flight per available connection on a small instance without being a // meaningful share of a large one's capacity; the autoscaler moves it from // there when it is enabled. DefaultConcurrency = 4 )
Variables ¶
var ( // ErrDifferencesExhausted is returned by Run when the attempts kept finding // row differences: every attempt completed and found them, or, after an // attempt that errored, at least two completed attempts in a row did, so a // completed attempt re-read a repair and found the difference again. The // table is diverging in a way the repairs cannot close, so a further // attempt reproduces it: a lossy ALTER (adding a UNIQUE index to // non-unique data being the common one), or a bug. Callers that decide // whether to retry should not. ErrDifferencesExhausted = errors.New("checksum kept finding differences") // ErrAttemptsExhausted is returned by Run when the attempts ran out and at // least one of them errored before it could compare the whole table, and // fewer than two completed attempts followed the last such error — // killed connections, a cancelled context, a failure inside a pass. Any // other attempt found and repaired differences, but an errored attempt // says nothing about whether a repair held, so nothing has been proven // about the data, and the condition may well be gone by the next attempt. // It wraps the most recent attempt error, which is the one worth triaging. ErrAttemptsExhausted = errors.New("checksum attempts errored before the table was verified") // ErrPermanentDivergence is returned by RunContinuous when it confirms a // difference: the source is not racing, and (for LocklessChecker) the // mismatch survived a full drain of every feed, so it is not apply lag. // RunContinuous never repairs, because it runs while a cutover may be // imminent; the caller aborts, and the resumed run's initial Run repairs // the range. Run never returns it, since Run repairs what it finds. ErrPermanentDivergence = errors.New("checksum: permanent divergence detected") )
var ( // LocklessMinPassInterval is RunContinuous's pacing for every checker: // the minimum time between passes, so a small table whose // pass finishes in seconds doesn't re-scan back-to-back during a possibly // days-long sentinel wait. It is not configurable: every caller runs // continuous verification at the same pace. LocklessMinPassInterval = 1 * time.Hour // DefaultLocklessRetryDelay is the constructor default for RetryDelay: the // wait before re-reading a mismatched chunk. It is short because it is not // what keeps apply lag from being mistaken for divergence: a retry also // waits for the change feed to flush (see gateOnFlush), and a stable // mismatch drains the feed and re-reads before any verdict (see // executeWork). With a feed it is only a floor; without one it is the // whole wait. DefaultLocklessRetryDelay = 5 * time.Second // DefaultLocklessRetryFlushWait is the constructor default for // RetryFlushWait: two *default* flush intervals, so a periodic flush lands // inside it even when the flush before it ran long. A caller whose feeds // flush on another interval sets RetryFlushWait from it (spirit sync does, // from --flush-interval); otherwise the cap expires first and retries fall // back to RetryDelay alone. DefaultLocklessRetryFlushWait = 2 * change.DefaultFlushInterval )
Shared lockless-checksum pacing. Vars (not consts) so tests can shorten them; production never overrides them. Keeping them here makes the pacing identical across every caller (migrate, sync).
var ( // ErrYieldTimeout is returned by runChecksum when the yield timeout expires. // This is distinct from the parent context being canceled, and signals that // the checksum should resume from the current watermark after releasing // long-running transactions to reduce HLL (history list length) growth. ErrYieldTimeout = errors.New("checksum yield timeout") // DefaultYieldTimeout is the default maximum duration for a single checksum // pass before yielding to release long-running REPEATABLE READ transactions. DefaultYieldTimeout = 24 * time.Hour )
var ErrVerificationUnresolved = errors.New("checksum: verification did not converge within the pass budget")
ErrVerificationUnresolved is returned by RunUntilClean when MaxPasses passes have completed and none of them was clean — every pass still ended with ranges that were repaired, or that were changing too fast to verify. Nothing is proven about those ranges, so the caller must not cut over; but neither is a divergence proven, so this is deliberately distinct from ErrPermanentDivergence. It is the optimistic counterpart of ErrDifferencesExhausted: a bound that makes the run terminate instead of re-walking the table forever. When the budget spans more than one pass and every pass repaired at least one range, the divergence reproduced after each repair, and the error wraps ErrDifferencesExhausted as well. Settling (lockless_settle.go) resolves most hot rows before they get that far; what reaches this bound is a range that could not even be settled — see "Continuously updated hot rows" in the package README for the cases where that happens.
Functions ¶
func IsReproducible ¶ added in v0.19.0
IsReproducible reports whether err is a checksum verdict about the data: one that checking the same data again would reach again. It covers ErrDifferencesExhausted (a completed attempt found differences again after the one before it repaired them, which includes an ErrVerificationUnresolved whose every pass repaired) and ErrPermanentDivergence (a confirmed difference). The error may be wrapped.
One exception rests on a single observation: SingleChecker.Run with MaxRetries set to 1 reports ErrDifferencesExhausted after one attempt that found and repaired differences, which no later attempt re-read. A caller that relies on this verdict leaves MaxRetries at its default.
A bare ErrVerificationUnresolved is not reproducible. The pass budget ran out with ranges that were changing too fast to verify, or that a single repair did not settle, but no divergence is proven, and a later attempt against a quieter table can pass. ErrAttemptsExhausted is not reproducible either: an attempt that errored proved nothing about the data.
Reproducible is about re-reading the same data, not about what a caller's retry does to it: RunContinuous reports ErrPermanentDivergence without repairing, and the initial Run of a resumed migration repairs that range. Whether such a retry is worth making is the caller's decision.
func StatusSuffix ¶ added in v0.16.0
StatusSuffix renders the pacing fields for the checksum row of a runner status block, or "" if the checker does not report them. It keeps the leading two spaces used between fields within a row, so callers can append it unconditionally.
func StatusSummary ¶ added in v0.18.0
StatusSummary formats rich status when supported, otherwise basic progress.
Types ¶
type AutoscaleConfig ¶ added in v0.16.0
type AutoscaleConfig struct {
Enabled bool
// MaxThreads is the ceiling scaling may reach. The transaction pools are
// provisioned at this size whether or not Enabled is set, so callers must
// budget connections for it (see SingleChecker.initConnPool for why the
// pools cannot grow on demand). Values below Concurrency are raised to it.
MaxThreads int
}
AutoscaleConfig controls the checksum phase's worker-count control loop. It mirrors copier.AutoscaleConfig, minus a StartThreads field — the checksum starts at CheckerConfig.Concurrency.
Enabled only turns on *scaling*. The throttler hard-stop applies either way: a checksum with autoscaling disabled still pauses when the throttler says to, which before this existed it did not do at all.
type Checker ¶
type Checker interface {
// SetThrottler installs pacing before Run. Every finite checker supports it.
SetThrottler(throttler.Throttler)
// ResumeWatermark returns safe verification progress, or an empty string when
// a resumed run must recheck everything. Read it instead of the walker watermark.
ResumeWatermark() (string, error)
// Run performs finite verification, and it repairs: a chunk that differs
// is rewritten from the source through the configured Applier and
// re-verified by a later pass. A nil result authorizes completion;
// deferred ranges and repairs alone are not verification.
Run(ctx context.Context) error
// RunContinuous reuses a successfully completed checker for background
// verification, and it never repairs: a confirmed difference returns
// ErrPermanentDivergence. A resumed run's initial Run is what repairs it.
// Calls to Run and RunContinuous must be sequential. A nil return means
// cancellation was safe, not that the interrupted pass verified every row.
// Other errors abort cutover. Once started, ResumeWatermark stays empty:
// restarting requires full initial verification.
RunContinuous(context.Context) error
// ContinuousActive distinguishes a running pass from interval pacing.
// It is safe to query concurrently with RunContinuous.
ContinuousActive() bool
// GetProgress returns the structured checksum progress — rows verified so far
// and the total to verify. Call String() on the result for the display form.
GetProgress() status.ChecksumProgress
StartTime() time.Time
ExecTime() time.Duration
// DifferencesFound reports observed mismatches, including transient ones.
// Snapshot checkers count the current pass; optimistic verification counts
// across passes within Run. This is not resume evidence: use ResumeWatermark.
DifferencesFound() uint64
}
func NewChecker ¶
func NewChecker(sourceDBs []*sql.DB, chunker table.Chunker, feeds []change.Source, config *CheckerConfig) (Checker, error)
NewChecker creates a new checksum object. CheckerConfig.Lockless picks which one, and nothing else does; the zero value is SingleChecker.
sourceDBs contains the source database connections (one for single-source migrations, multiple for N:M moves), each paired with the feed at the same index. Lockless aggregates checksums across all of them; Single accepts exactly one. For Single the copy being verified lives on sourceDBs[0] too; for Lockless it is wherever resolveLocklessTargets says — TargetDB (how a sync verifies across two servers), else the applier's targets, else sourceDBs[0]. Open the chunker before construction unless supplying Watermark, in which case the factory opens it according to policy.
type CheckerConfig ¶
type CheckerConfig struct {
// Lockless selects LocklessChecker; false (the zero value) selects
// SingleChecker. It is the only thing that selects a checker — the Applier
// is a repair write path, not a switch. Lockless is the only one that
// spans servers: a sync (TargetDB), and a move, which reads N sources
// against the applier's M targets and aggregates each chunk across all of
// them. SingleChecker is expected to be removed in its favour.
Lockless bool
Concurrency int
DBConfig *dbconn.DBConfig
Logger *slog.Logger
// Watermark is verification evidence from a previous run: every row below
// it was read on both sides and observed equal. Supplying it makes the
// factory open the chunker there, so verification resumes rather than
// restarting; leave the chunker unopened when supplying it. Every checker
// honours it — the claim it encodes does not depend on which checker
// observed it. Take it from Checker.ResumeWatermark, never from the
// chunker's traversal watermark.
Watermark string
// MaxRetries bounds whole-run attempts for every checker: a transient
// infrastructure failure costs an attempt rather than the migration.
MaxRetries int
// Applier is the write path Run rewrites a mismatched chunk through. It is
// the caller's to supply because every runner already has one, and building
// a second here would hide which one a repair actually goes through.
//
// Required: Run always repairs, and a checker that cannot repair should
// fail to build, not on the first mismatch hours in. A repair starts and
// stops it around each rewrite, since repairs are rare and serialized.
// Lockless also reads GetTargets from it to find the copy being verified
// (see resolveLocklessTargets), which is how a move names its M targets.
Applier applier.Applier
// Throttler paces the checksum. Optional: nil installs a Noop, and callers
// that build the checker before their throttlers are open should use
// SetThrottler instead (the migration runner does).
//
// Whatever is passed is narrowed by loadOnlyThrottler — a checksum reacts to
// load signals and ignores binary ones such as replica lag.
Throttler throttler.Throttler
// Autoscale configures the worker-count control loop.
Autoscale AutoscaleConfig
// MetricsSink is where the control loop reports its gauges. Optional.
MetricsSink metrics.Sink
// YieldTimeout is the maximum duration for a single checksum pass before
// yielding to release long-running transactions. Lockless reads are short
// by construction and hold no snapshot to yield.
YieldTimeout time.Duration
// RetryDelay is the minimum wait between attempts for any given chunk —
// measured from the *last* attempt of that chunk, not from the original
// failure. Default DefaultLocklessRetryDelay (5s). It is also what paces
// the re-walk between finite passes.
RetryDelay time.Duration
// RetryFlushWait bounds how long a retry additionally waits for every
// change feed to complete a flush since the attempt that queued it. The
// target only moves when a feed flushes, so a retry before then re-reads
// an image it has already seen. It is an upper bound, not a delay: the
// retry runs as soon as every feed has flushed (and RetryDelay has
// passed), and without feeds there is no wait at all. Default
// DefaultLocklessRetryFlushWait (2 × change.DefaultFlushInterval), which
// assumes the default flush interval: a caller whose feeds flush less
// often should set it to twice their interval.
RetryFlushWait time.Duration
// MaxQueueSize is the cap on entries in the delayed-retry queue. Reaching
// it stalls the walker until retries drain rather than failing the run.
// Default 1024.
MaxQueueSize int
// MaxHotAttempts is the number of observations allowed for a chunk whose
// source signature keeps changing. Once reached, the chunk is deferred to
// the next pass rather than holding the current pass open forever. Default
// 10; a positive value below 2 is clamped to 2 because detecting a source
// change requires an initial read and a retry. Deferral never counts as
// verification and makes the pass not clean.
MaxHotAttempts int
// MaxPasses bounds Run and RunUntilClean: once this many passes have
// completed without one of them being clean, verification gives up with
// ErrVerificationUnresolved rather than walking the table again. Zero means
// unbounded, which is what RunContinuous always is; NewChecker defaults it
// to DefaultLocklessMaxPasses so the finite gate terminates.
MaxPasses int
// TargetDB is the server holding the copy being verified, when that is not
// the server being read from. Nil — the usual case — means the target lives
// on sourceDBs[0], which is true of a migration (the shadow table sits
// beside the original). A sync verifies two servers, so it supplies one.
//
// Setting it also changes the shape of a repair: a cross-server rewrite has
// no ColumnMapping to apply, because both sides hold the same logical
// table. See newRecopier.
TargetDB *sql.DB
// ExternalFlushLoop says the caller runs the feed's periodic flush itself,
// for longer than any one run, so a run must not start and stop it. The
// default (false) is that the checker owns flushing for the duration of a
// run, which is what a migration and a move want: nothing else is driving
// the feed while verification is the only thing happening.
//
// A sync is the opposite — its flush loop runs for the whole process at its
// own configured interval, and a verification pass stopping it on the way
// out would be stopping someone else's goroutine.
ExternalFlushLoop bool
// contains filtered or unexported fields
}
func NewCheckerDefaultConfig ¶
func NewCheckerDefaultConfig() *CheckerConfig
type ChecksumStatus ¶ added in v0.18.0
type ChecksumStatus struct {
Progress status.ChecksumProgress
Optimistic *LocklessCheckerStats
}
ChecksumStatus separates optimistic traversal from completed verification. Optimistic is a point-in-time copy, not mutable checker state.
func (ChecksumStatus) String ¶ added in v0.18.0
func (s ChecksumStatus) String() string
type LocklessChecker ¶ added in v0.18.0
type LocklessChecker struct {
// contains filtered or unexported fields
}
LocklessChecker is the optimistic checker. It satisfies the whole Checker contract natively: Run verifies the table once and returns, RunContinuous verifies it forever in the background, and both drive the same pass loop. Construct it through NewChecker with CheckerConfig.Lockless set, naming a TargetDB when the copy being verified is on another server.
Run and RunContinuous must not overlap; everything else — Stats, FirstCleanPass, ResumeWatermark, the status accessors — is safe to call concurrently with either.
func (*LocklessChecker) ChecksumStatus ¶ added in v0.18.0
func (c *LocklessChecker) ChecksumStatus() ChecksumStatus
ChecksumStatus reports rows verified rather than rows walked. The chunker advances on feedback, and this checker gives feedback only for a chunk that resolved clean (see feedbackResolved), so its progress is verification progress. Before that was so, this reported 0 until the first clean pass and then jumped to the whole table, which read as a stalled checksum for the entire run.
func (*LocklessChecker) ContinuousActive ¶ added in v0.18.0
func (c *LocklessChecker) ContinuousActive() bool
ContinuousActive distinguishes a running pass from the interval pacing between passes.
func (*LocklessChecker) DifferencesFound ¶ added in v0.18.0
func (c *LocklessChecker) DifferencesFound() uint64
DifferencesFound returns the lifetime number of chunks that mismatched on their initial (fresh-walk) read — i.e. Stats().MismatchesDetected. It exists so a LocklessChecker can be consumed through the same minimal "has this checker observed any divergence?" view the migration runner uses to gate checkpoint-watermark persistence (DumpCheckpoint / invalidateChecksumWatermark), matching the Checker.DifferencesFound semantics of the SingleChecker. A transient mismatch that later reconciles on retry still counts here, so the gate stays conservative: any hint of divergence blanks the persisted watermark and forces re-verification on resume.
func (*LocklessChecker) ExecTime ¶ added in v0.18.0
func (c *LocklessChecker) ExecTime() time.Duration
ExecTime is how long the current run has been going, or how long the last one took.
func (*LocklessChecker) FirstCleanPass ¶ added in v0.18.0
func (c *LocklessChecker) FirstCleanPass() <-chan struct{}
FirstCleanPass returns a channel that is closed the first time a pass completes with every chunk READ-verified equal and zero recopies. A pass containing a recopy does not qualify: the repaired rows were never observed equal, so the signal waits for a follow-up pass that re-reads them (and everything else) with no repairs needed. The signal is monotonic: once closed it stays closed. Callers that need a "data is known consistent" gate should select on this channel. Safe to call concurrently with Run.
func (*LocklessChecker) GetProgress ¶ added in v0.18.0
func (c *LocklessChecker) GetProgress() status.ChecksumProgress
func (*LocklessChecker) ResumeWatermark ¶ added in v0.18.0
func (c *LocklessChecker) ResumeWatermark() (string, error)
ResumeWatermark returns the verified-clean prefix of the table for the walk now in progress: every row below it was read on both sides and observed equal, so a resumed run may start there instead of at the beginning. It is the direct consequence of feedbackResolved — see that method for why the chunker's low watermark carries this meaning at all.
A second pass resets the chunker and so resets this to nothing. The prefix the first pass verified is deliberately not carried over: the second pass exists because the first one repaired or deferred something, and if it now fails to re-verify a range the first pass verified, that range stopped being equal — exactly the case where reporting the older, further-along answer would let a resume skip the damage.
Continuous verification reports nothing: it never finishes, so there is no run to resume, and a restart requires full initial verification.
Unlike the snapshot checker there is no "any difference found ⇒ no evidence" gate here, and there does not need to be. Optimistic reads mismatch routinely on a table taking writes, and almost all of those resolve on retry; gating on the mismatch counter would discard the watermark on essentially every real migration. What makes the prefix trustworthy instead is that a chunk is reported to the chunker only once it has resolved clean.
A watermark that is not yet available (no chunk has resolved) is reported as an empty string rather than an error: nothing has resolved, so there is nothing to persist, and that must not stop the caller writing the rest of its checkpoint.
func (*LocklessChecker) Run ¶ added in v0.18.0
func (c *LocklessChecker) Run(ctx context.Context) error
Run verifies the whole table and returns, which is the finite half of the Checker contract. A nil result authorizes completion: it means a pass walked every chunk and needed no repairs and deferred nothing. Repairs and deferred ranges are not verification, so a pass containing either is followed by another one (bounded by MaxPasses, after which it gives up with ErrVerificationUnresolved).
A failed attempt is retried up to MaxRetries times when a fresh attempt could plausibly survive the failure. This mirrors SingleChecker.Run, and for the same reason: a checksum is the last thing standing between a migration and a cut-over, and a pool of connections killed mid-pass (or any other transient infrastructure failure) should not fail the migration outright. The verdicts that are *about the data* (IsReproducible) are not retried, because repeating the read would reach the same conclusion, and neither is ErrVerificationUnresolved, whose attempt already spent the pass budget (see locklessRetryable).
func (*LocklessChecker) RunContinuous ¶ added in v0.18.0
func (c *LocklessChecker) RunContinuous(ctx context.Context) error
RunContinuous verifies the table in the background for as long as ctx lives, which is the continuous half of the Checker contract. It is the same pass loop as Run with three differences: there is no pass budget, because the point is to keep verifying; nothing is repaired, so a confirmed divergence returns ErrPermanentDivergence; and a cancellation is reported as nil, because a background verifier being shut down is not a failure. Any error it returns should abort a cutover.
Following a finite run, the first continuous pass waits LocklessMinPassInterval rather than re-walking the table immediately behind the pass that just verified it.
func (*LocklessChecker) RunUntilClean ¶ added in v0.18.0
func (c *LocklessChecker) RunUntilClean(ctx context.Context) error
RunUntilClean is one attempt of Run: it returns as soon as a complete pass has no repairs and no deferred ranges, without the whole-run retry loop. It joins all workers before returning. Cancellation is an error, never evidence of verification.
func (*LocklessChecker) SetThrottler ¶ added in v0.18.0
func (c *LocklessChecker) SetThrottler(t throttler.Throttler)
SetThrottler installs pacing before a run. It is called during runner setup, because a runner usually opens its throttlers after it builds the checker.
func (*LocklessChecker) StartTime ¶ added in v0.18.0
func (c *LocklessChecker) StartTime() time.Time
StartTime is when the current (or last) run began.
func (*LocklessChecker) Stats ¶ added in v0.18.0
func (c *LocklessChecker) Stats() LocklessCheckerStats
Stats returns a point-in-time snapshot of the checker's counters. Safe to call concurrently with Run.
type LocklessCheckerStats ¶ added in v0.18.0
type LocklessCheckerStats struct {
// PassesCompleted is the number of passes finished so far. A pass
// completes when every chunk has resolved (READ-verified, recopied, or
// explicitly deferred as continuously hot);
// only a pass with zero recopies counts as clean for the
// FirstCleanPass signal.
PassesCompleted uint64
// CurrentPass is the 1-indexed active or most recently completed pass
// (0 before the first pass starts).
CurrentPass uint64
// NextPassAt is the scheduled start while waiting between passes; zero otherwise.
NextPassAt time.Time
// ChunksThisPass is how many chunks the walker has emitted in the
// current pass, including split parents and their subsequently emitted children.
ChunksThisPass uint64
// ChunksPassedThisPass is how many chunks have gone clean in the
// current pass (either initially or via retry). Split parents are excluded,
// so this is not a completion numerator over ChunksThisPass.
ChunksPassedThisPass uint64
// ProgressBasisPoints estimates how far the chunker has walked through the
// current pass, from 0 to 10000. Chunk sizes adapt while the pass runs, so
// ChunksThisPass is only the number emitted so far and can never be an
// honest denominator. Chunker.Progress supplies a stable-enough fraction
// over keyspace distance or estimated rows, depending on the chunker.
ProgressBasisPoints uint64
// ScanComplete means the walker exhausted the current pass successfully.
// Retries, in-flight reads, repairs, or deferred ranges may still prevent
// verification; an estimate of 100% does not imply ScanComplete.
ScanComplete bool
// MismatchesThisPass is how many chunks mismatched on their initial
// (fresh-walk) read in the current pass and were enqueued for retry.
// At the end of a completed pass this equals PassedSecondAttemptThisPass +
// PassedUnder5AttemptsThisPass + PassedUnder10AttemptsThisPass +
// RecopiesThisPass + HotChunksDeferredThisPass + HotChunksSplitThisPass.
// A completed pass may contain repairs or deferrals and need not be clean. Resets
// each pass.
MismatchesThisPass uint64
// Per-pass histogram of attempts-to-converge. "attempts" counts every
// read of the chunk (initial fresh-walk + each retry). Buckets are
// non-overlapping; their sum equals ChunksPassedThisPass on a clean
// pass. All reset each pass.
PassedFirstAttemptThisPass uint64 // 1 attempt (no retry needed)
PassedSecondAttemptThisPass uint64 // 2 attempts (1 retry)
PassedUnder5AttemptsThisPass uint64 // 3-4 attempts
PassedUnder10AttemptsThisPass uint64 // 5+ attempts; 10+ outliers fold in here
// RecopiesThisPass is the count of chunks that were recopied this
// pass — i.e. retry detected stable target divergence (source CRC
// unchanged across the retry window, target still wrong) and the
// Recopier rewrote the chunk from source. Always zero under
// RunContinuous, which does not repair (those failures surface as
// ErrPermanentDivergence and abort the run instead). A pass with
// RecopiesThisPass > 0 cannot be the first clean pass — recopied
// chunks are repaired, not verified, and are re-read on the next
// pass before FirstCleanPass can fire.
RecopiesThisPass uint64
// HotChunksDeferredThisPass is the number of continuously changing chunks
// deferred after MaxHotAttempts *and* not settled against the change stream
// (see HotChunksSettledThisPass). They are not counted as passed; any value
// greater than zero makes this pass ineligible for FirstCleanPass.
HotChunksDeferredThisPass uint64
// HotChunksSettledThisPass counts chunks that exhausted MaxHotAttempts and
// then reached a real verdict by comparing the target against the change
// stream's own image of each outstanding row — verified outright, or
// diverged and handed to the repair path. It is the escalation that keeps a
// continuously written row from deferring forever; a rising count alongside
// a falling HotChunksDeferredThisPass is it working. It does not say which
// verdict was reached: a settled divergence is also counted by
// RecopiesThisPass (or ends the run), and a settled clean chunk by the
// ordinary passed counters.
HotChunksSettledThisPass uint64
// HotChunksSplitThisPass counts parents replaced by child ranges. A split
// is not a verification result; all children must resolve independently.
HotChunksSplitThisPass uint64
// RetryQueueDepth is the current size of the delayed-retry queue.
RetryQueueDepth int
// HotChunkCount is the number of entries currently in the retry queue
// with consecutiveSrcChanged >= 2 — i.e. a chunk that has been observed
// changing on the source across multiple retry windows.
HotChunkCount int
// InFlight is the number of checksum reads or recopies currently executing.
// Together with RetryQueueDepth it distinguishes work waiting for another
// observation from a slow query that has not returned yet.
InFlight int
// WalkerStalls is the lifetime count of times the dispatcher refused
// to read a fresh chunk from the walker because the retry queue was
// already at MaxQueueSize. Each stall represents the checker holding
// back the walker until existing retries drain enough to make room —
// it does not abort the run. A persistently rising value means source
// churn is outpacing the verifier (consider tuning MaxQueueSize,
// Concurrency, or RetryDelay).
WalkerStalls uint64
// MismatchesDetected is the lifetime count of initial-read mismatches
// (does not include re-failures within a single retry sequence).
MismatchesDetected uint64
// PermanentFailures is the lifetime count of chunks that failed twice
// in a row with the source CRC unchanged. Run returns on the first such
// event; this counter is bumped immediately before the error returns.
PermanentFailures uint64
// ConfirmedDifferences is the count of divergences that survived a drain
// of every feed (or were settled against the change stream), each of which
// was repaired or reported. Unlike MismatchesDetected it excludes apply
// lag that reconciled on retry, and it is never reset, so it covers every
// run of this checker.
ConfirmedDifferences uint64
// FirstCleanPassAt is the wall-clock time at which the first clean
// pass completed (zero before that).
FirstCleanPassAt time.Time
}
LocklessCheckerStats is a snapshot of the checker's counters. All fields are point-in-time; for monotonic totals, sample successively.
type MockChecker ¶ added in v0.18.0
type MockChecker struct {
Chunker table.Chunker
RunError error
// contains filtered or unexported fields
}
MockChecker is a shared test double for runners. Configure Chunker and RunError before use; SetDifferencesFound may be called concurrently with runner work. It models snapshot resume eligibility without performing verification.
func (*MockChecker) ContinuousActive ¶ added in v0.18.0
func (*MockChecker) ContinuousActive() bool
func (*MockChecker) DifferencesFound ¶ added in v0.18.0
func (m *MockChecker) DifferencesFound() uint64
func (*MockChecker) ExecTime ¶ added in v0.18.0
func (*MockChecker) ExecTime() time.Duration
func (*MockChecker) GetProgress ¶ added in v0.18.0
func (*MockChecker) GetProgress() status.ChecksumProgress
func (*MockChecker) ResumeWatermark ¶ added in v0.18.0
func (m *MockChecker) ResumeWatermark() (string, error)
func (*MockChecker) RunContinuous ¶ added in v0.18.0
func (m *MockChecker) RunContinuous(ctx context.Context) error
func (*MockChecker) SetDifferencesFound ¶ added in v0.18.0
func (m *MockChecker) SetDifferencesFound(n uint64)
func (*MockChecker) SetThrottler ¶ added in v0.18.0
func (*MockChecker) SetThrottler(throttler.Throttler)
func (*MockChecker) StartTime ¶ added in v0.18.0
func (*MockChecker) StartTime() time.Time
type Paced ¶ added in v0.16.0
type Paced interface {
// Threads is the live worker count, which the autoscaler may have moved
// away from the configured concurrency.
Threads() int
// IsThrottled reports whether the throttler is currently telling the
// checksum to pause.
IsThrottled() bool
// ChunkSize is the row count of the most recently checksummed chunk. The
// checksum sizes its chunks dynamically just as the copy does, so the same
// field is worth watching in both phases.
ChunkSize() uint64
}
Paced is the optional capability a Checker exposes when it can report how it is currently being paced. The runner status block uses it so a slow checksum can be told apart from a throttled or scaled-down one — the same question the copier row's throttled= answers for the copy phase.
type Recopier ¶ added in v0.15.0
Recopier knows how to overwrite a single chunk's worth of data on the target from the source. It is invoked when the lockless checker's retry path detects stable target divergence — i.e. the source CRC is unchanged across a retry window but the target CRC is still wrong.
Recopy must be safe to call concurrently from multiple worker goroutines; implementations are expected to serialize internally where needed (see mysqlRecopier for the production implementation).
type SingleChecker ¶ added in v0.10.1
func (*SingleChecker) ChecksumChunk ¶ added in v0.10.1
func (*SingleChecker) ChunkSize ¶ added in v0.17.0
func (c *SingleChecker) ChunkSize() uint64
ChunkSize reports the row count of the most recently checksummed chunk, or 0 before the first one.
func (*SingleChecker) ContinuousActive ¶ added in v0.18.0
func (c *SingleChecker) ContinuousActive() bool
func (*SingleChecker) DifferencesFound ¶ added in v0.14.0
func (c *SingleChecker) DifferencesFound() uint64
DifferencesFound returns the number of chunks where a source/target mismatch was detected in the most recent (or in-flight) pass. Used by the lockless-checksum loop to decide whether a cancellation swallow is safe.
func (*SingleChecker) ExecTime ¶ added in v0.10.1
func (c *SingleChecker) ExecTime() time.Duration
func (*SingleChecker) GetProgress ¶ added in v0.10.1
func (c *SingleChecker) GetProgress() status.ChecksumProgress
GetProgress returns rows verified so far and the total to verify, proxied from the chunker.
func (*SingleChecker) IsThrottled ¶ added in v0.16.0
func (c *SingleChecker) IsThrottled() bool
IsThrottled reports whether the throttler is currently pausing dispatch.
func (*SingleChecker) ResumeWatermark ¶ added in v0.18.0
func (c *SingleChecker) ResumeWatermark() (string, error)
ResumeWatermark returns evidence from one attempt, synchronized with retries.
func (*SingleChecker) RunContinuous ¶ added in v0.18.0
func (c *SingleChecker) RunContinuous(ctx context.Context) error
RunContinuous re-verifies the table every LocklessMinPassInterval until ctx is cancelled. Each pass is a Run that does not repair: a mismatch returns ErrPermanentDivergence.
func (*SingleChecker) SetThrottler ¶ added in v0.16.0
func (c *SingleChecker) SetThrottler(t throttler.Throttler)
SetThrottler installs the throttler the checksum paces itself against. It mirrors Copier.SetThrottler and exists for the same reason: the runner builds the checker before it opens the throttlers (the throttler needs the monitoring connection, which is set up later), so the wiring cannot happen at construction. A nil throttler is ignored, leaving the Noop in place.
What is installed is the load-only narrowing of t, not t itself — see loadOnlyThrottler.
func (*SingleChecker) StartTime ¶ added in v0.10.1
func (c *SingleChecker) StartTime() time.Time
func (*SingleChecker) Threads ¶ added in v0.16.0
func (c *SingleChecker) Threads() int
Threads reports the live worker count: the limiter's current limit while a pass is running, falling back to the configured concurrency before the first pass starts.
type StatusReporter ¶ added in v0.18.0
type StatusReporter interface{ ChecksumStatus() ChecksumStatus }
StatusReporter optionally exposes algorithm-specific verification state. Callers depend on this capability rather than concrete checker types.