diff --git a/go/vt/throttler/throttler.go b/go/vt/throttler/throttler.go index 179b6f1059f..87184e0e751 100644 --- a/go/vt/throttler/throttler.go +++ b/go/vt/throttler/throttler.go @@ -44,9 +44,9 @@ const ( // not throttled. NotThrottled time.Duration = 0 - // ZeroRateNoProgess can be used to set maxRate to 0. In this case, the + // ZeroRateNoProgress can be used to set maxRate to 0. In this case, the // throttler won't let any requests through until the rate is increased again. - ZeroRateNoProgess = 0 + ZeroRateNoProgress = 0 // MaxRateModuleDisabled can be set in NewThrottler() to disable throttling // by a fixed rate. @@ -262,7 +262,7 @@ func (t *Throttler) updateMaxRate() { return } - if maxRate != ZeroRateNoProgess && maxRate < int64(threadsRunning) { + if maxRate != ZeroRateNoProgress && maxRate < int64(threadsRunning) { log.Warningf("Set maxRate is less than the number of threads (%v). To prevent threads from starving, maxRate was increased from: %v to: %v.", threadsRunning, maxRate, threadsRunning) maxRate = int64(threadsRunning) } diff --git a/go/vt/throttler/throttler_test.go b/go/vt/throttler/throttler_test.go index b8d6faf6363..b735e3c04e4 100644 --- a/go/vt/throttler/throttler_test.go +++ b/go/vt/throttler/throttler_test.go @@ -324,7 +324,7 @@ func TestThreadFinished(t *testing.T) { func TestThrottle_MaxRateIsZero(t *testing.T) { fc := &fakeClock{} // 1 Thread, 0 QPS. - throttler, _ := newThrottlerWithClock("test", "queries", 1, ZeroRateNoProgess, ReplicationLagModuleDisabled, fc.now) + throttler, _ := newThrottlerWithClock("test", "queries", 1, ZeroRateNoProgress, ReplicationLagModuleDisabled, fc.now) defer throttler.Close() fc.setNow(1000 * time.Millisecond) diff --git a/go/vt/worker/defaults.go b/go/vt/worker/defaults.go index 7bf60295f71..adf2ccf3546 100644 --- a/go/vt/worker/defaults.go +++ b/go/vt/worker/defaults.go @@ -46,11 +46,12 @@ const ( // StreamExecute response. As of 06/2015, the default for it was 32 kB. // Note that higher values for this flag --destination_pack_count will // increase memory consumption in vtworker, vttablet and mysql. - defaultDestinationPackCount = 10 - defaultDestinationWriterCount = 20 - defaultMinHealthyRdonlyTablets = 2 - defaultDestTabletType = "RDONLY" - defaultParallelDiffsCount = 8 - defaultMaxTPS = throttler.MaxRateModuleDisabled - defaultMaxReplicationLag = throttler.ReplicationLagModuleDisabled + defaultDestinationPackCount = 10 + defaultDestinationWriterCount = 20 + defaultMinHealthyTablets = 2 + defaultDestTabletType = "RDONLY" + defaultParallelDiffsCount = 8 + defaultMaxTPS = throttler.MaxRateModuleDisabled + defaultMaxReplicationLag = throttler.ReplicationLagModuleDisabled + defaultUseConsistentSnapshot = false ) diff --git a/go/vt/worker/diff_utils.go b/go/vt/worker/diff_utils.go index 551e12281fa..4ff6ebc92ad 100644 --- a/go/vt/worker/diff_utils.go +++ b/go/vt/worker/diff_utils.go @@ -27,6 +27,8 @@ import ( "time" "vitess.io/vitess/go/vt/vterrors" + "vitess.io/vitess/go/vt/vttablet/tmclient" + "vitess.io/vitess/go/vt/wrangler" "golang.org/x/net/context" @@ -36,6 +38,7 @@ import ( "vitess.io/vitess/go/vt/grpcclient" "vitess.io/vitess/go/vt/key" "vitess.io/vitess/go/vt/logutil" + "vitess.io/vitess/go/vt/proto/query" "vitess.io/vitess/go/vt/topo" "vitess.io/vitess/go/vt/topo/topoproto" "vitess.io/vitess/go/vt/vtgate/vindexes" @@ -157,8 +160,8 @@ func (qrr *QueryResultReader) Fields() []*querypb.Field { } // Close closes the connection to the tablet. -func (qrr *QueryResultReader) Close(ctx context.Context) error { - return qrr.conn.Close(ctx) +func (qrr *QueryResultReader) Close(ctx context.Context) { + qrr.conn.Close(ctx) } // v3KeyRangeFilter is a sqltypes.ResultStream implementation that filters @@ -266,6 +269,16 @@ func TransactionalTableScan(ctx context.Context, log logutil.Logger, ts *topo.Se return NewTransactionalQueryResultReaderForTablet(ctx, ts, tabletAlias, sql, txID) } +// CreateTargetFrom is a helper function +func CreateTargetFrom(tablet *topodatapb.Tablet) *query.Target { + return &query.Target{ + Cell: tablet.Alias.Cell, + Keyspace: tablet.Keyspace, + Shard: tablet.Shard, + TabletType: tablet.Type, + } +} + // TableScanByKeyRange returns a QueryResultReader that gets all the // rows from a table that match the supplied KeyRange, ordered by // Primary Key. The returned columns are ordered with the Primary Key @@ -637,3 +650,129 @@ func (rd *RowDiffer) Go(log logutil.Logger) (dr DiffReport, err error) { advanceRight = true } } + +// createTransactions returns an array of transactions that all share the same view of the data. +// It will check that no new transactions have been seen between the creation of the underlying transactions, +// to guarantee that all TransactionalTableScanner are pointing to the same point +func createTransactions(ctx context.Context, numberOfScanners int, wr *wrangler.Wrangler, cleaner *wrangler.Cleaner, queryService queryservice.QueryService, target *query.Target, tabletInfo *topodatapb.Tablet) ([]int64, error) { + scanners := make([]int64, numberOfScanners) + for i := 0; i < numberOfScanners; i++ { + + tx, err := queryService.Begin(ctx, target, &query.ExecuteOptions{ + // Make sure our tx is not killed by tx sniper + Workload: query.ExecuteOptions_DBA, + TransactionIsolation: query.ExecuteOptions_CONSISTENT_SNAPSHOT_READ_ONLY, + }) + if err != nil { + return nil, fmt.Errorf("could not open transaction on %v\n%v", topoproto.TabletAliasString(tabletInfo.Alias), err) + } + + // Remember to rollback the transactions + cleaner.Record("CloseTransaction", topoproto.TabletAliasString(tabletInfo.Alias), func(ctx context.Context, wr *wrangler.Wrangler) error { + queryService, err := tabletconn.GetDialer()(tabletInfo, true) + if err != nil { + return err + } + return queryService.Rollback(ctx, target, tx) + }) + + scanners[i] = tx + } + + return scanners, nil +} + +// TableScanner is a simple abstraction that allows a TableScanner user to remain impervious +// by the transactionality of the connection +type TableScanner interface { + ScanTable(ctx context.Context, td *tabletmanagerdatapb.TableDefinition) (*QueryResultReader, error) +} + +// TransactionalTableScanner works inside of a transaction set up with CONSISTENT SNAPSHOT +type TransactionalTableScanner struct { + wr *wrangler.Wrangler + cleaner *wrangler.Cleaner + tabletAlias *topodatapb.TabletAlias + queryService queryservice.QueryService + tx int64 +} + +// ScanTable performs a full table scan, ordered by the primary keys, if any +func (tt TransactionalTableScanner) ScanTable(ctx context.Context, td *tabletmanagerdatapb.TableDefinition) (*QueryResultReader, error) { + return TransactionalTableScan(ctx, tt.wr.Logger(), tt.wr.TopoServer(), tt.tabletAlias, tt.tx, td) +} + +// NonTransactionalTableScanner just passes through the queries, and relies on paused replication traffic taking care of the consistent snapshot part +type NonTransactionalTableScanner struct { + wr *wrangler.Wrangler + cleaner *wrangler.Cleaner + tabletAlias *topodatapb.TabletAlias + queryService queryservice.QueryService +} + +// ScanTable performs a full table scan, ordered by the primary keys, if any +func (ntts NonTransactionalTableScanner) ScanTable(ctx context.Context, td *tabletmanagerdatapb.TableDefinition) (*QueryResultReader, error) { + return TableScan(ctx, ntts.wr.Logger(), ntts.wr.TopoServer(), ntts.tabletAlias, td) +} + +// CreateConsistentTableScanners will momentarily stop updates on the tablet, and then create connections that are all +// consistent snapshots of the same point in the transaction history +func CreateConsistentTableScanners(ctx context.Context, tablet *topo.TabletInfo, wr *wrangler.Wrangler, cleaner *wrangler.Cleaner, numberOfScanners int) ([]TableScanner, string, error) { + txs, gtid, err := CreateConsistentTransactions(ctx, tablet, wr, cleaner, numberOfScanners) + if err != nil { + return nil, "", err + } + + queryService, err := tabletconn.GetDialer()(tablet.Tablet, true) + defer queryService.Close(ctx) + + scanners := make([]TableScanner, numberOfScanners) + for i, tx := range txs { + scanners[i] = TransactionalTableScanner{ + wr: wr, + cleaner: cleaner, + tabletAlias: tablet.Alias, + queryService: queryService, + tx: tx, + } + } + + return scanners, gtid, nil +} + +// CreateConsistentTransactions creates a number of consistent snapshot transactions, +// all starting from the same spot in the tx log +func CreateConsistentTransactions(ctx context.Context, tablet *topo.TabletInfo, wr *wrangler.Wrangler, cleaner *wrangler.Cleaner, numberOfScanners int) ([]int64, string, error) { + tm := tmclient.NewTabletManagerClient() + defer tm.Close() + + // Lock all tables with a read lock to pause replication + err := tm.LockTables(ctx, tablet.Tablet) + if err != nil { + return nil, "", fmt.Errorf("could not lock tables on %v\n%v", topoproto.TabletAliasString(tablet.Tablet.Alias), err) + } + defer func() { + tm := tmclient.NewTabletManagerClient() + defer tm.Close() + tm.UnlockTables(ctx, tablet.Tablet) + wr.Logger().Infof("tables unlocked on %v", topoproto.TabletAliasString(tablet.Tablet.Alias)) + }() + + wr.Logger().Infof("tables locked on %v", topoproto.TabletAliasString(tablet.Tablet.Alias)) + target := CreateTargetFrom(tablet.Tablet) + + // Create transactions + queryService, err := tabletconn.GetDialer()(tablet.Tablet, true) + defer queryService.Close(ctx) + connections, err := createTransactions(ctx, numberOfScanners, wr, cleaner, queryService, target, tablet.Tablet) + if err != nil { + return nil, "", fmt.Errorf("failed to create transactions on %v: %v", topoproto.TabletAliasString(tablet.Tablet.Alias), err) + } + wr.Logger().Infof("transactions created on %v", topoproto.TabletAliasString(tablet.Tablet.Alias)) + executedGtid, err := tm.MasterPosition(ctx, tablet.Tablet) + if err != nil { + return nil, "", fmt.Errorf("could not read executed GTID set on %v\n%v", topoproto.TabletAliasString(tablet.Tablet.Alias), err) + } + + return connections, executedGtid, nil +} diff --git a/go/vt/worker/legacy_split_clone_cmd.go b/go/vt/worker/legacy_split_clone_cmd.go index ff48c291d4b..ab73cca3903 100644 --- a/go/vt/worker/legacy_split_clone_cmd.go +++ b/go/vt/worker/legacy_split_clone_cmd.go @@ -92,7 +92,7 @@ func commandLegacySplitClone(wi *Instance, wr *wrangler.Wrangler, subFlags *flag sourceReaderCount := subFlags.Int("source_reader_count", defaultSourceReaderCount, "number of concurrent streaming queries to use on the source") destinationPackCount := subFlags.Int("destination_pack_count", defaultDestinationPackCount, "number of packets to pack in one destination insert") destinationWriterCount := subFlags.Int("destination_writer_count", defaultDestinationWriterCount, "number of concurrent RPCs to execute on the destination") - minHealthyRdonlyTablets := subFlags.Int("min_healthy_rdonly_tablets", defaultMinHealthyRdonlyTablets, "minimum number of healthy RDONLY tablets before taking out one") + minHealthyRdonlyTablets := subFlags.Int("min_healthy_rdonly_tablets", defaultMinHealthyTablets, "minimum number of healthy RDONLY tablets before taking out one") maxTPS := subFlags.Int64("max_tps", defaultMaxTPS, "if non-zero, limit copy to maximum number of (write) transactions/second on the destination (unlimited by default)") if err := subFlags.Parse(args); err != nil { return nil, err @@ -146,7 +146,7 @@ func interactiveLegacySplitClone(ctx context.Context, wi *Instance, wr *wrangler result["DefaultSourceReaderCount"] = fmt.Sprintf("%v", defaultSourceReaderCount) result["DefaultDestinationPackCount"] = fmt.Sprintf("%v", defaultDestinationPackCount) result["DefaultDestinationWriterCount"] = fmt.Sprintf("%v", defaultDestinationWriterCount) - result["DefaultMinHealthyRdonlyTablets"] = fmt.Sprintf("%v", defaultMinHealthyRdonlyTablets) + result["DefaultMinHealthyRdonlyTablets"] = fmt.Sprintf("%v", defaultMinHealthyTablets) result["DefaultMaxTPS"] = fmt.Sprintf("%v", defaultMaxTPS) return nil, legacySplitCloneTemplate2, result, nil } diff --git a/go/vt/worker/multi_split_diff.go b/go/vt/worker/multi_split_diff.go index 71ee4c22c28..002eb70e0f5 100644 --- a/go/vt/worker/multi_split_diff.go +++ b/go/vt/worker/multi_split_diff.go @@ -24,16 +24,20 @@ import ( "time" "golang.org/x/net/context" - "vitess.io/vitess/go/sqltypes" - "vitess.io/vitess/go/vt/binlog/binlogplayer" + "vitess.io/vitess/go/vt/vttablet/queryservice" + "vitess.io/vitess/go/vt/vttablet/tabletconn" + "vitess.io/vitess/go/vt/concurrency" "vitess.io/vitess/go/vt/mysqlctl/tmutils" + "vitess.io/vitess/go/vt/topo" + "vitess.io/vitess/go/vt/wrangler" + + "vitess.io/vitess/go/sqltypes" + "vitess.io/vitess/go/vt/binlog/binlogplayer" tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata" topodatapb "vitess.io/vitess/go/vt/proto/topodata" - "vitess.io/vitess/go/vt/topo" "vitess.io/vitess/go/vt/vterrors" "vitess.io/vitess/go/vt/vtgate/vindexes" - "vitess.io/vitess/go/vt/wrangler" ) // MultiSplitDiffWorker executes a diff between a destination shard and its @@ -46,10 +50,12 @@ type MultiSplitDiffWorker struct { keyspace string shard string excludeTables []string - minHealthyRdonlyTablets int + minHealthyTablets int parallelDiffsCount int waitForFixedTimeRatherThanGtidSet bool cleaner *wrangler.Cleaner + useConsistentSnapshot bool + tabletType topodatapb.TabletType // populated during WorkerStateInit, read-only after that keyspaceInfo *topo.KeyspaceInfo @@ -58,23 +64,27 @@ type MultiSplitDiffWorker struct { destinationShards []*topo.ShardInfo // populated during WorkerStateFindTargets, read-only after that - sourceAlias *topodatapb.TabletAlias - destinationAliases []*topodatapb.TabletAlias // matches order of destinationShards + sourceAlias *topodatapb.TabletAlias + destinationAliases []*topodatapb.TabletAlias // matches order of destinationShards + sourceScanners []TableScanner + destinationScanners [][]TableScanner } // NewMultiSplitDiffWorker returns a new MultiSplitDiffWorker object. -func NewMultiSplitDiffWorker(wr *wrangler.Wrangler, cell, keyspace, shard string, excludeTables []string, minHealthyRdonlyTablets, parallelDiffsCount int, waitForFixedTimeRatherThanGtidSet bool) Worker { +func NewMultiSplitDiffWorker(wr *wrangler.Wrangler, cell, keyspace, shard string, excludeTables []string, minHealthyTablets, parallelDiffsCount int, waitForFixedTimeRatherThanGtidSet bool, useConsistentSnapshot bool, tabletType topodatapb.TabletType) Worker { return &MultiSplitDiffWorker{ - waitForFixedTimeRatherThanGtidSet: waitForFixedTimeRatherThanGtidSet, StatusWorker: NewStatusWorker(), wr: wr, cell: cell, keyspace: keyspace, shard: shard, excludeTables: excludeTables, - minHealthyRdonlyTablets: minHealthyRdonlyTablets, + minHealthyTablets: minHealthyTablets, parallelDiffsCount: parallelDiffsCount, cleaner: &wrangler.Cleaner{}, + useConsistentSnapshot: useConsistentSnapshot, + waitForFixedTimeRatherThanGtidSet: waitForFixedTimeRatherThanGtidSet, + tabletType: tabletType, } } @@ -154,8 +164,8 @@ func (msdw *MultiSplitDiffWorker) run(ctx context.Context) error { } // third phase: synchronize replication - if err := msdw.synchronizeReplication(ctx); err != nil { - return fmt.Errorf("synchronizeReplication() failed: %v", err) + if err := msdw.synchronizeSrcAndDestTxState(ctx); err != nil { + return fmt.Errorf("synchronizeSrcAndDestTxState() failed: %v", err) } if err := checkDone(ctx); err != nil { return err @@ -174,6 +184,12 @@ func (msdw *MultiSplitDiffWorker) run(ctx context.Context) error { func (msdw *MultiSplitDiffWorker) init(ctx context.Context) error { msdw.SetState(WorkerStateInit) + if msdw.useConsistentSnapshot { + msdw.wr.Logger().Infof("splitting using consistent snapshot") + } else { + msdw.wr.Logger().Infof("splitting using STOP SLAVE") + } + var err error shortCtx, cancel := context.WithTimeout(ctx, *remoteActionsTimeout) msdw.keyspaceInfo, err = msdw.wr.TopoServer().GetKeyspace(shortCtx, msdw.keyspace) @@ -282,47 +298,34 @@ func (msdw *MultiSplitDiffWorker) getShardInfo(ctx context.Context, keyspace str // - mark them all as 'worker' pointing back to us func (msdw *MultiSplitDiffWorker) findTargets(ctx context.Context) error { msdw.SetState(WorkerStateFindTargets) - var err error - // find an appropriate tablet in the source shard - msdw.sourceAlias, err = FindWorkerTablet( - ctx, - msdw.wr, - msdw.cleaner, - nil, /* tsc */ - msdw.cell, - msdw.keyspace, - msdw.shard, - 1, /* minHealthyTablets */ - topodatapb.TabletType_RDONLY) + var finderFunc func(keyspace string, shard string) (*topodatapb.TabletAlias, error) + if msdw.tabletType == topodatapb.TabletType_RDONLY { + finderFunc = func(keyspace string, shard string) (*topodatapb.TabletAlias, error) { + return FindWorkerTablet(ctx, msdw.wr, msdw.cleaner, nil /*tsc*/, msdw.cell, keyspace, shard, 1, topodatapb.TabletType_RDONLY) + } + } else { + finderFunc = func(keyspace string, shard string) (*topodatapb.TabletAlias, error) { + return FindHealthyTablet(ctx, msdw.wr, nil /*tsc*/, msdw.cell, keyspace, shard, 1, msdw.tabletType) + } + } + + msdw.sourceAlias, err = finderFunc(msdw.keyspace, msdw.shard) if err != nil { - return fmt.Errorf("FindWorkerTablet() failed for %v/%v/%v: %v", msdw.cell, msdw.keyspace, msdw.shard, err) + return fmt.Errorf("finding source failed for %v/%v/%v: %v", msdw.cell, msdw.keyspace, msdw.shard, err) } - // find an appropriate tablet in each destination shard msdw.destinationAliases = make([]*topodatapb.TabletAlias, len(msdw.destinationShards)) for i, destinationShard := range msdw.destinationShards { keyspace := destinationShard.Keyspace() shard := destinationShard.ShardName() - destinationAlias, err := FindWorkerTablet( - ctx, - msdw.wr, - msdw.cleaner, - nil, /* tsc */ - msdw.cell, - keyspace, - shard, - msdw.minHealthyRdonlyTablets, - topodatapb.TabletType_RDONLY) + destinationAlias, err := finderFunc(keyspace, shard) if err != nil { - return fmt.Errorf("FindWorkerTablet() failed for %v/%v/%v: %v", msdw.cell, keyspace, shard, err) + return fmt.Errorf("finding destination failed for %v/%v/%v: %v", msdw.cell, keyspace, shard, err) } msdw.destinationAliases[i] = destinationAlias } - if err != nil { - return fmt.Errorf("FindWorkerTablet() failed for %v/%v/%v: %v", msdw.cell, msdw.keyspace, msdw.shard, err) - } return nil } @@ -330,22 +333,22 @@ func (msdw *MultiSplitDiffWorker) findTargets(ctx context.Context) error { // ask the master of the destination shard to pause filtered replication, // and return the source binlog positions // (add a cleanup task to restart filtered replication on master) -func (msdw *MultiSplitDiffWorker) stopReplicationOnAllDestinationMasters(ctx context.Context, masterInfos []*topo.TabletInfo) ([]string, error) { +func (msdw *MultiSplitDiffWorker) stopVreplicationOnAll(ctx context.Context, tabletInfo []*topo.TabletInfo) ([]string, error) { destVreplicationPos := make([]string, len(msdw.destinationShards)) for i, shardInfo := range msdw.destinationShards { - masterInfo := masterInfos[i] + tablet := tabletInfo[i].Tablet - msdw.wr.Logger().Infof("Stopping master binlog replication on %v", shardInfo.MasterAlias) + msdw.wr.Logger().Infof("stopping master binlog replication on %v", shardInfo.MasterAlias) shortCtx, cancel := context.WithTimeout(ctx, *remoteActionsTimeout) - _, err := msdw.wr.TabletManagerClient().VReplicationExec(shortCtx, masterInfo.Tablet, binlogplayer.StopVReplication(msdw.sourceUID, "for split diff")) + _, err := msdw.wr.TabletManagerClient().VReplicationExec(shortCtx, tablet, binlogplayer.StopVReplication(msdw.sourceUID, "for split diff")) cancel() if err != nil { return nil, fmt.Errorf("VReplicationExec(stop) for %v failed: %v", shardInfo.MasterAlias, err) } - wrangler.RecordVReplicationAction(msdw.cleaner, masterInfo.Tablet, binlogplayer.StartVReplication(msdw.sourceUID)) + wrangler.RecordVReplicationAction(msdw.cleaner, tablet, binlogplayer.StartVReplication(msdw.sourceUID)) shortCtx, cancel = context.WithTimeout(ctx, *remoteActionsTimeout) - p3qr, err := msdw.wr.TabletManagerClient().VReplicationExec(shortCtx, masterInfo.Tablet, binlogplayer.ReadVReplicationPos(msdw.sourceUID)) + p3qr, err := msdw.wr.TabletManagerClient().VReplicationExec(shortCtx, tablet, binlogplayer.ReadVReplicationPos(msdw.sourceUID)) cancel() if err != nil { return nil, fmt.Errorf("VReplicationExec(stop) for %v failed: %v", msdw.shardInfo.MasterAlias, err) @@ -362,12 +365,12 @@ func (msdw *MultiSplitDiffWorker) stopReplicationOnAllDestinationMasters(ctx con return destVreplicationPos, nil } -func (msdw *MultiSplitDiffWorker) getTabletInfoForShard(ctx context.Context, shardInfo *topo.ShardInfo) (*topo.TabletInfo, error) { +func (msdw *MultiSplitDiffWorker) getMasterTabletInfoForShard(ctx context.Context, shardInfo *topo.ShardInfo) (*topo.TabletInfo, error) { shortCtx, cancel := context.WithTimeout(ctx, *remoteActionsTimeout) masterInfo, err := msdw.wr.TopoServer().GetTablet(shortCtx, shardInfo.MasterAlias) cancel() if err != nil { - return nil, fmt.Errorf("synchronizeReplication: cannot get Tablet record for master %v: %v", msdw.shardInfo.MasterAlias, err) + return nil, fmt.Errorf("synchronizeSrcAndDestTxState: cannot get Tablet record for master %v: %v", msdw.shardInfo.MasterAlias, err) } return masterInfo, nil } @@ -376,7 +379,7 @@ func (msdw *MultiSplitDiffWorker) getTabletInfoForShard(ctx context.Context, sha // destination masters. Return the reached position // (add a cleanup task to restart binlog replication on the source tablet, and // change the existing ChangeSlaveType cleanup action to 'spare' type) -func (msdw *MultiSplitDiffWorker) stopReplicationOnSourceRdOnlyTabletAt(ctx context.Context, destVreplicationPos []string) (string, error) { +func (msdw *MultiSplitDiffWorker) stopReplicationOnSourceTabletAt(ctx context.Context, destVreplicationPos []string) (string, error) { shortCtx, cancel := context.WithTimeout(ctx, *remoteActionsTimeout) sourceTablet, err := msdw.wr.TopoServer().GetTablet(shortCtx, msdw.sourceAlias) cancel() @@ -392,12 +395,8 @@ func (msdw *MultiSplitDiffWorker) stopReplicationOnSourceRdOnlyTabletAt(ctx cont // if we make StopSlaveMinimum take multiple blp positions then this will be a lot more efficient because you just // check for each position using WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS and then stop replication. - msdw.wr.Logger().Infof("Stopping slave %v at a minimum of %v", msdw.sourceAlias, vreplicationPos) - // read the tablet - sourceTablet, err := msdw.wr.TopoServer().GetTablet(shortCtx, msdw.sourceAlias) - if err != nil { - return "", err - } + msdw.wr.Logger().Infof("stopping slave %v at a minimum of %v", msdw.sourceAlias, vreplicationPos) + shortCtx, cancel = context.WithTimeout(ctx, *remoteActionsTimeout) msdw.wr.TabletManagerClient().StartSlave(shortCtx, sourceTablet.Tablet) cancel() @@ -420,21 +419,22 @@ func (msdw *MultiSplitDiffWorker) stopReplicationOnSourceRdOnlyTabletAt(ctx cont } // ask the master of the destination shard to resume filtered replication -// up to the new list of positions, and return its binlog position. -func (msdw *MultiSplitDiffWorker) resumeReplicationOnDestinationMasterUntil(ctx context.Context, shardInfo *topo.ShardInfo, mysqlPos string, masterInfo *topo.TabletInfo) (string, error) { - msdw.wr.Logger().Infof("Restarting master %v until it catches up to %v", shardInfo.MasterAlias, mysqlPos) +// up to the specified source position, and return the destination position. +func (msdw *MultiSplitDiffWorker) stopVreplicationAt(ctx context.Context, shardInfo *topo.ShardInfo, sourcePosition string, masterInfo *topo.TabletInfo) (string, error) { + msdw.wr.Logger().Infof("Restarting master %v until it catches up to %v", shardInfo.MasterAlias, sourcePosition) shortCtx, cancel := context.WithTimeout(ctx, *remoteActionsTimeout) - _, err := msdw.wr.TabletManagerClient().VReplicationExec(shortCtx, masterInfo.Tablet, binlogplayer.StartVReplicationUntil(msdw.sourceUID, mysqlPos)) + _, err := msdw.wr.TabletManagerClient().VReplicationExec(shortCtx, masterInfo.Tablet, binlogplayer.StartVReplicationUntil(msdw.sourceUID, sourcePosition)) cancel() if err != nil { - return "", fmt.Errorf("VReplication(start until) for %v until %v failed: %v", shardInfo.MasterAlias, mysqlPos, err) + return "", fmt.Errorf("VReplication(start until) for %v until %v failed: %v", shardInfo.MasterAlias, sourcePosition, err) } + shortCtx, cancel = context.WithTimeout(ctx, *remoteActionsTimeout) - if err := msdw.wr.TabletManagerClient().VReplicationWaitForPos(shortCtx, masterInfo.Tablet, int(msdw.sourceUID), mysqlPos); err != nil { - cancel() - return "", fmt.Errorf("VReplicationWaitForPos for %v until %v failed: %v", shardInfo.MasterAlias, mysqlPos, err) - } + err = msdw.wr.TabletManagerClient().VReplicationWaitForPos(shortCtx, masterInfo.Tablet, int(msdw.sourceUID), sourcePosition) cancel() + if err != nil { + return "", fmt.Errorf("VReplicationWaitForPos for %v until %v failed: %v", shardInfo.MasterAlias, sourcePosition, err) + } shortCtx, cancel = context.WithTimeout(ctx, *remoteActionsTimeout) masterPos, err := msdw.wr.TabletManagerClient().MasterPosition(shortCtx, masterInfo.Tablet) @@ -449,11 +449,11 @@ func (msdw *MultiSplitDiffWorker) resumeReplicationOnDestinationMasterUntil(ctx // binlog position, and stop its replication. // (add a cleanup task to restart binlog replication on it, and change // the existing ChangeSlaveType cleanup action to 'spare' type) -func (msdw *MultiSplitDiffWorker) stopReplicationOnDestinationRdOnlys(ctx context.Context, destinationAlias *topodatapb.TabletAlias, masterPos string) error { +func (msdw *MultiSplitDiffWorker) stopReplicationAt(ctx context.Context, destinationAlias *topodatapb.TabletAlias, masterPos string) error { if msdw.waitForFixedTimeRatherThanGtidSet { - msdw.wr.Logger().Infof("Workaround for broken GTID set in destination RDONLY. Just waiting for 1 minute for %v and assuming replication has caught up. (should be at %v)", destinationAlias, masterPos) + msdw.wr.Logger().Infof("workaround for broken GTID set in destination RDONLY. Just waiting for 1 minute for %v and assuming replication has caught up. (should be at %v)", destinationAlias, masterPos) } else { - msdw.wr.Logger().Infof("Waiting for destination tablet %v to catch up to %v", destinationAlias, masterPos) + msdw.wr.Logger().Infof("waiting for destination tablet %v to catch up to %v", destinationAlias, masterPos) } shortCtx, cancel := context.WithTimeout(ctx, *remoteActionsTimeout) destinationTablet, err := msdw.wr.TopoServer().GetTablet(shortCtx, destinationAlias) @@ -482,8 +482,8 @@ func (msdw *MultiSplitDiffWorker) stopReplicationOnDestinationRdOnlys(ctx contex // restart filtered replication on the destination master. // (remove the cleanup task that does the same) -func (msdw *MultiSplitDiffWorker) restartReplicationOn(ctx context.Context, shardInfo *topo.ShardInfo, masterInfo *topo.TabletInfo) error { - msdw.wr.Logger().Infof("Restarting filtered replication on master %v", shardInfo.MasterAlias) +func (msdw *MultiSplitDiffWorker) startVreplication(ctx context.Context, shardInfo *topo.ShardInfo, masterInfo *topo.TabletInfo) error { + msdw.wr.Logger().Infof("restarting filtered replication on master %v", shardInfo.MasterAlias) shortCtx, cancel := context.WithTimeout(ctx, *remoteActionsTimeout) _, err := msdw.wr.TabletManagerClient().VReplicationExec(shortCtx, masterInfo.Tablet, binlogplayer.StartVReplication(msdw.sourceUID)) if err != nil { @@ -493,67 +493,164 @@ func (msdw *MultiSplitDiffWorker) restartReplicationOn(ctx context.Context, shar return nil } -// synchronizeReplication phase: -// At this point, the source and the destination tablet are stopped at the same -// point. +func (msdw *MultiSplitDiffWorker) createNonTransactionalTableScanners(ctx context.Context, queryService queryservice.QueryService, source *topo.TabletInfo) ([]TableScanner, error) { + // If we are not using consistent snapshot, we'll use the NonTransactionalTableScanner, + // which does not have any instance state and so can be used by all connections + scanners := make([]TableScanner, msdw.parallelDiffsCount) + scanner := NonTransactionalTableScanner{ + queryService: queryService, + cleaner: msdw.cleaner, + wr: msdw.wr, + tabletAlias: source.Alias, + } -func (msdw *MultiSplitDiffWorker) synchronizeReplication(ctx context.Context) error { + for i := 0; i < msdw.parallelDiffsCount; i++ { + scanners[i] = scanner + } + + return scanners, nil +} + +// synchronizeSrcAndDestTxState phase: +// After this point, the source and the destination tablet are stopped at the same point. +func (msdw *MultiSplitDiffWorker) synchronizeSrcAndDestTxState(ctx context.Context) error { msdw.SetState(WorkerStateSyncReplication) var err error + // 1. Find all the tablets we will need to work with masterInfos := make([]*topo.TabletInfo, len(msdw.destinationAliases)) for i, shardInfo := range msdw.destinationShards { - masterInfos[i], err = msdw.getTabletInfoForShard(ctx, shardInfo) + masterInfos[i], err = msdw.getMasterTabletInfoForShard(ctx, shardInfo) if err != nil { return err } } - destVreplicationPos, err := msdw.stopReplicationOnAllDestinationMasters(ctx, masterInfos) + shortCtx, cancel := context.WithTimeout(ctx, *remoteActionsTimeout) + source, err := msdw.wr.TopoServer().GetTablet(shortCtx, msdw.sourceAlias) + cancel() + + var sourcePosition string + + // 2. Stop replication on destination + destVreplicationPos, err := msdw.stopVreplicationOnAll(ctx, masterInfos) if err != nil { return err } - mysqlPos, err := msdw.stopReplicationOnSourceRdOnlyTabletAt(ctx, destVreplicationPos) - if err != nil { - return err + // 3. Pause updates on the source and create consistent snapshot connections + if msdw.useConsistentSnapshot { + connections, pos, err := CreateConsistentTableScanners(ctx, source, msdw.wr, msdw.cleaner, msdw.parallelDiffsCount) + if err != nil { + return fmt.Errorf("failed to create transactional connections %v", err.Error()) + } + msdw.sourceScanners = connections + sourcePosition = pos + } else { + sourcePosition, err = msdw.stopReplicationOnSourceTabletAt(ctx, destVreplicationPos) + if err != nil { + return fmt.Errorf("failed to stop replication on source %v", err.Error()) + } + + queryService, err := tabletconn.GetDialer()(source.Tablet, true) + if err != nil { + return fmt.Errorf("failed to instantiate query service for %v: %v", source.Tablet, err.Error()) + } + msdw.sourceScanners, err = msdw.createNonTransactionalTableScanners(ctx, queryService, source) + if err != nil { + return fmt.Errorf("failed to create table scanners %v", err.Error()) + } } + msdw.destinationScanners = make([][]TableScanner, msdw.parallelDiffsCount) + + // 4. Make sure all replicas have caught up with the master for i, shardInfo := range msdw.destinationShards { masterInfo := masterInfos[i] destinationAlias := msdw.destinationAliases[i] - masterPos, err := msdw.resumeReplicationOnDestinationMasterUntil(ctx, shardInfo, mysqlPos, masterInfo) + destinationPosition, err := msdw.stopVreplicationAt(ctx, shardInfo, sourcePosition, masterInfo) if err != nil { return err } - err = msdw.stopReplicationOnDestinationRdOnlys(ctx, destinationAlias, masterPos) + shortCtx, cancel := context.WithTimeout(ctx, *remoteActionsTimeout) + destTabletInfo, err := msdw.wr.TopoServer().GetTablet(shortCtx, destinationAlias) + cancel() if err != nil { - return err + return fmt.Errorf("waitForDestinationTabletToReach: cannot get Tablet record for master %v: %v", msdw.shardInfo.MasterAlias, err) + } + + queryService, err := tabletconn.GetDialer()(source.Tablet, true) + + if msdw.useConsistentSnapshot { + // loop to wait for the destinationAlias tablet in shardInfo to have reached destinationPosition + err = msdw.waitForDestinationTabletToReach(ctx, destTabletInfo.Tablet, destinationPosition) + if err != nil { + return err + } + + scanners, _, err := CreateConsistentTableScanners(ctx, destTabletInfo, msdw.wr, msdw.cleaner, msdw.parallelDiffsCount) + if err != nil { + return fmt.Errorf("failed to create transactional destination connections") + } + for j, scanner := range scanners { + msdw.destinationScanners[j] = append(msdw.destinationScanners[j], scanner) + } + } else { + err = msdw.stopReplicationAt(ctx, destinationAlias, destinationPosition) + if err != nil { + return fmt.Errorf("failed to stop replication on %v at position %v: %v", destinationAlias, destinationPosition, err.Error()) + } + msdw.destinationScanners[i], err = msdw.createNonTransactionalTableScanners(ctx, queryService, destTabletInfo) + if err != nil { + return fmt.Errorf("failed to stop create table scanners for %v using %v : %v", destTabletInfo, queryService, err.Error()) + } } - err = msdw.restartReplicationOn(ctx, shardInfo, masterInfo) + err = msdw.startVreplication(ctx, shardInfo, masterInfo) if err != nil { - return err + return fmt.Errorf("failed to restart vreplication for shard %v on tablet %v: %v", shardInfo, masterInfo, err.Error()) } - } + } return nil } -func (msdw *MultiSplitDiffWorker) diffSingleTable(ctx context.Context, wg *sync.WaitGroup, tableDefinition *tabletmanagerdatapb.TableDefinition, keyspaceSchema *vindexes.KeyspaceSchema) error { +func (msdw *MultiSplitDiffWorker) waitForDestinationTabletToReach(ctx context.Context, tablet *topodatapb.Tablet, mysqlPos string) error { + for i := 0; i < 20; i++ { + shortCtx, cancel := context.WithTimeout(ctx, *remoteActionsTimeout) + pos, err := msdw.wr.TabletManagerClient().MasterPosition(shortCtx, tablet) + cancel() + if err != nil { + return fmt.Errorf("get MasterPosition for %v failed: %v", tablet, err) + } + + if pos == mysqlPos { + return nil + } + time.Sleep(time.Second) + } + return fmt.Errorf("failed to reach transaction position after multiple attempts") +} + +func (msdw *MultiSplitDiffWorker) diffSingleTable(ctx context.Context, wg *sync.WaitGroup, tableDefinition *tabletmanagerdatapb.TableDefinition, keyspaceSchema *vindexes.KeyspaceSchema, sourceScanner TableScanner, destinationScanners []TableScanner) error { msdw.wr.Logger().Infof("Starting the diff on table %v", tableDefinition.Name) - sourceQueryResultReader, err := TableScan(ctx, msdw.wr.Logger(), msdw.wr.TopoServer(), msdw.sourceAlias, tableDefinition) + if len(destinationScanners) != len(msdw.destinationAliases) { + return fmt.Errorf("did not receive the expected amount of destination connections") + } + + sourceQueryResultReader, err := sourceScanner.ScanTable(ctx, tableDefinition) if err != nil { return fmt.Errorf("TableScan(source) failed: %v", err) } defer sourceQueryResultReader.Close(ctx) destinationQueryResultReaders := make([]ResultReader, len(msdw.destinationAliases)) - for i, destinationAlias := range msdw.destinationAliases { - destinationQueryResultReader, err := TableScan(ctx, msdw.wr.Logger(), msdw.wr.TopoServer(), destinationAlias, tableDefinition) + for i := range msdw.destinationAliases { + scanner := destinationScanners[i] + destinationQueryResultReader, err := scanner.ScanTable(ctx, tableDefinition) if err != nil { return fmt.Errorf("TableScan(destination) failed: %v", err) } @@ -593,16 +690,16 @@ func (msdw *MultiSplitDiffWorker) diffSingleTable(ctx context.Context, wg *sync. return fmt.Errorf("table %v has differences: %v", tableDefinition.Name, report.String()) } - msdw.wr.Logger().Infof("Table %v checks out (%v rows processed, %v qps)", tableDefinition.Name, report.processedRows, report.processingQPS) + msdw.wr.Logger().Infof("table %v checks out (%v rows processed, %v qps)", tableDefinition.Name, report.processedRows, report.processingQPS) return nil } -func (msdw *MultiSplitDiffWorker) tableDiffingConsumer(ctx context.Context, wg *sync.WaitGroup, tableChan chan *tabletmanagerdatapb.TableDefinition, rec *concurrency.AllErrorRecorder, keyspaceSchema *vindexes.KeyspaceSchema) { +func (msdw *MultiSplitDiffWorker) tableDiffingConsumer(ctx context.Context, wg *sync.WaitGroup, tableChan chan *tabletmanagerdatapb.TableDefinition, rec *concurrency.AllErrorRecorder, keyspaceSchema *vindexes.KeyspaceSchema, sourceScanner TableScanner, destinationScanners []TableScanner) { defer wg.Done() for tableDefinition := range tableChan { - err := msdw.diffSingleTable(ctx, wg, tableDefinition, keyspaceSchema) + err := msdw.diffSingleTable(ctx, wg, tableDefinition, keyspaceSchema, sourceScanner, destinationScanners) if err != nil { msdw.markAsWillFail(rec, err) msdw.wr.Logger().Errorf("%v", err) @@ -611,7 +708,7 @@ func (msdw *MultiSplitDiffWorker) tableDiffingConsumer(ctx context.Context, wg * } func (msdw *MultiSplitDiffWorker) gatherSchemaInfo(ctx context.Context) ([]*tabletmanagerdatapb.SchemaDefinition, *tabletmanagerdatapb.SchemaDefinition, error) { - msdw.wr.Logger().Infof("Gathering schema information...") + msdw.wr.Logger().Infof("gathering schema information...") wg := sync.WaitGroup{} rec := &concurrency.AllErrorRecorder{} @@ -630,7 +727,7 @@ func (msdw *MultiSplitDiffWorker) gatherSchemaInfo(ctx context.Context) ([]*tabl msdw.markAsWillFail(rec, err) } destinationSchemaDefinitions[i] = destinationSchemaDefinition - msdw.wr.Logger().Infof("Got schema from destination %v", destinationAlias) + msdw.wr.Logger().Infof("got schema from destination %v", destinationAlias) wg.Done() }(i, destinationAlias) } @@ -644,7 +741,7 @@ func (msdw *MultiSplitDiffWorker) gatherSchemaInfo(ctx context.Context) ([]*tabl if err != nil { msdw.markAsWillFail(rec, err) } - msdw.wr.Logger().Infof("Got schema from source %v", msdw.sourceAlias) + msdw.wr.Logger().Infof("got schema from source %v", msdw.sourceAlias) wg.Done() }() @@ -656,8 +753,8 @@ func (msdw *MultiSplitDiffWorker) gatherSchemaInfo(ctx context.Context) ([]*tabl return destinationSchemaDefinitions, sourceSchemaDefinition, nil } -func (msdw *MultiSplitDiffWorker) diffSchemaInformation(ctx context.Context, destinationSchemaDefinitions []*tabletmanagerdatapb.SchemaDefinition, sourceSchemaDefinition *tabletmanagerdatapb.SchemaDefinition) { - msdw.wr.Logger().Infof("Diffing the schema...") +func (msdw *MultiSplitDiffWorker) diffSchemaInformation(ctx context.Context, destinationSchemaDefinitions []*tabletmanagerdatapb.SchemaDefinition, sourceSchemaDefinition *tabletmanagerdatapb.SchemaDefinition) error { + msdw.wr.Logger().Infof("diffing the schema...") rec := &concurrency.AllErrorRecorder{} sourceShardName := fmt.Sprintf("%v/%v", msdw.shardInfo.Keyspace(), msdw.shardInfo.ShardName()) for i, destinationSchemaDefinition := range destinationSchemaDefinitions { @@ -666,10 +763,12 @@ func (msdw *MultiSplitDiffWorker) diffSchemaInformation(ctx context.Context, des tmutils.DiffSchema(destinationShardName, destinationSchemaDefinition, sourceShardName, sourceSchemaDefinition, rec) } if rec.HasErrors() { - msdw.wr.Logger().Warningf("Different schemas: %v", rec.Error().Error()) - } else { - msdw.wr.Logger().Infof("Schema match, good.") + msdw.wr.Logger().Warningf("different schemas: %v", rec.Error().Error()) + return rec.Error() } + + msdw.wr.Logger().Infof("schema match, good.") + return nil } func (msdw *MultiSplitDiffWorker) loadVSchema(ctx context.Context) (*vindexes.KeyspaceSchema, error) { @@ -702,7 +801,10 @@ func (msdw *MultiSplitDiffWorker) diff(ctx context.Context) error { if err != nil { return err } - msdw.diffSchemaInformation(ctx, destinationSchemaDefinitions, sourceSchemaDefinition) + err = msdw.diffSchemaInformation(ctx, destinationSchemaDefinitions, sourceSchemaDefinition) + if err != nil { + return fmt.Errorf("schema comparison failed: %v", err) + } // read the vschema if needed var keyspaceSchema *vindexes.KeyspaceSchema @@ -713,7 +815,7 @@ func (msdw *MultiSplitDiffWorker) diff(ctx context.Context) error { } } - msdw.wr.Logger().Infof("Running the diffs...") + msdw.wr.Logger().Infof("running the diffs...") tableDefinitions := sourceSchemaDefinition.TableDefinitions rec := &concurrency.AllErrorRecorder{} @@ -730,7 +832,7 @@ func (msdw *MultiSplitDiffWorker) diff(ctx context.Context) error { // start as many goroutines we want parallel diffs running for i := 0; i < msdw.parallelDiffsCount; i++ { consumers.Add(1) - go msdw.tableDiffingConsumer(ctx, &consumers, tableChan, rec, keyspaceSchema) + go msdw.tableDiffingConsumer(ctx, &consumers, tableChan, rec, keyspaceSchema, msdw.sourceScanners[i], msdw.destinationScanners[i]) } // wait for all consumers to wrap up their work diff --git a/go/vt/worker/multi_split_diff_cmd.go b/go/vt/worker/multi_split_diff_cmd.go index 353e47790e7..44562b29658 100644 --- a/go/vt/worker/multi_split_diff_cmd.go +++ b/go/vt/worker/multi_split_diff_cmd.go @@ -25,6 +25,8 @@ import ( "strings" "sync" + "vitess.io/vitess/go/vt/proto/topodata" + "golang.org/x/net/context" "vitess.io/vitess/go/vt/concurrency" "vitess.io/vitess/go/vt/topo/topoproto" @@ -60,14 +62,22 @@ const multiSplitDiffHTML2 = `