Skip to content

Commit 2db5332

Browse files
committed
feat(move-tables): add moveTablesCutOver skeleton and wire into MoveTables (#8209)
Adds the cutover orchestration entry point with T0-T6 protocol structure as labeled blocks. Protocol logic lands in the next commit. Replaces the //TODO: cutover here seam with the actual call site. Ref: database-infrastructure#8209
1 parent 6ffb6ff commit 2db5332

1 file changed

Lines changed: 80 additions & 1 deletion

File tree

go/logic/migrator.go

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -886,7 +886,9 @@ func (mgtr *Migrator) MoveTables() (err error) {
886886
return err
887887
}
888888

889-
//TODO: cutover here
889+
if err := mgtr.moveTablesCutOver(); err != nil {
890+
return err
891+
}
890892

891893
if err := mgtr.finalCleanup(); err != nil {
892894
return nil
@@ -904,6 +906,83 @@ func (mgtr *Migrator) MoveTables() (err error) {
904906
return nil
905907
}
906908

909+
// moveTablesCutOver orchestrates the cooperative cutover protocol for move-tables
910+
// mode. It implements the T0-T6 transitions described in
911+
// docs/learning/design-refs/coop_cutover.md §1.3.
912+
//
913+
// NOT the standard cutOver() path: every internal call from cutOver() (throttle,
914+
// atomicCutOver, waitForEventsUpToLock, heartbeat-lag) was built on a
915+
// single-server assumption that no longer holds when the applier writes target
916+
// and the streamer reads source. Each is replaced or dropped here.
917+
//
918+
// This commit (1 of 3) lays out the protocol as labeled comment blocks only.
919+
// The protocol logic (RENAME, GTID capture, drain poll, flag set, hooks) lands
920+
// in commit 2. Tests land in commit 3.
921+
func (mgtr *Migrator) moveTablesCutOver() (err error) {
922+
if mgtr.migrationContext.Noop {
923+
mgtr.migrationContext.Log.Debugf("Noop operation; not really moving tables")
924+
return nil
925+
}
926+
927+
// ----- Postpone gate (precedes T0) -----
928+
// Reuses sleepWhileTrue with a predicate that KEEPS the postpone-flag-file
929+
// branch from standard cutOver() (migrator.go:965-984) and DROPS the
930+
// heartbeat-lag branch (lines 958-964). The heartbeat-lag check is dropped
931+
// because move-tables mode disables _ghc writes (#8206), so
932+
// TimeSinceLastHeartbeatOnChangelog() returns ~58 years (time.Since(zero))
933+
// and would deadlock the gate forever.
934+
// TODO(#8209 commit 2): implement postpone gate (flag-file + unpostpone
935+
// socket command, with OnBeginPostponed firing once).
936+
937+
// ----- T0: on-before-cut-over hook -----
938+
// Fires gh-ost-on-before-cut-over* via mgtr.hooksExecutor.OnBeforeCutOver().
939+
// Non-zero hook exit aborts cutover BEFORE any source DDL.
940+
// TODO(#8209 commit 2): implement T0 hook call with error propagation.
941+
942+
// ----- T1: RENAME source table -----
943+
// Connection: mgtr.inspector.db (privileged source connection per
944+
// move_table_mode.md §1.5; same connection as T2 so the RENAME's own GTID
945+
// is visible in @@gtid_executed when T2 reads it).
946+
// Exec: sqlutils.ExecNoPrepare with no retry — RENAME is not idempotent;
947+
// a partial success leaves the table already renamed and a retry would
948+
// fail. On error: return wrapped err; operator re-runs the whole hook.
949+
// SQL shape: RENAME TABLE `<source_db>`.`<table>` TO `<source_db>`.`_<table>_del`
950+
// TODO(#8209 commit 2): implement RENAME via inspector.db.
951+
952+
// ----- T2: capture @@gtid_executed -----
953+
// Same connection as T1. Parse the result into a *mysql.GTIDBinlogCoordinates
954+
// (the drain GTID — a closed superset of every committed source write to
955+
// the migrated table, per coop_cutover.md §3.2 steps 1-3).
956+
// TODO(#8209 commit 2): implement GTID capture on the inspector connection.
957+
958+
// ----- T3: drain poll -----
959+
// Loop ~100ms ticks (per move_table_mode.md §1.5) until
960+
// applier.CurrentCoordinates contains the drain GTID, comparing via
961+
// GTIDBinlogCoordinates.SmallerThan (go/mysql/binlog_gtid.go). Reads of
962+
// CurrentCoordinates MUST hold CurrentCoordinatesMutex (applier.go:75).
963+
// Timeout after CutOverLockTimeoutSeconds; on timeout return an error
964+
// naming the drain.
965+
// TODO(#8209 commit 2): implement drain poll with mutex-protected reads.
966+
967+
// ----- T4: set CutOverCompleteFlag -----
968+
// atomic.StoreInt64(&mgtr.migrationContext.CutOverCompleteFlag, 1).
969+
// MUST be set BEFORE T5 so the streamer's StreamEvents(canStopStreaming)
970+
// loop can wind down in parallel with the (potentially slow) on-success
971+
// hook. Forgetting this flag has no visible failure mode at the call site
972+
// — the run silently hangs after cutover.
973+
// TODO(#8209 commit 2): implement T4 flag set.
974+
975+
// ----- T5: fire on-success hook -----
976+
// mgtr.hooksExecutor.OnSuccess(false). Hook unlocks user_rw@target via
977+
// db-user-management and flips the write_cutover? feature flag.
978+
// Standard env vars only — GH_OST_DRAIN_GTID + GH_OST_TARGET_* are #8211
979+
// (1.7), not this PR.
980+
// TODO(#8209 commit 2): implement T5 hook call with error propagation.
981+
982+
// ----- T6: return nil -----
983+
return nil
984+
}
985+
907986
// ExecOnFailureHook executes the onFailure hook, and this method is provided as the only external
908987
// hook access point
909988
func (mgtr *Migrator) ExecOnFailureHook() (err error) {

0 commit comments

Comments
 (0)