Skip to content

Commit 7f7e11c

Browse files
committed
Merge branch 'feature-move-tables' into chriskirkland/issues-8260-pt2
2 parents 288f0a8 + a3a29ce commit 7f7e11c

8 files changed

Lines changed: 158 additions & 40 deletions

File tree

go/base/context.go

Lines changed: 81 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -178,31 +178,38 @@ type MigrationContext struct {
178178
CutOverType CutOver
179179
ReplicaServerId uint
180180

181-
Hostname string
182-
AssumeMasterHostname string
183-
ApplierTimeZone string
184-
ApplierWaitTimeout int64
185-
TableEngine string
186-
RowsEstimate int64
187-
RowsDeltaEstimate int64
188-
UsedRowsEstimateMethod RowsEstimateMethod
189-
HasSuperPrivilege bool
190-
OriginalBinlogFormat string
191-
OriginalBinlogRowImage string
192-
InspectorConnectionConfig *mysql.ConnectionConfig
193-
InspectorMySQLVersion string
194-
ApplierConnectionConfig *mysql.ConnectionConfig
195-
ApplierMySQLVersion string
196-
StartTime time.Time
197-
RowCopyStartTime time.Time
198-
RowCopyEndTime time.Time
199-
LockTablesStartTime time.Time
200-
RenameTablesStartTime time.Time
201-
RenameTablesEndTime time.Time
202-
pointOfInterestTime time.Time
203-
pointOfInterestTimeMutex *sync.Mutex
204-
lastHeartbeatOnChangelogTime time.Time
205-
lastHeartbeatOnChangelogMutex *sync.Mutex
181+
Hostname string
182+
AssumeMasterHostname string
183+
ApplierTimeZone string
184+
ApplierWaitTimeout int64
185+
TableEngine string
186+
RowsEstimate int64
187+
RowsDeltaEstimate int64
188+
UsedRowsEstimateMethod RowsEstimateMethod
189+
HasSuperPrivilege bool
190+
OriginalBinlogFormat string
191+
OriginalBinlogRowImage string
192+
InspectorConnectionConfig *mysql.ConnectionConfig
193+
InspectorMySQLVersion string
194+
ApplierConnectionConfig *mysql.ConnectionConfig
195+
ApplierMySQLVersion string
196+
StartTime time.Time
197+
RowCopyStartTime time.Time
198+
RowCopyEndTime time.Time
199+
LockTablesStartTime time.Time
200+
RenameTablesStartTime time.Time
201+
RenameTablesEndTime time.Time
202+
pointOfInterestTime time.Time
203+
pointOfInterestTimeMutex *sync.Mutex
204+
lastHeartbeatOnChangelogTime time.Time
205+
lastHeartbeatOnChangelogMutex *sync.Mutex
206+
// lastAppliedBinlogEventTime is the binlog-header timestamp of the last event
207+
// applied to the target in move-tables mode. lastBinlogEventStreamedTime is the
208+
// wall-clock time the streamer last delivered an event for the moved table.
209+
// Together they drive the move-tables writer-lag metric (see GetBinlogWriterLag).
210+
lastAppliedBinlogEventTime time.Time
211+
lastBinlogEventStreamedTime time.Time
212+
binlogWriterLagMutex *sync.Mutex
206213
CurrentLag int64
207214
currentProgress uint64
208215
etaNanoseonds int64
@@ -361,6 +368,7 @@ func NewMigrationContext() *MigrationContext {
361368
configMutex: &sync.Mutex{},
362369
pointOfInterestTimeMutex: &sync.Mutex{},
363370
lastHeartbeatOnChangelogMutex: &sync.Mutex{},
371+
binlogWriterLagMutex: &sync.Mutex{},
364372
ColumnRenameMap: make(map[string]string),
365373
PanicAbort: make(chan error),
366374
ctx: ctx,
@@ -740,6 +748,54 @@ func (mctx *MigrationContext) GetLastHeartbeatOnChangelogTime() time.Time {
740748
return mctx.lastHeartbeatOnChangelogTime
741749
}
742750

751+
// UpdateLastAppliedBinlogEventTime records the binlog-header timestamp of the
752+
// last event successfully applied to the target. Used by move-tables mode to
753+
// derive writer lag.
754+
func (mctx *MigrationContext) UpdateLastAppliedBinlogEventTime(t time.Time) {
755+
mctx.binlogWriterLagMutex.Lock()
756+
defer mctx.binlogWriterLagMutex.Unlock()
757+
758+
mctx.lastAppliedBinlogEventTime = t
759+
}
760+
761+
// MarkBinlogEventStreamed records that the streamer just delivered an event for
762+
// the moved table. Used to distinguish "falling behind" from "source is idle".
763+
func (mctx *MigrationContext) MarkBinlogEventStreamed() {
764+
mctx.binlogWriterLagMutex.Lock()
765+
defer mctx.binlogWriterLagMutex.Unlock()
766+
767+
mctx.lastBinlogEventStreamedTime = time.Now()
768+
}
769+
770+
// BumpBinlogWriterLagIfIdle treats prolonged streamer silence as "caught up":
771+
// if no event has been streamed for the moved table within idleThreshold, the
772+
// last-applied timestamp is advanced to now so writer lag does not climb forever
773+
// while the source is quiet.
774+
func (mctx *MigrationContext) BumpBinlogWriterLagIfIdle(idleThreshold time.Duration) {
775+
mctx.binlogWriterLagMutex.Lock()
776+
defer mctx.binlogWriterLagMutex.Unlock()
777+
778+
if mctx.lastBinlogEventStreamedTime.IsZero() || time.Since(mctx.lastBinlogEventStreamedTime) >= idleThreshold {
779+
mctx.lastAppliedBinlogEventTime = time.Now()
780+
}
781+
}
782+
783+
// GetBinlogWriterLag returns now - last applied event timestamp, the move-tables
784+
// writer lag. It returns 0 before any event has been applied.
785+
func (mctx *MigrationContext) GetBinlogWriterLag() time.Duration {
786+
mctx.binlogWriterLagMutex.Lock()
787+
defer mctx.binlogWriterLagMutex.Unlock()
788+
789+
if mctx.lastAppliedBinlogEventTime.IsZero() {
790+
return 0
791+
}
792+
lag := time.Since(mctx.lastAppliedBinlogEventTime)
793+
if lag < 0 {
794+
return 0
795+
}
796+
return lag
797+
}
798+
743799
func (mctx *MigrationContext) SetHeartbeatIntervalMilliseconds(heartbeatIntervalMilliseconds int64) {
744800
if heartbeatIntervalMilliseconds < 100 {
745801
heartbeatIntervalMilliseconds = 100

go/binlog/binlog_entry.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,19 @@ package binlog
77

88
import (
99
"fmt"
10+
"time"
1011

1112
"github.com/github/gh-ost/go/mysql"
1213
)
1314

1415
// BinlogEntry describes an entry in the binary log
1516
type BinlogEntry struct {
1617
Coordinates mysql.BinlogCoordinates
17-
DmlEvent *BinlogDMLEvent
18+
// Timestamp is the wall-clock time recorded in the binlog event header of the
19+
// event that produced this entry. It is used in move-tables mode to measure
20+
// writer lag (now - last applied event timestamp).
21+
Timestamp time.Time
22+
DmlEvent *BinlogDMLEvent
1823
}
1924

2025
// NewBinlogEntryAt creates an empty, ready to go BinlogEntry object

go/binlog/gomysql_reader.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ func (gmr *GoMySQLReader) handleRowsEvent(ev *replication.BinlogEvent, rowsEvent
9999
continue
100100
}
101101
binlogEntry := NewBinlogEntryAt(currentCoords)
102+
binlogEntry.Timestamp = time.Unix(int64(ev.Header.Timestamp), 0)
102103
binlogEntry.DmlEvent = NewBinlogDMLEvent(
103104
string(rowsEvent.Table.Schema),
104105
string(rowsEvent.Table.Table),

go/logic/hooks.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,17 @@ func (he *HooksExecutor) applyEnvironmentVariables(extraVariables ...string) []s
235235
env = append(env, fmt.Sprintf("GH_OST_EXECUTING_HOST=%s", he.migrationContext.Hostname))
236236
env = append(env, fmt.Sprintf("GH_OST_TARGET_HOST=%s", he.migrationContext.GetTargetHostname()))
237237
env = append(env, fmt.Sprintf("GH_OST_INSPECTED_LAG=%f", he.migrationContext.GetCurrentLagDuration().Seconds()))
238-
env = append(env, fmt.Sprintf("GH_OST_HEARTBEAT_LAG=%f", he.migrationContext.TimeSinceLastHeartbeatOnChangelog().Seconds()))
238+
// In move-tables mode there is no changelog heartbeat; writer lag (now - last
239+
// applied binlog event timestamp) replaces the heartbeat-derived lag. Re-point
240+
// GH_OST_HEARTBEAT_LAG at it so existing hooks keep seeing a meaningful value,
241+
// and also expose it explicitly as GH_OST_BINLOG_WRITER_LAG_SECONDS.
242+
heartbeatLagSeconds := he.migrationContext.TimeSinceLastHeartbeatOnChangelog().Seconds()
243+
binlogWriterLagSeconds := he.migrationContext.GetBinlogWriterLag().Seconds()
244+
if he.migrationContext.IsMoveTablesMode() {
245+
heartbeatLagSeconds = binlogWriterLagSeconds
246+
}
247+
env = append(env, fmt.Sprintf("GH_OST_HEARTBEAT_LAG=%f", heartbeatLagSeconds))
248+
env = append(env, fmt.Sprintf("GH_OST_BINLOG_WRITER_LAG_SECONDS=%f", binlogWriterLagSeconds))
239249
env = append(env, fmt.Sprintf("GH_OST_PROGRESS=%f", he.migrationContext.GetProgressPct()))
240250
env = append(env, fmt.Sprintf("GH_OST_ETA_SECONDS=%d", he.migrationContext.GetETASeconds()))
241251
env = append(env, fmt.Sprintf("GH_OST_HOOKS_HINT=%s", he.migrationContext.HooksHintMessage))

go/logic/migrator.go

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,10 @@ type lockProcessedStruct struct {
5555
}
5656

5757
type applyEventStruct struct {
58-
writeFunc *tableWriteFunc
59-
dmlEvent *binlog.BinlogDMLEvent
60-
coords mysql.BinlogCoordinates
58+
writeFunc *tableWriteFunc
59+
dmlEvent *binlog.BinlogDMLEvent
60+
coords mysql.BinlogCoordinates
61+
eventTimestamp time.Time
6162
}
6263

6364
func newApplyEventStructByFunc(writeFunc *tableWriteFunc) *applyEventStruct {
@@ -66,7 +67,7 @@ func newApplyEventStructByFunc(writeFunc *tableWriteFunc) *applyEventStruct {
6667
}
6768

6869
func newApplyEventStructByDML(dmlEntry *binlog.BinlogEntry) *applyEventStruct {
69-
result := &applyEventStruct{dmlEvent: dmlEntry.DmlEvent, coords: dmlEntry.Coordinates}
70+
result := &applyEventStruct{dmlEvent: dmlEntry.DmlEvent, coords: dmlEntry.Coordinates, eventTimestamp: dmlEntry.Timestamp}
7071
return result
7172
}
7273

@@ -2175,14 +2176,28 @@ func (mgtr *Migrator) printStatus(rule PrintStatusRule, writers ...io.Writer) {
21752176

21762177
currentBinlogCoordinates := mgtr.eventsStreamer.GetCurrentBinlogCoordinates()
21772178

2178-
status := fmt.Sprintf("Copy: %d/%d %.1f%%; Applied: %d; Backlog: %d/%d; Time: %+v(total), %+v(copy); streamer: %+v; Lag: %.2fs, HeartbeatLag: %.2fs, State: %s; ETA: %s",
2179+
// Lag reporting differs by mode. Standard mode reports the inspected replica
2180+
// "Lag" (changelog heartbeat) plus "HeartbeatLag". Move-tables mode has no
2181+
// changelog heartbeat, and the source-side replication "Lag" is meaningless
2182+
// (writes go to the target, so migration-induced replica lag appears on target
2183+
// replicas and is handled separately by throttling). The "Lag" field is
2184+
// therefore dropped and writer lag (now - last applied binlog event timestamp)
2185+
// is reported as "WriterLag" instead.
2186+
lagStatus := fmt.Sprintf("Lag: %.2fs, HeartbeatLag: %.2fs",
2187+
mgtr.migrationContext.GetCurrentLagDuration().Seconds(),
2188+
mgtr.migrationContext.TimeSinceLastHeartbeatOnChangelog().Seconds(),
2189+
)
2190+
if mgtr.migrationContext.IsMoveTablesMode() {
2191+
lagStatus = fmt.Sprintf("WriterLag: %.2fs", mgtr.migrationContext.GetBinlogWriterLag().Seconds())
2192+
}
2193+
2194+
status := fmt.Sprintf("Copy: %d/%d %.1f%%; Applied: %d; Backlog: %d/%d; Time: %+v(total), %+v(copy); streamer: %+v; %s, State: %s; ETA: %s",
21792195
totalRowsCopied, rowsEstimate, progressPct,
21802196
atomic.LoadInt64(&mgtr.migrationContext.TotalDMLEventsApplied),
21812197
len(mgtr.applyEventsQueue), cap(mgtr.applyEventsQueue),
21822198
base.PrettifyDurationOutput(elapsedTime), base.PrettifyDurationOutput(mgtr.migrationContext.ElapsedRowCopyTime()),
21832199
currentBinlogCoordinates.DisplayString(),
2184-
mgtr.migrationContext.GetCurrentLagDuration().Seconds(),
2185-
mgtr.migrationContext.TimeSinceLastHeartbeatOnChangelog().Seconds(),
2200+
lagStatus,
21862201
state,
21872202
eta,
21882203
)
@@ -2247,6 +2262,24 @@ func (mgtr *Migrator) initiateStreaming() error {
22472262
mgtr.migrationContext.SetRecentBinlogCoordinates(mgtr.eventsStreamer.GetCurrentBinlogCoordinates())
22482263
}
22492264
}()
2265+
2266+
// In move-tables mode there is no changelog heartbeat. Writer lag is derived
2267+
// from binlog-header timestamps of applied events; when the streamer has been
2268+
// silent for the heartbeat interval, treat that silence as "caught up" and bump
2269+
// the last-applied timestamp to now so lag does not climb forever while idle.
2270+
if mgtr.migrationContext.IsMoveTablesMode() {
2271+
go func() {
2272+
interval := time.Duration(mgtr.migrationContext.HeartbeatIntervalMilliseconds) * time.Millisecond
2273+
ticker := time.NewTicker(interval)
2274+
defer ticker.Stop()
2275+
for range ticker.C {
2276+
if atomic.LoadInt64(&mgtr.finishedMigrating) > 0 {
2277+
return
2278+
}
2279+
mgtr.migrationContext.BumpBinlogWriterLagIfIdle(interval)
2280+
}
2281+
}()
2282+
}
22502283
return nil
22512284
}
22522285

@@ -2263,6 +2296,11 @@ func (mgtr *Migrator) addDMLEventsListener() error {
22632296
mgtr.migrationContext.DatabaseName,
22642297
originalTableName,
22652298
func(dmlEntry *binlog.BinlogEntry) error {
2299+
// Record that the streamer just delivered an event for the moved table,
2300+
// so the idle-bump rule can tell "falling behind" from "source is quiet".
2301+
if mgtr.migrationContext.IsMoveTablesMode() {
2302+
mgtr.migrationContext.MarkBinlogEventStreamed()
2303+
}
22662304
// Use helper to prevent deadlock if buffer fills and executeWriteFuncs exits
22672305
// This is critical because this callback blocks the event streamer
22682306
return base.SendWithContext(mgtr.migrationContext.GetContext(), mgtr.applyEventsQueue, newApplyEventStructByDML(dmlEntry))
@@ -2499,6 +2537,7 @@ func (mgtr *Migrator) onApplyEventStruct(eventStruct *applyEventStruct) error {
24992537
if eventStruct.dmlEvent != nil {
25002538
dmlEvents := [](*binlog.BinlogDMLEvent){}
25012539
dmlEvents = append(dmlEvents, eventStruct.dmlEvent)
2540+
lastEventTimestamp := eventStruct.eventTimestamp
25022541
var nonDmlStructToApply *applyEventStruct
25032542

25042543
availableEvents := len(mgtr.applyEventsQueue)
@@ -2516,6 +2555,7 @@ func (mgtr *Migrator) onApplyEventStruct(eventStruct *applyEventStruct) error {
25162555
break
25172556
}
25182557
dmlEvents = append(dmlEvents, additionalStruct.dmlEvent)
2558+
lastEventTimestamp = additionalStruct.eventTimestamp
25192559
}
25202560
// Create a task to apply the DML event; this will be execute by executeWriteFuncs()
25212561
var applyEventFunc tableWriteFunc = func() error {
@@ -2529,6 +2569,12 @@ func (mgtr *Migrator) onApplyEventStruct(eventStruct *applyEventStruct) error {
25292569
mgtr.applier.CurrentCoordinates = eventStruct.coords
25302570
mgtr.applier.CurrentCoordinatesMutex.Unlock()
25312571

2572+
// In move-tables mode there is no changelog heartbeat; writer lag is derived
2573+
// from the binlog-header timestamp of the last event we just applied.
2574+
if mgtr.migrationContext.IsMoveTablesMode() && !lastEventTimestamp.IsZero() {
2575+
mgtr.migrationContext.UpdateLastAppliedBinlogEventTime(lastEventTimestamp)
2576+
}
2577+
25322578
if nonDmlStructToApply != nil {
25332579
// We pulled DML events from the queue, and then we hit a non-DML event. Wait!
25342580
// We need to handle it!

script/move-tables/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ script/move-tables/setup
77

88
Verify data is present in the source cluster.
99
```bash
10-
script/move-tables/mysql-source-primary -D gh_ost_test_db -e "SELECT * FROM gh_ost_test;"
10+
script/move-tables/mysql-source-primary -D test -e "SELECT * FROM gh_ost_test;"
1111
```
1212

1313
Verify the empty database is present in the target cluster.
1414
```bash
15-
script/move-tables/mysql-target-primary -D gh_ost_test_db -e "SHOW TABLES;"
15+
script/move-tables/mysql-target-primary -D test -e "SHOW TABLES;"
1616
```
1717

1818
### Testing `gh-ost`
@@ -24,7 +24,7 @@ script/build --cli
2424

2525
Run gh-ost to move tables:
2626
```bash
27-
./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 --postpone-cut-over-flag-file=/tmp/ghost-move-tables.postpone.flag --execute --verbose --checkpoint --checkpoint-seconds 10 --initially-drop-socket-file
27+
./script/build --cli; ./bin/gh-ost --move-tables=gh_ost_test --host=localhost --port=3308 --user root --password opensesame --database=test --target-host=localhost --target-port=3309 --target-user root --target-password opensesame --target-database=test --postpone-cut-over-flag-file=/tmp/ghost-move-tables.postpone.flag --execute --verbose --checkpoint --checkpoint-seconds 10 --initially-drop-socket-file
2828
```
2929

3030
Start continuous inserts against the source.

script/move-tables/insert-source-primary-loop

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ start_i="${1:-100000}"
1313
delay="${2:-0.2}"
1414
rows_per_batch="${3:-1}"
1515
i="$start_i"
16-
DATABASE="${DATABASE:-gh_ost_test_db}"
16+
DATABASE="${DATABASE:-test}"
1717

1818
echo "Starting continuous inserts on source primary. Press Ctrl+C to stop."
1919
echo "start_column1=$start_i sleep_seconds=$delay rows_per_batch=$rows_per_batch"

script/move-tables/reset

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ set -euo pipefail
44

55
GH_OST_ROOT=$(git rev-parse --show-toplevel)
66
SCRIPT_PATH="${GH_OST_ROOT}/script/move-tables"
7-
DATABASE_NAME="${GH_OST_TEST_DB:-gh_ost_test_db}"
7+
DATABASE_NAME="${GH_OST_TEST_DB:-test}"
88

99
# Reset source table state regardless of whether cutover renamed it.
1010
${SCRIPT_PATH}/mysql-source-primary -D "${DATABASE_NAME}" -e "DROP TABLE IF EXISTS _gh_ost_test_del, gh_ost_test;"
1111

1212
# Recreate and seed source table data, same fixture as setup uses.
13-
${SCRIPT_PATH}/mysql-source-primary -D "${DATABASE_NAME}" < "${GH_OST_ROOT}/localtests/move-tables/create.sql"
13+
${SCRIPT_PATH}/mysql-source-primary -D "${DATABASE_NAME}" < "${GH_OST_ROOT}/localtests/move-tables/single/create.sql"
1414

1515
${SCRIPT_PATH}/mysql-target-primary -D "${DATABASE_NAME}" -e "DROP TABLE IF EXISTS gh_ost_test, _gh_ost_test_ghk;"
1616

0 commit comments

Comments
 (0)