Skip to content

Commit 3047cda

Browse files
Fixes the cutover rename+gtid selection and drain logic
1 parent 279b5a3 commit 3047cda

5 files changed

Lines changed: 123 additions & 171 deletions

File tree

go/logic/migrator.go

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ import (
2020
"github.com/github/gh-ost/go/binlog"
2121
"github.com/github/gh-ost/go/mysql"
2222
"github.com/github/gh-ost/go/sql"
23-
24-
"github.com/openark/golib/sqlutils"
2523
)
2624

2725
var (
@@ -851,6 +849,15 @@ func (mgtr *Migrator) MoveTables() (err error) {
851849
if err := mgtr.initiateApplier(); err != nil {
852850
return err
853851
}
852+
if err := mgtr.checkAbort(); err != nil {
853+
return err
854+
}
855+
if err := mgtr.createFlagFiles(); err != nil {
856+
return err
857+
}
858+
if err := mgtr.checkAbort(); err != nil {
859+
return err
860+
}
854861
if err := mgtr.initiateStreaming(); err != nil {
855862
return err
856863
}
@@ -985,29 +992,38 @@ func (mgtr *Migrator) moveTablesCutOver() (err error) {
985992
return fmt.Errorf("on-before-cut-over hook failed: %w", err)
986993
}
987994

988-
// ----- T1: RENAME source table -----
989-
// Issued on mgtr.inspector.db (the privileged source connection per
990-
// move_table_mode.md §1.5). No retry: RENAME is not idempotent — a partial
991-
// success leaves the table already renamed and a retry would fail. The
992-
// operator re-runs the whole hook chain on failure.
995+
// ----- T1 + T2: RENAME then capture @@gtid_executed on the same connection -----
996+
// Pin both operations to a single *sql.Conn so MySQL's within-session
997+
// ordering guarantee makes it impossible for T2 to observe a state that
998+
// pre-dates T1's commit. Using mgtr.inspector.db directly would let the
999+
// pool schedule T1 and T2 on different underlying TCP connections (or, with
1000+
// a proxy, different servers), breaking the happens-before relationship.
1001+
//
1002+
// No retry on the RENAME: it is not idempotent — a partial success leaves
1003+
// the table already renamed and a retry would fail. The operator re-runs
1004+
// the whole hook chain on failure.
1005+
pinnedConn, err := mgtr.inspector.db.Conn(context.Background())
1006+
if err != nil {
1007+
return fmt.Errorf("failed to pin connection for T1/T2: %w", err)
1008+
}
1009+
defer pinnedConn.Close()
1010+
9931011
sourceDB := mgtr.migrationContext.DatabaseName
9941012
sourceTable := mgtr.migrationContext.OriginalTableName
9951013
delTable := mgtr.migrationContext.GetOldTableName()
9961014
renameQuery := fmt.Sprintf("RENAME TABLE %s.%s TO %s.%s",
9971015
sql.EscapeName(sourceDB), sql.EscapeName(sourceTable),
9981016
sql.EscapeName(sourceDB), sql.EscapeName(delTable))
9991017
mgtr.migrationContext.Log.Infof("T1: renaming source table: %s", renameQuery)
1000-
if _, err := sqlutils.ExecNoPrepare(mgtr.inspector.db, renameQuery); err != nil {
1018+
if _, err := pinnedConn.ExecContext(context.Background(), renameQuery); err != nil {
10011019
return fmt.Errorf("RENAME failed: %w", err)
10021020
}
10031021

1004-
// ----- T2: capture @@gtid_executed on the SAME *sql.DB handle as T1 -----
1005-
// The design doc specifies @@gtid_executed. We query @@GLOBAL.gtid_executed explicitly rather than the unqualified
1006-
// @@gtid_executed form to make the global server-wide scope unambiguous
1007-
// in the SQL itself.
1022+
// ----- T2: capture @@gtid_executed on the SAME connection as T1 -----
1023+
// @@GLOBAL scope is explicit so the intent is unambiguous in the SQL itself.
10081024
// Design: https://github.com/github/gh-ost-tablemove-poc/blob/9dc6df75c4c88ff473906a497836c7518f5614ec/design/coop_cutover.md#32-correctness-verification-for-p4
10091025
var drainGTIDStr string
1010-
if err := mgtr.inspector.db.QueryRow("select @@global.gtid_executed").Scan(&drainGTIDStr); err != nil {
1026+
if err := pinnedConn.QueryRowContext(context.Background(), "select @@gtid_executed").Scan(&drainGTIDStr); err != nil {
10111027
return fmt.Errorf("drain GTID capture failed: %w", err)
10121028
}
10131029
drainGTID, err := mysql.NewGTIDBinlogCoordinates(drainGTIDStr)
@@ -1041,11 +1057,20 @@ func (mgtr *Migrator) moveTablesCutOver() (err error) {
10411057
mgtr.applier.CurrentCoordinatesMutex.Lock()
10421058
applierCoords := mgtr.applier.CurrentCoordinates
10431059
mgtr.applier.CurrentCoordinatesMutex.Unlock()
1044-
if applierCoords != nil && !applierCoords.IsEmpty() && !applierCoords.SmallerThan(drainGTID) {
1060+
applyBacklog := len(mgtr.applyEventsQueue)
1061+
streamerBacklog := 0
1062+
if mgtr.eventsStreamer != nil {
1063+
streamerBacklog = len(mgtr.eventsStreamer.eventsChannel)
1064+
}
1065+
if applierCoords != nil && !applierCoords.IsEmpty() && !applierCoords.SmallerThan(drainGTID) && applyBacklog == 0 && streamerBacklog == 0 {
10451066
mgtr.migrationContext.Log.Infof("T3: drain complete; applier caught up to drain GTID")
10461067
break
10471068
}
1048-
mgtr.migrationContext.Log.Debugf("T3: applier still behind drain GTID, polling")
1069+
if applierCoords != nil && !applierCoords.IsEmpty() && !applierCoords.SmallerThan(drainGTID) {
1070+
mgtr.migrationContext.Log.Debugf("T3: drain GTID reached but backlog remains (apply=%d, streamer=%d)", applyBacklog, streamerBacklog)
1071+
} else {
1072+
mgtr.migrationContext.Log.Debugf("T3: applier still behind drain GTID, polling")
1073+
}
10491074
select {
10501075
case <-drainCtx.Done():
10511076
return fmt.Errorf("drain poll timed out after %s: applier did not catch up to drain GTID", moveTablesCutOverDrainTimeout)

go/logic/migrator_move_tables_cutover_test.go

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@ import (
1212
"testing"
1313
"time"
1414

15+
"github.com/github/gh-ost/go/base"
16+
"github.com/github/gh-ost/go/binlog"
17+
"github.com/github/gh-ost/go/mysql"
1518
"github.com/stretchr/testify/require"
1619
"github.com/stretchr/testify/suite"
17-
1820
"github.com/testcontainers/testcontainers-go"
1921
testmysql "github.com/testcontainers/testcontainers-go/modules/mysql"
20-
21-
"github.com/github/gh-ost/go/base"
22-
"github.com/github/gh-ost/go/mysql"
2322
)
2423

2524
// -----------------------------------------------------------------------------
@@ -320,6 +319,47 @@ func (s *MoveTablesCutOverSuite) TestDrainTimeoutPropagates() {
320319
"post-state: only T0 fires before the drain loop")
321320
}
322321

322+
// TestDrainWaitsForQueuedDML ensures T3 does not declare success just because
323+
// applier.CurrentCoordinates already contains the drain GTID while there is
324+
// still source-table DML queued for application.
325+
func (s *MoveTablesCutOverSuite) TestDrainWaitsForQueuedDML() {
326+
ctx := context.Background()
327+
_, err := s.db.ExecContext(ctx, fmt.Sprintf("CREATE TABLE %s (id INT PRIMARY KEY)", getTestTableName()))
328+
s.Require().NoError(err)
329+
330+
origTimeout, origPoll := moveTablesCutOverDrainTimeout, moveTablesCutOverDrainPollInterval
331+
moveTablesCutOverDrainTimeout = 200 * time.Millisecond
332+
moveTablesCutOverDrainPollInterval = 50 * time.Millisecond
333+
s.T().Cleanup(func() {
334+
moveTablesCutOverDrainTimeout = origTimeout
335+
moveTablesCutOverDrainPollInterval = origPoll
336+
})
337+
338+
var calls []string
339+
fakeHooks := &recordingHooks{name: "fake", calls: &calls}
340+
m, mc := s.buildMigrator(fakeHooks, s.containingDrainGTID())
341+
m.applyEventsQueue <- newApplyEventStructByDML(&binlog.BinlogEntry{
342+
DmlEvent: &binlog.BinlogDMLEvent{
343+
DatabaseName: testMysqlDatabase,
344+
TableName: testMysqlTableName,
345+
DML: binlog.InsertDML,
346+
},
347+
Coordinates: s.containingDrainGTID(),
348+
})
349+
350+
s.Require().Equal(int64(0), atomic.LoadInt64(&mc.CutOverCompleteFlag), "pre-state: flag must be 0")
351+
err = m.moveTablesCutOver()
352+
s.Require().Error(err)
353+
s.Require().Contains(err.Error(), "drain poll timed out")
354+
s.Require().Equal(int64(0), atomic.LoadInt64(&mc.CutOverCompleteFlag),
355+
"post-state: queued DML must keep T3 from reaching T4")
356+
for _, c := range calls {
357+
s.Require().NotEqual("fake:OnSuccess", c, "OnSuccess must not fire while backlog remains")
358+
}
359+
s.Require().Equal([]string{"fake:OnBeforeCutOver"}, calls,
360+
"post-state: only T0 fires before the drain loop times out")
361+
}
362+
323363
func TestMoveTablesCutOver(t *testing.T) {
324364
if testing.Short() {
325365
t.Skip("skipping integration suite in short mode")

script/move-tables/README.md

Lines changed: 5 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -24,157 +24,10 @@ script/build --cli
2424

2525
Run gh-ost to move tables:
2626
```bash
27-
./bin/gh-ost --move-tables=gh_ost_test --host=localhost --port=3308 --user root --password opensesame --database=gh_ost_test_db --target-host=localhost --target-port=3309 --target-user root --target-password opensesame --target-database=gh_ost_test_db --execute --verbose
27+
./script/build --cli; ./bin/gh-ost --move-tables=gh_ost_test --host=localhost --port=3307 --user root --password opensesame --database=gh_ost_test_db --target-host=localhost --target-port=3309 --target-user root --target-password opensesame --target-database=gh_ost_test_db --postpone-cut-over-flag-file=/tmp/ghost-move-tables.postpone.flag --execute --verbose
2828
```
2929

30-
### WIP
31-
32-
Current state based on the current outer dev loop:
33-
34-
35-
```bash
36-
37-
38-
\u2718 \e[2m\u2388 (\u2205) gh-ost:(move-tables/1.2-skip-ghost-tables)
39-
> rm /tmp/gh-ost.gh_ost_test_db..sock; ./script/build --cli && ./bin/gh-ost --move-tables=gh_ost_test --host=localhost --port=3308 --user root --password opensesame --database=gh_ost_test_db --target-host=localhost --target-port=3309 --target-user root --target-password opensesame --target-database=gh_ost_test_db --execute --verbose
40-
rm: /tmp/gh-ost.gh_ost_test_db..sock: No such file or directory
41-
go version go1.25.9 darwin/arm64 found in : Go Binary: /opt/homebrew/bin/go
42-
++ '[' '!' -L .gopath/src/github.com/github/gh-ost ']'
43-
++ export GOPATH=/Users/chriskirkland/git/src/github.com/github/gh-ost/.gopath:/Users/chriskirkland/git/src/github.com/github/gh-ost/.vendor
44-
++ GOPATH=/Users/chriskirkland/git/src/github.com/github/gh-ost/.gopath:/Users/chriskirkland/git/src/github.com/github/gh-ost/.vendor
45-
+ mkdir -p bin
46-
+ bindir=/Users/chriskirkland/git/src/github.com/github/gh-ost/bin
47-
+ scriptdir=/Users/chriskirkland/git/src/github.com/github/gh-ost/script
48-
++ git rev-parse HEAD
49-
+ version=0508dd782e1871de9dcaa51d3f59e5ba4cd92117
50-
++ git describe --tags --always --dirty
51-
+ describe=v1.1.9-19-g0508dd78-dirty
52-
+ export GOPATH=/Users/chriskirkland/git/src/github.com/github/gh-ost/.gopath
53-
+ GOPATH=/Users/chriskirkland/git/src/github.com/github/gh-ost/.gopath
54-
+ cd .gopath/src/github.com/github/gh-ost
55-
+ go build -o /Users/chriskirkland/git/src/github.com/github/gh-ost/bin/gh-ost -ldflags '-X main.AppVersion=0508dd782e1871de9dcaa51d3f59e5ba4cd92117 -X main.BuildDescribe=v1.1.9-19-g0508dd78-dirty' ./go/cmd/gh-ost/main.go
56-
2026-06-01 16:41:13 INFO starting gh-ost 0508dd782e1871de9dcaa51d3f59e5ba4cd92117 (git commit: unknown)
57-
2026-06-01 16:41:13 INFO Moving tables [gh_ost_test] from `gh_ost_test_db` to `gh_ost_test_db` (localhost)
58-
2026-06-01 16:41:13 INFO inspector connection validated on localhost:3308
59-
2026-06-01 16:41:13 INFO User has SUPER, REPLICATION SLAVE privileges, and has ALL privileges on `gh_ost_test_db`.*
60-
2026-06-01 16:41:13 INFO binary logs validated on localhost:3308
61-
2026-06-01 16:41:13 INFO Restarting replication on localhost:3308 to make sure binlog settings apply to replication thread
62-
2026-06-01 16:41:13 INFO Inspector initiated on 3e162abb4a14:3308, version 8.0.41
63-
2026-06-01 16:41:13 INFO Inspector validating original table
64-
2026-06-01 16:41:13 INFO Table found. Engine=InnoDB
65-
2026-06-01 16:41:13 INFO Estimated number of rows via EXPLAIN: 20
66-
2026-06-01 16:41:13 INFO Inspector validated original table
67-
2026-06-01 16:41:13 INFO Inspector inspected original table
68-
2026-06-01 16:41:13 INFO log_slave_updates validated on localhost:3308
69-
2026-06-01 16:41:13 INFO Inspector validated and initialized
70-
2026-06-01 16:41:13 INFO applier connection validated on localhost:3309
71-
2026-06-01 16:41:13 INFO applier connection validated on localhost:3309
72-
2026-06-01 16:41:13 INFO will use time_zone='SYSTEM' on applier
73-
2026-06-01 16:41:13 INFO applier connection validated on localhost:3309
74-
2026-06-01 16:41:13 INFO Applier initiated on 381ee87dc2c6:3309, version 8.0.41
75-
2026-06-01 16:41:13 INFO Fetching create table statement for `gh_ost_test_db.gh_ost_test`
76-
2026-06-01 16:41:13 INFO Create table statement: CREATE TABLE `gh_ost_test` (
77-
`id` bigint NOT NULL AUTO_INCREMENT,
78-
`column1` int NOT NULL,
79-
`column2` smallint unsigned NOT NULL,
80-
`column3` mediumint unsigned NOT NULL,
81-
`column4` tinyint unsigned NOT NULL,
82-
`column5` int NOT NULL,
83-
`column6` int NOT NULL,
84-
PRIMARY KEY (`id`),
85-
KEY `c12_ix` (`column1`,`column2`)
86-
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
87-
2026-06-01 16:41:13 INFO Creating target table `gh_ost_test_db`.`gh_ost_test`
88-
2026-06-01 16:41:13 INFO Target table created
89-
2026-06-01 16:41:13 INFO streamer connection validated on localhost:3308
90-
[2026/06/01 16:41:13] [info] binlogsyncer.go:191 create BinlogSyncer with config {ServerID:99999 Flavor:mysql Host:localhost Port:3308 User:root Password: Localhost: Charset: SemiSyncEnabled:false RawModeEnabled:false TLSConfig:<nil> ParseTime:false TimestampStringLocation:UTC UseDecimal:true RecvBufferSize:0 HeartbeatPeriod:0s ReadTimeout:0s MaxReconnectAttempts:0 DisableRetrySync:false VerifyChecksum:false DumpCommandFlag:0 Option:<nil> Logger:0x14000494a20 Dialer:0x100c8c0c0 RowsEventDecodeFunc:<nil> TableMapOptionalMetaDecodeFunc:<nil> DiscardGTIDSet:false EventCacheCount:10240 SynchronousEventHandler:<nil>}
91-
2026-06-01 16:41:13 INFO Connecting binlog streamer at mysql-bin.000003:2987908
92-
[2026/06/01 16:41:13] [info] binlogsyncer.go:443 begin to sync binlog from position (mysql-bin.000003, 2987908)
93-
[2026/06/01 16:41:13] [info] binlogsyncer.go:409 Connected to mysql 8.0.41 server
94-
2026-06-01 16:41:13 INFO Skipping stream of the changelog table []
95-
[2026/06/01 16:41:13] [info] binlogsyncer.go:868 rotate to (mysql-bin.000003, 2987908)
96-
2026-06-01 16:41:13 INFO rotate to next log from mysql-bin.000003:0 to mysql-bin.000003
97-
2026-06-01 16:41:13 INFO Listening on unix socket file: /tmp/gh-ost.gh_ost_test_db..sock
98-
2026-06-01 16:41:13 INFO Adding listener for gh_ost_test_db.gh_ost_test
99-
2026-06-01 16:41:13 INFO Reading migration range according to key: PRIMARY (
100-
select /* gh-ost `gh_ost_test_db`.`gh_ost_test` */ `id`
101-
from
102-
`gh_ost_test_db`.`gh_ost_test`
103-
force index (PRIMARY)
104-
order by
105-
`id` asc
106-
limit 1)
107-
2026-06-01 16:41:13 INFO Migration min values: [1]
108-
2026-06-01 16:41:13 INFO Migration max values: [20]
109-
2026-06-01 16:41:13 INFO Skipping throttling in move tables mode []
110-
# Migrating `gh_ost_test_db`.`gh_ost_test`; Target table is `gh_ost_test_db`.`gh_ost_test`
111-
# Migrating 381ee87dc2c6:3309; inspecting 3e162abb4a14:3308; executing on Chriss-MBP-2
112-
# Migration started at Mon Jun 01 16:41:13 -0600 2026
113-
# chunk-size: 1000; max-lag-millis: 1500ms; dml-batch-size: 10; max-load: ; critical-load: ; nice-ratio: 0.000000
114-
# throttle-additional-flag-file: /tmp/gh-ost.throttle
115-
# Serving on unix socket: /tmp/gh-ost.gh_ost_test_db..sock
116-
Copy: 0/20 0.0%; Applied: 0; Backlog: 0/1000; Time: 0s(total), 0s(copy); streamer: mysql-bin.000003:0; Lag: 0.00s, HeartbeatLag: 9223372036.85s, State: migrating; ETA: N/A
117-
2026-06-01 16:41:13 INFO Copy: 0/20 0.0%; Applied: 0; Backlog: 0/1000; Time: 0s(total), 0s(copy); streamer: mysql-bin.000003:0; Lag: 0.00s, HeartbeatLag: 9223372036.85s, State: migrating; ETA: N/A []
118-
Copy: 0/20 0.0%; Applied: 0; Backlog: 0/1000; Time: 1s(total), 1s(copy); streamer: mysql-bin.000003:0; Lag: 0.00s, HeartbeatLag: 9223372036.85s, State: migrating; ETA: N/A
119-
2026-06-01 16:41:14 INFO [execWriteFuncs] Processing row copy function []
120-
2026-06-01 16:41:14 INFO Copy: 0/20 0.0%; Applied: 0; Backlog: 0/1000; Time: 1s(total), 1s(copy); streamer: mysql-bin.000003:0; Lag: 0.00s, HeartbeatLag: 9223372036.85s, State: migrating; ETA: N/A []
121-
2026-06-01 16:41:14 INFO ApplyIterationInsertQuery affected 20 rows
122-
2026-06-01 16:41:14 INFO [execWriteFuncs] Processing row copy function []
123-
2026-06-01 16:41:14 INFO [execWriteFuncs] Processing row copy function []
124-
2026-06-01 16:41:14 INFO Row copy complete
125-
2026-06-01 16:41:14 INFO Writing changelog state: Migrated
126-
[2026/06/01 16:41:14] [info] binlogsyncer.go:225 syncer is closing...
127-
2026-06-01 16:41:14 INFO StreamEvents encountered unexpected error: Sync was closed
128-
github.com/go-mysql-org/go-mysql/replication.init
129-
<autogenerated>:1
130-
runtime.doInit1
131-
/Users/chriskirkland/git/src/github.com/github/gh-ost/.gopath/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.9.darwin-arm64/src/runtime/proc.go:7670
132-
runtime.doInit
133-
/Users/chriskirkland/git/src/github.com/github/gh-ost/.gopath/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.9.darwin-arm64/src/runtime/proc.go:7637
134-
runtime.main
135-
/Users/chriskirkland/git/src/github.com/github/gh-ost/.gopath/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.9.darwin-arm64/src/runtime/proc.go:256
136-
runtime.goexit
137-
/Users/chriskirkland/git/src/github.com/github/gh-ost/.gopath/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.9.darwin-arm64/src/runtime/asm_arm64.s:1268
138-
[2026/06/01 16:41:14] [info] binlogsyncer.go:988 kill last connection id 405
139-
[2026/06/01 16:41:14] [info] binlogsyncer.go:255 syncer is closed
140-
2026-06-01 16:41:14 INFO Closed streamer connection. err=<nil>
141-
2026-06-01 16:41:14 INFO Done moving tables [gh_ost_test] from `gh_ost_test_db` to `gh_ost_test_db` (localhost)
142-
2026-06-01 16:41:14 INFO Removing socket file: /tmp/gh-ost.gh_ost_test_db..sock
143-
2026-06-01 16:41:14 INFO Tearing down inspector
144-
2026-06-01 16:41:14 INFO Tearing down applier
145-
2026-06-01 16:41:14 INFO Tearing down streamer
146-
# Done
147-
148-
```
149-
150-
:tada: :tada: :tada: :tada:
151-
```bash
152-
153-
\u2714 \e[2m\u2388 (\u2205) gh-ost-tablemove-poc:(chriskirkland/move-tables)
154-
> ./script/move-tables/mysql-target-primary -D "gh_ost_test_db" -e "SELECT * FROM gh_ost_test;"
155-
+----+---------+---------+---------+---------+------------+------------+
156-
| id | column1 | column2 | column3 | column4 | column5 | column6 |
157-
+----+---------+---------+---------+---------+------------+------------+
158-
| 1 | 1001 | 100 | 500000 | 10 | 1700000001 | 1700000002 |
159-
| 2 | 1002 | 200 | 600000 | 20 | 1700000003 | 1700000004 |
160-
| 3 | 1003 | 300 | 700000 | 30 | 1700000005 | 1700000006 |
161-
| 4 | 1004 | 400 | 800000 | 40 | 1700000007 | 1700000008 |
162-
| 5 | 1005 | 500 | 900000 | 50 | 1700000009 | 1700000010 |
163-
| 6 | 1006 | 600 | 1000000 | 60 | 1700000011 | 1700000012 |
164-
| 7 | 1007 | 700 | 1100000 | 70 | 1700000013 | 1700000014 |
165-
| 8 | 1008 | 800 | 1200000 | 80 | 1700000015 | 1700000016 |
166-
| 9 | 1009 | 900 | 1300000 | 90 | 1700000017 | 1700000018 |
167-
| 10 | 1010 | 1000 | 1400000 | 100 | 1700000019 | 1700000020 |
168-
| 11 | 1011 | 1100 | 1500000 | 110 | 1700000021 | 1700000022 |
169-
| 12 | 1012 | 1200 | 1600000 | 120 | 1700000023 | 1700000024 |
170-
| 13 | 1013 | 1300 | 1700000 | 130 | 1700000025 | 1700000026 |
171-
| 14 | 1014 | 1400 | 1800000 | 140 | 1700000027 | 1700000028 |
172-
| 15 | 1015 | 1500 | 1900000 | 150 | 1700000029 | 1700000030 |
173-
| 16 | 1016 | 1600 | 2000000 | 160 | 1700000031 | 1700000032 |
174-
| 17 | 1017 | 1700 | 2100000 | 170 | 1700000033 | 1700000034 |
175-
| 18 | 1018 | 1800 | 2200000 | 180 | 1700000035 | 1700000036 |
176-
| 19 | 1019 | 1900 | 2300000 | 190 | 1700000037 | 1700000038 |
177-
| 20 | 1020 | 2000 | 2400000 | 200 | 1700000039 | 1700000040 |
178-
+----+---------+---------+---------+---------+------------+------------+
179-
180-
```
30+
Note: replicas in this local topology are configured with `read_only=ON` and
31+
`super_read_only=ON`. If you point `--host` at `mysql-source-replica` (3308),
32+
the cutover `RENAME TABLE` step will fail by design. Use source primary (3307)
33+
as the inspected host when you want cutover to rename on source.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Continuously insert new rows into gh_ost_test on source primary.
5+
# Usage:
6+
# script/move-tables/insert-source-primary-loop [start_column1] [sleep_seconds]
7+
# Example:
8+
# script/move-tables/insert-source-primary-loop 100000 0.2
9+
10+
start_i="${1:-100000}"
11+
delay="${2:-0.2}"
12+
i="$start_i"
13+
14+
echo "Starting continuous inserts on source primary. Press Ctrl+C to stop."
15+
echo "start_column1=$start_i sleep_seconds=$delay"
16+
17+
trap 'echo; echo "Stopped."; exit 0' INT TERM
18+
19+
while true; do
20+
ts="$(date +%s)"
21+
22+
script/move-tables/mysql-source-primary -D gh_ost_test_db -e "
23+
INSERT INTO gh_ost_test (column1, column2, column3, column4, column5, column6)
24+
VALUES ($i, $((i % 65535)), $((i % 16777215)), $((i % 255)), $ts, $((ts + 1)));
25+
"
26+
27+
echo "inserted row: column1=$i ts=$ts"
28+
i=$((i + 1))
29+
sleep "$delay"
30+
done

0 commit comments

Comments
 (0)