Skip to content

Commit c6ca7ef

Browse files
Merge branch 'chriskirkland/issues-8260-pt2' into multitable-spike
2 parents b133029 + 288f0a8 commit c6ca7ef

37 files changed

Lines changed: 2819 additions & 38 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@
44
/.vendor/
55
.idea/
66
*.tmp
7+
8+
tools/
9+
*__failpoint_binding__.go
10+
*.go__failpoint_stash__

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ require (
5555
github.com/opencontainers/go-digest v1.0.0 // indirect
5656
github.com/opencontainers/image-spec v1.1.1 // indirect
5757
github.com/pingcap/errors v0.11.5-0.20260310054046-9c8b3586e4b2 // indirect
58+
github.com/pingcap/failpoint v0.0.0-20260521055755-e7642935314f // indirect
5859
github.com/pingcap/log v1.1.1-0.20260227082333-572e590d08f1 // indirect
5960
github.com/pingcap/tidb/pkg/parser v0.0.0-20260504140133-511dba1dbe17 // indirect
6061
github.com/pkg/errors v0.9.1 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb h1:3pSi4EDG6hg0o
117117
github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg=
118118
github.com/pingcap/errors v0.11.5-0.20260310054046-9c8b3586e4b2 h1:cLgCk5mwDG9lDH+dPK8TmEliTjyGJwwKN0qevWAl8IY=
119119
github.com/pingcap/errors v0.11.5-0.20260310054046-9c8b3586e4b2/go.mod h1:ktAJCA9lxrHHjVyVl2pKJFvzBnq2eZbb+CUOjBRPlXo=
120+
github.com/pingcap/failpoint v0.0.0-20260521055755-e7642935314f h1:cDo4qNgaQc2POMWTXjNrMA7yySdIF/d1AaW8kOA7qOs=
121+
github.com/pingcap/failpoint v0.0.0-20260521055755-e7642935314f/go.mod h1:jimwlLpI/XtwQdlZML15HS+j4rirvwZM0GLY07wwgOo=
120122
github.com/pingcap/log v1.1.1-0.20230317032135-a0d097d16e22 h1:2SOzvGvE8beiC1Y4g9Onkvu6UmuBBOeWRGQEjJaT/JY=
121123
github.com/pingcap/log v1.1.1-0.20230317032135-a0d097d16e22/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4=
122124
github.com/pingcap/log v1.1.1-0.20260227082333-572e590d08f1 h1:A2bEfgSb7hLwR9mxDszgGKweF+xY9YoTDG+8RjdFjDE=

go/base/context.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ import (
2525
"github.com/github/gh-ost/go/metrics"
2626
"github.com/github/gh-ost/go/mysql"
2727
"github.com/github/gh-ost/go/sql"
28-
"github.com/openark/golib/log"
2928

3029
"github.com/go-ini/ini"
30+
"github.com/openark/golib/log"
31+
"github.com/pingcap/failpoint"
3132
)
3233

3334
// RowsEstimateMethod is the type of row number estimation
@@ -451,6 +452,8 @@ type MigrationContext struct {
451452
DrainGTID mysql.BinlogCoordinates // Source @@gtid_executed captured immediately after the source RENAME TABLE; the applier drains until it reaches this coordinate (move-tables only).
452453
}
453454

455+
UnsafeFailPointsEnabled bool
456+
454457
Log Logger
455458
}
456459

@@ -1449,3 +1452,33 @@ func SendWithContext[T any](ctx context.Context, ch chan<- T, val T) error {
14491452
return ctx.Err()
14501453
}
14511454
}
1455+
1456+
type failPointOpts struct {
1457+
wait time.Duration
1458+
}
1459+
1460+
type FailPointOpt func(*failPointOpts)
1461+
1462+
// WithFailPointWait sets the time for a fail point to wait before exiting.
1463+
func WithFailPointWait(wait time.Duration) FailPointOpt {
1464+
return func(opts *failPointOpts) {
1465+
opts.wait = wait
1466+
}
1467+
}
1468+
1469+
func (mctx *MigrationContext) NewFailPoint(name string, opts ...FailPointOpt) {
1470+
if mctx.UnsafeFailPointsEnabled {
1471+
var fpo failPointOpts
1472+
for _, opt := range opts {
1473+
opt(&fpo)
1474+
}
1475+
1476+
failpoint.Inject(name, func(_ failpoint.Value) {
1477+
mctx.Log.Debugf("[TEST] Encountered fail point: '%s'", name)
1478+
if fpo.wait > 0 {
1479+
time.Sleep(fpo.wait)
1480+
}
1481+
panic(fmt.Sprintf("[TEST] Encountered fail point: '%s'", name))
1482+
})
1483+
}
1484+
}

