Skip to content

Commit fe8e3bb

Browse files
dnovitskishaohkCopilot
committed
feat: merge-DML batching optimization for binlog apply
Add --is-merge-dml-event flag that batches and deduplicates binlog DML events before applying them to the ghost table, significantly reducing SQL round-trips during high-write migrations. When enabled and the unique key is memory-comparable (numeric columns): - Deduplicates DML events by unique key (latest event wins) - Reduces INSERT+DELETE sequences to DELETE (safe against row-copy races) - Batches INSERTs/UPDATEs as multi-row REPLACE INTO - Batches DELETEs as DELETE WHERE (pk) IN (...) - Skips events beyond migration range (not yet copied by row-copy) - Disables merge for tables with secondary unique indexes Safety: strict numeric type validation in formatNumericValue prevents SQL injection. Type detection uses exact base-type parsing (not substring). Uses BuildColumnsPreparedValues for proper per-column conversion tokens. Original implementation by shaohoukun in PR #1378, adapted to current master's builder-pattern API with correctness and security hardening. Co-authored-by: shaohk <shaohoukun@meituan.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 154d214 commit fe8e3bb

9 files changed

Lines changed: 840 additions & 65 deletions

File tree

doc/command-line-flags.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,18 @@ While the ongoing estimated number of rows is still heuristic, it's almost exact
158158

159159
Without this parameter, migration is a _noop_: testing table creation and validity of migration, but not touching data.
160160

161+
### is-merge-dml-event
162+
163+
When enabled, batched binlog DML events are merged in memory before applying them to the ghost table. Only effective when the migration unique key uses numeric column types (`int`, `bigint`, `decimal`, `float`, etc.).
164+
165+
**Batching:** All DML events in a batch are grouped by type — inserts and updates become a single multi-row `REPLACE INTO`, deletes become a single `DELETE WHERE (pk) IN (...)`.
166+
167+
**Deduplication:** Repeated changes to the same unique key within a batch collapse to the final state (last writer wins).
168+
169+
**Range filtering:** When a binlog event's unique key value is beyond `MigrationIterationRangeMaxValues` but within `MigrationRangeMaxValues`, the event is skipped — that data will be synced by the row-copy chunk. Events beyond `MigrationRangeMaxValues` or below `MigrationIterationRangeMaxValues` are applied normally.
170+
171+
Automatically disabled for tables with multiple unique indexes (REPLACE semantics are unsafe with secondary unique constraints).
172+
161173
### force-named-cut-over
162174

163175
If given, a `cut-over` command must name the migrated table, or else ignored.

go/base/context.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,9 @@ type MigrationContext struct {
211211
controlReplicasLagResult mysql.ReplicationLagResult
212212
TotalRowsCopied int64
213213
TotalDMLEventsApplied int64
214+
TotalDMLEventsIgnored int64
214215
DMLBatchSize int64
216+
IsMergeDMLEvents bool
215217
isThrottled bool
216218
throttleReason string
217219
throttleReasonHint ThrottleReasonHint

go/cmd/gh-ost/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ func main() {
108108
exponentialBackoffMaxInterval := flag.Int64("exponential-backoff-max-interval", 64, "Maximum number of seconds to wait between attempts when performing various operations with exponential backoff.")
109109
chunkSize := flag.Int64("chunk-size", 1000, "amount of rows to handle in each iteration (allowed range: 10-100,000)")
110110
dmlBatchSize := flag.Int64("dml-batch-size", 10, "batch size for DML events to apply in a single transaction (range 1-1000)")
111+
flag.BoolVar(&migrationContext.IsMergeDMLEvents, "is-merge-dml-event", false, "Merge DML Binlog Event")
111112
defaultRetries := flag.Int64("default-retries", 60, "Default number of retries for various operations before panicking")
112113
flag.BoolVar(&migrationContext.PanicOnWarnings, "panic-on-warnings", false, "Panic when SQL warnings are encountered when copying a batch indicating data loss")
113114
cutOverLockTimeoutSeconds := flag.Int64("cut-over-lock-timeout-seconds", 3, "Max number of seconds to hold locks on tables while attempting to cut-over (retry attempted when lock exceeds timeout) or attempting instant DDL")

0 commit comments

Comments
 (0)