feat: multi-threaded slave (MTS) parallel DML apply#1692
Conversation
|
Thanks for the PR @jackiesre721. When we attempted MTS before we ran into data consistency issues. If possible could you run some integration tests and tests under high query load to increase confidence in correctness? If I have time I will run some tests as well |
7147d84 to
ab784af
Compare
Implements LOGICAL_CLOCK-based parallel binlog event application, mirroring MySQL 5.7 MTS scheduling. With --num-workers=N, gh-ost applies DML events to the ghost table using N concurrent workers, significantly increasing throughput for high-write tables. Key components: - commitBarrier: dependency tracking via last_committed/sequence_number - mtsScheduleState: new-group detection and epoch reset handling - dmlCoordinator: transaction grouping and dependency-aware dispatch - dmlWorker: per-worker goroutine with independent DB connection - Deadlock-aware retry: immediate retry on errno 1213, 1s sleep on others - Monotonic coordinate update: prevents checkpoint regression when workers complete out of order Backward compatible: --num-workers=1 (default) uses the original single-threaded path with zero behavioral changes.
ab784af to
c4060b9
Compare
Replace naive gap-free LWM with dispatched-subsequence tracking. gh-ost sees only one table's binlog events, so sequence_numbers are sparse (5, 9, 14, ...). A gap-free LWM would stall at the first gap. Key changes: - Track dispatched sequence numbers; LWM advances over the committed prefix of that subsequence - Detect cross-table dependencies via dispatched set membership (replaces explicit parentSeenOnStream bool) - Fix sync.Cond.Wait() not responding to context cancellation by spawning a watcher goroutine that calls Broadcast() on ctx.Done() - Guard delegatedJobs under mu to prevent lost-wakeup deadlocks - Add delegatedJobCount() and reset() helpers Fixes the 16-worker deadlock where concurrent deadlock retries caused all workers to block in waitForDependency indefinitely.
The CI runs localtests/test.sh without -g flag, so --gtid must be in the per-test extra_args file for the MTS test to activate GTID mode and use logical timestamps for dependency tracking.
|
Hi @meiji163, great questions. Here is a detailed summary of the testing we have done and the specific consistency issues we identified and fixed. PR #1454 Root Cause AnalysisPR #1454 previously had data consistency issues. We traced the root cause to two bugs: 1. Naive gap-free LWM stalls on sparse sequence numbersgh-ost only streams binlog rows for the single migrated table, but MySQL assigns Fix: Replaced gap-free LWM with dispatched-subsequence tracking. The coordinator records every transaction it dispatches via 2. Cross-table dependency false blockingPR #1454 tracked whether a parent transaction was "seen on stream" via an explicit boolean. This had an observe/dispatch ordering hazard. Fix: Cross-table dependencies are now detected directly from the dispatched set: if 3.
|
Co-authored-by: Cursor <cursoragent@cursor.com>
Add retryOperationCtx and pass ctx into handleCoordinatorNonDML, retryMTSApply, applyMTSWorkerJob, and ApplyDMLEventQueries so golangci-lint contextcheck passes on CI. Co-authored-by: Cursor <cursoragent@cursor.com>
Reduce sysbench to 8 threads at 1200 trx/s for 45s so MTS can drain the backlog before cut-over. Add per-test timeout file (300s) for mts-sysbench instead of the default 120s wrapper limit. Co-authored-by: Cursor <cursoragent@cursor.com>
…replica --test-on-replica swap-reverts cut-over so _sbtest1_del never remains. Compare sbtest1 vs _sbtest1_gho like other localtests, require Done migrating, and use CHECKSUM column NF for schema-qualified table names. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Thanks for all the details @jackiesre721. It looks like the CI tests are getting a nil pointer dereference somewhere |
…rors Under concurrent MTS workers, MySQL 8.0 may return ER_LOCK_NOWAIT (3572) when gap locks on the ghost table's secondary indexes conflict between parallel statements. This was not classified as retryable, causing the MTS worker to abort and cascade a migration failure. Changes: - Add errno 3572 (ER_LOCK_NOWAIT) to isRetryableApplyError alongside existing 1205 (lock wait timeout) and 1213 (deadlock) - Remove context.Background() fallback in ApplyDMLEventQueries; all callers already pass a valid context (resolves contextcheck lint) - Use the passed ctx parameter in applyMTSWorkerJob defer instead of mgtr.migrationContext.GetContext() (resolves contextcheck lint)
ApplyIterationInsertQuery used FOR SHARE NOWAIT on MySQL 8.x but had no retry logic. Under heavy write load (MTS sysbench 8 threads / 1200 trx/s), errno 3572 exhausted the outer retryOperation budget (3 attempts) and caused FATAL abort. - Extract transaction logic into executeChunkInsertTx method - Add 20-attempt internal retry loop for transient lock errors (3572/1205/1213) - Use short backoff (100ms + 50ms per attempt) for fast recovery - Add exponential backoff in retryOperationCtx for isRetryableApplyError - Add 3572 test case to TestIsRetryableApplyError
Under MTS mode (NumWorkers > 1), parallel DML workers hold row locks on the ghost table concurrently with row-copy. SELECT ... FOR SHARE NOWAIT fails immediately with errno 3572 when it encounters those locks, and the combined retry budget (20 internal + 3 outer) is insufficient under heavy write load on MySQL 8.4, causing the migration to FATAL. Extract the noWait decision into rowCopyUsesNoWait() which returns false when NumWorkers > 1, making row-copy use LOCK IN SHARE MODE (blocking wait) instead. This is the correct trade-off: DML apply is latency-sensitive and should not be preempted by row-copy retries, so row-copy should wait for DML locks to release rather than failing fast. Single-threaded mode on MySQL 8.x retains NOWAIT behaviour unchanged.
|
Hi @meiji163, thanks for flagging the CI failures. We identified and fixed the root cause of the Problem: Under MTS mode with parallel DML workers, the row-copy path uses Fix: Extracted the noWait decision into Re: the nil pointer dereference you mentioned — that was caused by context cancellation propagating through The latest push ( |
Implements LOGICAL_CLOCK-based parallel binlog event application, mirroring MySQL 5.7 MTS scheduling. With --num-workers=N, gh-ost applies DML events to the ghost table using N concurrent workers, significantly increasing throughput for high-write tables.
Key components:
Backward compatible: --num-workers=1 (default) uses the original single-threaded path with zero behavioral changes.
A Pull Request should be associated with an Issue.
Related issue: https://github.com/github/gh-ost/issues/0123456789
Description
This PR [briefly explain what it does]
script/cibuildreturns with no formatting errors, build errors or unit test errors.