You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Local repro update: Could not reproduce this locally despite 500+ iterations and varied stress configurations. Summary of attempts:
~500 isolated runs of TestCutoverAtomicityWithConcurrentWrites (with explicit DROP TABLE IF EXISTS between iterations to avoid stale-state cascades): all PASS, no row-count mismatch.
30 iterations with -race: all PASS.
8 parallel go test runners × 10 iterations each, sharing one MySQL instance (mimicking CI's -parallel=4 + -race): plenty of failures, but all from sibling-test "metadata lock held by another connection" cascades caused by the partial-cutover leaving tables half-renamed — none were the row-count failure from this issue.
16-thread variant with longer warmup (500ms) and 4-thread migration: failed in setup before reaching cutover.
Added per-event artificial latency to readStream (SPIRIT_DEBUG_BINLOG_DELAY_US) to simulate slow CI binlog delivery: 500us = no repro, 2ms = test aborts in setup, 5ms = hung. None reproduced the row-count mismatch either.
Local environment: macOS, NVMe SSD, native MySQL 8.0.43. CI: Docker on Linux + -race + -parallel=4 + MySQL 8.0.45.
Code-walk summary (see PR #750 for the diagnostic logging that should help next time this fires):
The cutover protocol appears correct on paper. LOCK TABLES waits for in-flight transactions to fully commit (binlog write happens before row locks release). FlushUnderTableLock is called twice with BlockWait between, which forces the binlog reader to drain via FLUSH BINARY LOGS + SHOW MASTER STATUS. AllChangesFlushed() then checks each subscription's pending length. So I can't construct a deterministic bug from the code alone.
That said, two things stand out as soft guarantees:
AllChangesFlushed() only warns on bufferedPos > flushedPos, doesn't return false. Consistency hinges entirely on subscription length being authoritative.
KeyAboveHighWatermark() returns TRUE (silently drops the change) when chunkPtr.IsNil() — the safe-by-accident default. The window between SetWatermarkOptimization(true) and the first chunker.Next() call drops binlog events, with the implicit assumption that "the chunker will SELECT them later." Probably fine for AUTO_INCREMENT but I haven't proven it can't bite under the right interleaving.
Possible link to #743: Both flakes share the CI environment (binlog syncer can stall under load). But #743's fix in f4ce965 only changes when BlockWait issues FLUSH BINARY LOGS — it doesn't change BlockWait's correctness criterion (bufferedPos >= targetPos). I don't think f4ce965 will fix #746.
The PR adds enough logging on failure to tell whether the missing rows are the latest ones (cutover-window race) or scattered through the id range (copy-phase race), and whether they're consecutive (single transaction) or independent. That should let us bisect on the next CI hit.
JFG, a replication expert, reached out with advice on the bug report.
When you consider semi-sync, rows are sent to the replica ahead of when they are committed (and visible) on the source. So this race was likely present and a serious issue for semi-sync. We did not make any claims w.r.t. semi sync support, but I think that's a mistake and we should be testing it, so I am going to go ahead and add it to the test suite.
JFG also said that even though semi-sync was not in use here, the same code-path is exercised in non-semi sync so there is a brief race by design. In other-words, expecting to see 'committed' rows in the binary log to be visible in InnoDB was an incorrect assumption (and likely a common one).
I will leave the MySQL bug open for now to get a possible second opinion on this, but what JFG is saying makes sense. The only action item here is to ensure we have semi-sync testing in scope and document it as supported.
Activity
morgo commentedon May 1, 2026
Local repro update: Could not reproduce this locally despite 500+ iterations and varied stress configurations. Summary of attempts:
TestCutoverAtomicityWithConcurrentWrites(with explicitDROP TABLE IF EXISTSbetween iterations to avoid stale-state cascades): all PASS, no row-count mismatch.-race: all PASS.go testrunners × 10 iterations each, sharing one MySQL instance (mimicking CI's-parallel=4+-race): plenty of failures, but all from sibling-test "metadata lock held by another connection" cascades caused by the partial-cutover leaving tables half-renamed — none were the row-count failure from this issue.readStream(SPIRIT_DEBUG_BINLOG_DELAY_US) to simulate slow CI binlog delivery: 500us = no repro, 2ms = test aborts in setup, 5ms = hung. None reproduced the row-count mismatch either.Local environment: macOS, NVMe SSD, native MySQL 8.0.43. CI: Docker on Linux +
-race+-parallel=4+ MySQL 8.0.45.Code-walk summary (see PR #750 for the diagnostic logging that should help next time this fires):
The cutover protocol appears correct on paper.
LOCK TABLESwaits for in-flight transactions to fully commit (binlog write happens before row locks release).FlushUnderTableLockis called twice withBlockWaitbetween, which forces the binlog reader to drain viaFLUSH BINARY LOGS+SHOW MASTER STATUS.AllChangesFlushed()then checks each subscription's pending length. So I can't construct a deterministic bug from the code alone.That said, two things stand out as soft guarantees:
AllChangesFlushed()only warns onbufferedPos > flushedPos, doesn't return false. Consistency hinges entirely on subscription length being authoritative.KeyAboveHighWatermark()returns TRUE (silently drops the change) whenchunkPtr.IsNil()— the safe-by-accident default. The window betweenSetWatermarkOptimization(true)and the firstchunker.Next()call drops binlog events, with the implicit assumption that "the chunker will SELECT them later." Probably fine for AUTO_INCREMENT but I haven't proven it can't bite under the right interleaving.Possible link to #743: Both flakes share the CI environment (binlog syncer can stall under load). But #743's fix in
f4ce965only changes whenBlockWaitissuesFLUSH BINARY LOGS— it doesn't change BlockWait's correctness criterion (bufferedPos >= targetPos). I don't thinkf4ce965will fix #746.The PR adds enough logging on failure to tell whether the missing rows are the latest ones (cutover-window race) or scattered through the id range (copy-phase race), and whether they're consecutive (single transaction) or independent. That should let us bisect on the next CI hit.
test: log missing/diverged rows on cutover atomicity assertion failure
morgo commentedon May 1, 2026
test: log applier path and copier chunk completions at Debug for bloc…
Merge pull request #759 from morgo/diagnostic-logging-746
test: disable FixDifferences when useTestCutover for #746 repro
Merge pull request #764 from morgo/repro-746-no-fixdifferences
fix: apply same KeyAboveHighWatermark fix to composite chunker; rever…
fix(block#746): KeyAboveHighWatermark must not silently drop in IsNil…
fix: apply same KeyAboveHighWatermark fix to composite chunker; rever…
fix(block#746): KeyAboveHighWatermark must not silently drop in IsNil…
fix: apply same KeyAboveHighWatermark fix to composite chunker; rever…
test: run cutover-atomicity probe across optimistic+composite × unbuf…
16 remaining items
docs(repl): add history note explaining the unified subscription type
test(migration): re-enable FixDifferences in TestCutoverAtomicityWith…
morgo commentedon May 14, 2026
MySQL Bug: https://bugs.mysql.com/bug.php?id=120471
fix(block#746): make semi-sync lane actually inject the 100ms latency
docs(block#746): note semi-sync is supported and tested
morgo commentedon May 16, 2026
JFG, a replication expert, reached out with advice on the bug report.
When you consider semi-sync, rows are sent to the replica ahead of when they are committed (and visible) on the source. So this race was likely present and a serious issue for semi-sync. We did not make any claims w.r.t. semi sync support, but I think that's a mistake and we should be testing it, so I am going to go ahead and add it to the test suite.
JFG also said that even though semi-sync was not in use here, the same code-path is exercised in non-semi sync so there is a brief race by design. In other-words, expecting to see 'committed' rows in the binary log to be visible in InnoDB was an incorrect assumption (and likely a common one).
I will leave the MySQL bug open for now to get a possible second opinion on this, but what JFG is saying makes sense. The only action item here is to ensure we have semi-sync testing in scope and document it as supported.
(Also many thanks to JFG!)
docs(block#746): address Copilot review — describe the connected dela…
fix(block#746): keep netem sidecar alive so test container actually runs
test(#746): semi-sync cutover regression test (build-tagged)
Merge pull request #852 from morgo/semi-sync-tests
fix(#847): switch applier to REPLACE INTO; remove rewind machinery
docs+test: KeyAboveHighWatermark discard vs binlog delivery-before-vi…
change: stop deferring not-yet-dispatched keys at flush time (#1167)