go/cmd/gh-ost/main.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ func main() {
197197
flag.StringVar(&migrationContext.MoveTables.TargetDatabase, "target-database", "", "Target MySQL database name for --move-tables mode. If not provided, uses the same database name as the source connection")
198198
flag.BoolVar(&migrationContext.MoveTables.AllowOnSourcePrimary, "allow-on-source-primary", false, "allow --move-tables to read (schema, row copy, binlog) from the source cluster's primary. By default gh-ost stops if --host is the primary; prefer pointing --host at a replica to spare the primary the copy load.")
199199

200+
// unsafe fail points, for integration testing purposes
201+
flag.BoolVar(&migrationContext.UnsafeFailPointsEnabled, "unsafe-fail-points-enabled", false, "UNSAFE: Enable fail points for integration testing purposes. Do not use in production.")
202+
200203
flag.CommandLine.SetOutput(os.Stdout)
201204
flag.Parse()
202205
cutOverLockTimeoutUserSpecified := false
@@ -345,7 +348,9 @@ func main() {
345348
if *storageEngine == "rocksdb" {
346349
migrationContext.Log.Warning("RocksDB storage engine support is experimental")
347350
}
348-
if migrationContext.CheckpointIntervalSeconds < 10 {
351+
// ignore low checkpoint intervals in unsafe mode as frequent checkpoints are required to reliably
352+
// reduce test duration
353+
if migrationContext.CheckpointIntervalSeconds < 10 && !migrationContext.UnsafeFailPointsEnabled {
349354
migrationContext.Log.Fatalf("--checkpoint-seconds should be >=10")
350355
}
351356
if migrationContext.CountTableRows && migrationContext.PanicOnWarnings {

go/logic/migrator.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,6 +1459,9 @@ func (mgtr *Migrator) moveTablesCutOver() (err error) {
14591459
}
14601460
}
14611461

1462+
mgtr.migrationContext.NewFailPoint("panic-before-drain-completion", base.WithFailPointWait(2*time.Second))
1463+
1464+
// ------ T3: draining applier to drain GTID -----------
14621465
if err := mgtr.drainMoveTablesCutOver(drainGTID); err != nil {
14631466
return err
14641467
}
@@ -1476,6 +1479,8 @@ func (mgtr *Migrator) moveTablesCutOver() (err error) {
14761479
atomic.StoreInt64(&mgtr.migrationContext.CutOverCompleteFlag, 1)
14771480
mgtr.migrationContext.Log.Debugf("T4: CutOverCompleteFlag set")
14781481

1482+
mgtr.migrationContext.NewFailPoint("panic-before-on-success-hook", base.WithFailPointWait(2*time.Second))
1483+
14791484
// ----- T5: on-success hook -----
14801485
// Hook unlocks user_rw@target via db-user-management and flips the
14811486
// write_cutover? feature flag. Standard env vars only — GH_OST_DRAIN_GTID +
@@ -2661,6 +2666,8 @@ func (mgtr *Migrator) iterateChunks() error {
26612666
}
26622667
return terminateRowIteration(err)
26632668
}
2669+
2670+
mgtr.migrationContext.NewFailPoint("panic-after-row-copy", base.WithFailPointWait(2*time.Second))
26642671
}
26652672
}
26662673

localtests/move-tables-test.sh

Lines changed: 72 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ original_sql_mode=
3636
current_gtid_mode=
3737
test_timeout=120
3838
test_failure_log_tail_lines=50
39+
tables_to_migrate=()
3940

4041
OPTIND=1
4142
while getopts "b:s:dg" OPTION; do
@@ -175,8 +176,13 @@ build_ghost_command() {
175176
# Build gh-ost command with all standard options
176177
#
177178
# expected $1 to be a comma-separated list of tables to move
179+
180+
# build comma-separated list of tables to move
181+
move_tables_arg=$(IFS=, ; echo "${tables_to_migrate[*]}")
182+
183+
# NOTE(chriskirkland): fully qualified package name + failpoint name
178184
cmd="GOTRACEBACK=crash $ghost_binary \
179-
--move-tables=$1 \
185+
--move-tables=$move_tables_arg \
180186
--user=root \
181187
--password=opensesame \
182188
--host=$source_replica_host \
@@ -197,7 +203,13 @@ build_ghost_command() {
197203
--stack \
198204
--checkpoint \
199205
--postpone-cut-over-flag-file=$postpone_cutover_flag_file \
206+
--checkpoint-seconds=1 \
207+
--unsafe-fail-points-enabled \
200208
--execute ${extra_args[@]}"
209+
210+
if [ -n "$GO_FAILPOINTS" ]; then
211+
cmd="GO_FAILPOINTS=\"$GO_FAILPOINTS\" $cmd"
212+
fi
201213
}
202214

203215
print_log_excerpt() {
@@ -348,46 +360,47 @@ test_single() {
348360
wait $test_pid 2>/dev/null
349361
execution_result=$?
350362
return $execution_result
351-
fi
352363

353-
# kick off the on_test script for the test. this enables arbitrary custom logic
354-
# concurrent with the gh-ost process. this enables additional scenarios like
355-
# streaming of writes prior to the write cutover.
356-
#
357-
# IMPORTANT: The on-test script is executed in the background and will be killed as soon
358-
# as the gh-ost process terminates.
359-
if [ -f $tests_path/$test_name/on_test.sh ]; then
360-
$tests_path/$test_name/on_test.sh &> /dev/null &
361-
on_test_pid=$!
362-
fi
364+
else
363365

364-
# queue up removal of the postpone cutover flag, otherwise gh-ost hangs on the cutover
365-
(
366-
sleep 1;
367-
echo "⏩ Sending unpostpone cutover"
368-
rm $postpone_cutover_flag_file &> /dev/null;
369-
) &
366+
# kick off the on_test script for the test. this enables arbitrary custom logic
367+
# concurrent with the gh-ost process. this enables additional scenarios like
368+
# streaming of writes prior to the write cutover.
369+
#
370+
# IMPORTANT: The on-test script is executed in the background and will be killed as soon
371+
# as the gh-ost process terminates.
372+
if [ -f $tests_path/$test_name/on_test.sh ]; then
373+
$tests_path/$test_name/on_test.sh &> /dev/null &
374+
on_test_pid=$!
375+
fi
370376

371-
# Build and execute gh-ost command
372-
move_tables_arg=$(IFS=, ; echo "${tables_to_migrate[*]}")
373-
build_ghost_command "$move_tables_arg"
374-
echo_dot
375-
echo $cmd >$exec_command_file
376-
echo_dot
377-
timeout $test_timeout bash $exec_command_file >$test_logfile 2>&1
377+
# queue up removal of the postpone cutover flag, otherwise gh-ost hangs on the cutover
378+
(
379+
sleep 1;
380+
echo "⏩ Sending unpostpone cutover"
381+
rm $postpone_cutover_flag_file &> /dev/null;
382+
) &
378383

379-
execution_result=$?
384+
# Build and execute gh-ost command
385+
build_ghost_command
386+
echo_dot
387+
echo $cmd >$exec_command_file
388+
echo_dot
389+
timeout $test_timeout bash $exec_command_file >$test_logfile 2>&1
380390

381-
if [ -n "$on_test_pid" ]; then
382-
kill -KILL $on_test_pid &>/dev/null
383-
fi
391+
execution_result=$?
384392

385-
# Check for timeout (exit code 124)
386-
if [ $execution_result -eq 124 ]; then
387-
echo
388-
echo "ERROR $test_name execution timed out"
389-
print_log_excerpt
390-
return 1
393+
if [ -n "$on_test_pid" ]; then
394+
kill -KILL $on_test_pid &>/dev/null
395+
fi
396+
397+
# Check for timeout (exit code 124)
398+
if [ $execution_result -eq 124 ]; then
399+
echo
400+
echo "ERROR $test_name execution timed out"
401+
print_log_excerpt
402+
return 1
403+
fi
391404
fi
392405

393406
if [ -f $tests_path/$test_name/sql_mode ]; then
@@ -453,7 +466,29 @@ test_single() {
453466
done
454467
}
455468

469+
enable_failpoint() {
470+
mkdir -p $repo_root/tools/bin
471+
if [ ! -f $repo_root/tools/bin/failpoint-ctl ]; then
472+
echo "⚙️ Installing failpoint"
473+
GOBIN=$repo_root/tools/bin go install github.com/pingcap/failpoint/failpoint-ctl@v0.0.0-20220801062533-2eaa32854a6c
474+
fi
475+
476+
echo "⚙️ Enabling failpoint"
477+
$repo_root/tools/bin/failpoint-ctl enable go
478+
479+
echo "✅ Successfully enabled failpoint"
480+
}
481+
482+
disable_failpoint() {
483+
echo "⚙️ Disabling failpoint"
484+
$repo_root/tools/bin/failpoint-ctl disable go
485+
486+
echo "✅ Successfully disabled failpoint"
487+
}
488+
456489
build_binary() {
490+
enable_failpoint
491+
457492
echo "Building"
458493
rm -f $default_ghost_binary
459494
[ "$ghost_binary" == "" ] && ghost_binary="$default_ghost_binary"
@@ -468,6 +503,8 @@ build_binary() {
468503
echo "Build failure"
469504
exit 1
470505
fi
506+
507+
disable_failpoint
471508
}
472509

473510
test_all() {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
drop table if exists gh_ost_test;
2+
create table gh_ost_test (
3+
id bigint(20) NOT NULL AUTO_INCREMENT,
4+
column1 int(11) NOT NULL,
5+
column2 smallint(5) unsigned NOT NULL,
6+
column3 mediumint(8) unsigned NOT NULL,
7+
column4 tinyint(3) unsigned NOT NULL,
8+
column5 int(11) NOT NULL,
9+
column6 int(11) NOT NULL,
10+
PRIMARY KEY (id),
11+
KEY c12_ix (column1, column2)
12+
) auto_increment=1;
13+
14+
insert into gh_ost_test values
15+
(NULL, 1001, 100, 500000, 10, 1700000001, 1700000002),
16+
(NULL, 1002, 200, 600000, 20, 1700000003, 1700000004),
17+
(NULL, 1003, 300, 700000, 30, 1700000005, 1700000006),
18+
(NULL, 1004, 400, 800000, 40, 1700000007, 1700000008),
19+
(NULL, 1005, 500, 900000, 50, 1700000009, 1700000010),
20+
(NULL, 1006, 600, 1000000, 60, 1700000011, 1700000012),
21+
(NULL, 1007, 700, 1100000, 70, 1700000013, 1700000014),
22+
(NULL, 1008, 800, 1200000, 80, 1700000015, 1700000016),
23+
(NULL, 1009, 900, 1300000, 90, 1700000017, 1700000018),
24+
(NULL, 1010, 1000, 1400000, 100, 1700000019, 1700000020),
25+
(NULL, 1011, 1100, 1500000, 110, 1700000021, 1700000022),
26+
(NULL, 1012, 1200, 1600000, 120, 1700000023, 1700000024),
27+
(NULL, 1013, 1300, 1700000, 130, 1700000025, 1700000026),
28+
(NULL, 1014, 1400, 1800000, 140, 1700000027, 1700000028),
29+
(NULL, 1015, 1500, 1900000, 150, 1700000029, 1700000030),
30+
(NULL, 1016, 1600, 2000000, 160, 1700000031, 1700000032),
31+
(NULL, 1017, 1700, 2100000, 170, 1700000033, 1700000034),
32+
(NULL, 1018, 1800, 2200000, 180, 1700000035, 1700000036),
33+
(NULL, 1019, 1900, 2300000, 190, 1700000037, 1700000038),
34+
(NULL, 1020, 2000, 2400000, 200, 1700000039, 1700000040);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
gh_ost_test

0 commit comments

Comments
 (0)