vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletmanager/vreplication/vreplicator.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package vreplication
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"math"
    23  	"sort"
    24  	"strconv"
    25  	"strings"
    26  	"time"
    27  
    28  	"vitess.io/vitess/go/timer"
    29  	"vitess.io/vitess/go/vt/schema"
    30  	"vitess.io/vitess/go/vt/sqlparser"
    31  	"vitess.io/vitess/go/vt/vterrors"
    32  
    33  	querypb "vitess.io/vitess/go/vt/proto/query"
    34  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    35  
    36  	"vitess.io/vitess/go/vt/vtgate/evalengine"
    37  
    38  	"context"
    39  
    40  	"vitess.io/vitess/go/mysql"
    41  	"vitess.io/vitess/go/sqltypes"
    42  	"vitess.io/vitess/go/vt/binlog/binlogplayer"
    43  	"vitess.io/vitess/go/vt/log"
    44  	"vitess.io/vitess/go/vt/mysqlctl"
    45  
    46  	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
    47  	tabletmanagerdatapb "vitess.io/vitess/go/vt/proto/tabletmanagerdata"
    48  )
    49  
    50  var (
    51  	// idleTimeout is set to slightly above 1s, compared to heartbeatTime
    52  	// set by VStreamer at slightly below 1s. This minimizes conflicts
    53  	// between the two timeouts.
    54  	idleTimeout = 1100 * time.Millisecond
    55  
    56  	dbLockRetryDelay = 1 * time.Second
    57  
    58  	// vreplicationMinimumHeartbeatUpdateInterval overrides vreplicationHeartbeatUpdateInterval if the latter is higher than this
    59  	// to ensure that it satisfies liveness criteria implicitly expected by internal processes like Online DDL
    60  	vreplicationMinimumHeartbeatUpdateInterval = 60
    61  
    62  	vreplicationExperimentalFlagOptimizeInserts int64 = 1
    63  )
    64  
    65  const (
    66  	getSQLModeQuery = `SELECT @@session.sql_mode AS sql_mode`
    67  	// SQLMode should be used whenever performing a schema change as part of a vreplication
    68  	// workflow to ensure that you set a permissive SQL mode as defined by
    69  	// VReplication. We follow MySQL's model for recreating database objects
    70  	// on a target -- using SQL statements generated from a source -- which
    71  	// ensures that we can recreate them regardless of the sql_mode that was
    72  	// in effect on the source when it was created:
    73  	//   https://github.com/mysql/mysql-server/blob/3290a66c89eb1625a7058e0ef732432b6952b435/client/mysqldump.cc#L795-L818
    74  	SQLMode          = "NO_AUTO_VALUE_ON_ZERO"
    75  	StrictSQLMode    = "STRICT_ALL_TABLES,NO_AUTO_VALUE_ON_ZERO"
    76  	setSQLModeQueryf = `SET @@session.sql_mode='%s'`
    77  
    78  	sqlCreatePostCopyAction = `insert into _vt.post_copy_action(vrepl_id, table_name, action)
    79  	values(%a, %a, convert(%a using utf8mb4))`
    80  	sqlGetPostCopyActions = `select id, action from _vt.post_copy_action where vrepl_id=%a and
    81  	table_name=%a`
    82  	// sqlGetPostCopyActionsForTable gets a write lock on all post_copy_action
    83  	// rows for the table and should only be called from within an explicit
    84  	// multi-statement transaction in order to hold those locks until the
    85  	// related work is finished as this is a concurrency control mechanism.
    86  	sqlGetAndLockPostCopyActionsForTable = `select id, vrepl_id, action from _vt.post_copy_action where id in
    87  	(
    88  		select pca.id from _vt.post_copy_action as pca inner join _vt.vreplication as vr on (pca.vrepl_id = vr.id)
    89  		where pca.table_name=%a
    90  	) for update`
    91  	sqlGetPostCopyActionTaskByType = `select json_unquote(json_extract(action, '$.task')) as task from _vt.post_copy_action where
    92  	json_unquote(json_extract(action, '$.type'))=%a and vrepl_id=%a and table_name=%a`
    93  	sqlDeletePostCopyAction = `delete from _vt.post_copy_action where vrepl_id=%a and
    94  	table_name=%a and id=%a`
    95  )
    96  
    97  type ComponentName string
    98  
    99  const (
   100  	VPlayerComponentName     ComponentName = "vplayer"
   101  	VCopierComponentName     ComponentName = "vcopier"
   102  	VStreamerComponentName   ComponentName = "vstreamer"
   103  	RowStreamerComponentName ComponentName = "rowstreamer"
   104  )
   105  
   106  // vreplicator provides the core logic to start vreplication streams
   107  type vreplicator struct {
   108  	vre      *Engine
   109  	id       uint32
   110  	dbClient *vdbClient
   111  	// source
   112  	source          *binlogdatapb.BinlogSource
   113  	sourceVStreamer VStreamerClient
   114  	state           string
   115  	stats           *binlogplayer.Stats
   116  	// mysqld is used to fetch the local schema.
   117  	mysqld     mysqlctl.MysqlDaemon
   118  	colInfoMap map[string][]*ColumnInfo
   119  
   120  	originalFKCheckSetting int64
   121  	originalSQLMode        string
   122  
   123  	WorkflowType int32
   124  	WorkflowName string
   125  
   126  	throttleUpdatesRateLimiter *timer.RateLimiter
   127  }
   128  
   129  // newVReplicator creates a new vreplicator. The valid fields from the source are:
   130  // Keyspace, Shard, Filter, OnDdl, ExternalMySql and StopAfterCopy.
   131  // The Filter consists of Rules. Each Rule has a Match and an (inner) Filter field.
   132  // The Match can be a table name or, if it begins with a "/", a wildcard.
   133  // The Filter can be empty: get all rows and columns.
   134  // The Filter can be a keyrange, like "-80": get all rows that are within the keyrange.
   135  // The Filter can be a select expression. Examples.
   136  //
   137  //	"select * from t", same as an empty Filter,
   138  //	"select * from t where in_keyrange('-80')", same as "-80",
   139  //	"select * from t where in_keyrange(col1, 'hash', '-80')",
   140  //	"select col1, col2 from t where...",
   141  //	"select col1, keyspace_id() as ksid from t where...",
   142  //	"select id, count(*), sum(price) from t group by id",
   143  //	"select * from t where customer_id=1 and val = 'newton'".
   144  //	Only "in_keyrange" expressions, integer and string comparisons are supported in the where clause.
   145  //	The select expressions can be any valid non-aggregate expressions,
   146  //	or count(*), or sum(col).
   147  //	If the target column name does not match the source expression, an
   148  //	alias like "a+b as targetcol" must be used.
   149  //	More advanced constructs can be used. Please see the table plan builder
   150  //	documentation for more info.
   151  func newVReplicator(id uint32, source *binlogdatapb.BinlogSource, sourceVStreamer VStreamerClient, stats *binlogplayer.Stats, dbClient binlogplayer.DBClient, mysqld mysqlctl.MysqlDaemon, vre *Engine) *vreplicator {
   152  	if vreplicationHeartbeatUpdateInterval > vreplicationMinimumHeartbeatUpdateInterval {
   153  		log.Warningf("The supplied value for vreplication_heartbeat_update_interval:%d seconds is larger than the maximum allowed:%d seconds, vreplication will fallback to %d",
   154  			vreplicationHeartbeatUpdateInterval, vreplicationMinimumHeartbeatUpdateInterval, vreplicationMinimumHeartbeatUpdateInterval)
   155  	}
   156  	return &vreplicator{
   157  		vre:             vre,
   158  		id:              id,
   159  		source:          source,
   160  		sourceVStreamer: sourceVStreamer,
   161  		stats:           stats,
   162  		dbClient:        newVDBClient(dbClient, stats),
   163  		mysqld:          mysqld,
   164  
   165  		throttleUpdatesRateLimiter: timer.NewRateLimiter(time.Second),
   166  	}
   167  }
   168  
   169  // Replicate starts a vreplication stream. It can be in one of three phases:
   170  // 1. Init: If a request is issued with no starting position, we assume that the
   171  // contents of the tables must be copied first. During this phase, the list of
   172  // tables to be copied is inserted into the copy_state table. A successful insert
   173  // gets us out of this phase.
   174  // 2. Copy: If the copy_state table has rows, then we are in this phase. During this
   175  // phase, we repeatedly invoke copyNext until all the tables are copied. After each
   176  // table is successfully copied, it's removed from the copy_state table. We exit this
   177  // phase when there are no rows left in copy_state.
   178  // 3. Replicate: In this phase, we replicate binlog events indefinitely, unless
   179  // a stop position was requested. This phase differs from the Init phase because
   180  // there is a replication position.
   181  // If a request had a starting position, then we go directly into phase 3.
   182  // During these phases, the state of vreplication is reported as 'Init', 'Copying',
   183  // or 'Running'. They all mean the same thing. The difference in the phases depends
   184  // on the criteria defined above. The different states reported are mainly
   185  // informational. The 'Stopped' state is, however, honored.
   186  // All phases share the same plan building framework. We leverage the fact the
   187  // row representation of a read (during copy) and a binlog event are identical.
   188  // However, there are some subtle differences, explained in the plan builder
   189  // code.
   190  func (vr *vreplicator) Replicate(ctx context.Context) error {
   191  	err := vr.replicate(ctx)
   192  	if err != nil {
   193  		if err := vr.setMessage(err.Error()); err != nil {
   194  			binlogplayer.LogError("Failed to set error state", err)
   195  		}
   196  	}
   197  	return err
   198  }
   199  
   200  func (vr *vreplicator) replicate(ctx context.Context) error {
   201  	// Manage SQL_MODE in the same way that mysqldump does.
   202  	// Save the original sql_mode, set it to a permissive mode,
   203  	// and then reset it back to the original value at the end.
   204  	resetFunc, err := vr.setSQLMode(ctx, vr.dbClient)
   205  	defer resetFunc()
   206  	if err != nil {
   207  		return err
   208  	}
   209  
   210  	colInfo, err := vr.buildColInfoMap(ctx)
   211  	if err != nil {
   212  		return err
   213  	}
   214  	vr.colInfoMap = colInfo
   215  	if err := vr.getSettingFKCheck(); err != nil {
   216  		return err
   217  	}
   218  	//defensive guard, should be a no-op since it should happen after copy is done
   219  	defer vr.resetFKCheckAfterCopy(vr.dbClient)
   220  
   221  	for {
   222  		select {
   223  		case <-ctx.Done():
   224  			return nil
   225  		default:
   226  		}
   227  		// This rollback is a no-op. It's here for safety
   228  		// in case the functions below leave transactions open.
   229  		vr.dbClient.Rollback()
   230  
   231  		settings, numTablesToCopy, err := vr.loadSettings(ctx, vr.dbClient)
   232  		if err != nil {
   233  			return err
   234  		}
   235  		// If any of the operations below changed state to Stopped or Error, we should return.
   236  		if settings.State == binlogplayer.BlpStopped || settings.State == binlogplayer.BlpError {
   237  			return nil
   238  		}
   239  		switch {
   240  		case numTablesToCopy != 0:
   241  			if err := vr.clearFKCheck(vr.dbClient); err != nil {
   242  				log.Warningf("Unable to clear FK check %v", err)
   243  				return err
   244  			}
   245  			if err := newVCopier(vr).copyNext(ctx, settings); err != nil {
   246  				vr.stats.ErrorCounts.Add([]string{"Copy"}, 1)
   247  				return err
   248  			}
   249  			settings, numTablesToCopy, err = vr.loadSettings(ctx, vr.dbClient)
   250  			if err != nil {
   251  				return err
   252  			}
   253  			if numTablesToCopy == 0 {
   254  				if err := vr.insertLog(LogCopyEnd, fmt.Sprintf("Copy phase completed at gtid %s", settings.StartPos)); err != nil {
   255  					return err
   256  				}
   257  			}
   258  		case settings.StartPos.IsZero():
   259  			if err := newVCopier(vr).initTablesForCopy(ctx); err != nil {
   260  				vr.stats.ErrorCounts.Add([]string{"Copy"}, 1)
   261  				return err
   262  			}
   263  		default:
   264  			if err := vr.resetFKCheckAfterCopy(vr.dbClient); err != nil {
   265  				log.Warningf("Unable to reset FK check %v", err)
   266  				return err
   267  			}
   268  			if vr.source.StopAfterCopy {
   269  				return vr.setState(binlogplayer.BlpStopped, "Stopped after copy.")
   270  			}
   271  			if err := vr.setState(binlogplayer.BlpRunning, ""); err != nil {
   272  				vr.stats.ErrorCounts.Add([]string{"Replicate"}, 1)
   273  				return err
   274  			}
   275  			return newVPlayer(vr, settings, nil, mysql.Position{}, "replicate").play(ctx)
   276  		}
   277  	}
   278  }
   279  
   280  // ColumnInfo is used to store charset and collation
   281  type ColumnInfo struct {
   282  	Name        string
   283  	CharSet     string
   284  	Collation   string
   285  	DataType    string
   286  	ColumnType  string
   287  	IsPK        bool
   288  	IsGenerated bool
   289  }
   290  
   291  func (vr *vreplicator) buildColInfoMap(ctx context.Context) (map[string][]*ColumnInfo, error) {
   292  	req := &tabletmanagerdatapb.GetSchemaRequest{Tables: []string{"/.*/"}, ExcludeTables: []string{"/" + schema.GCTableNameExpression + "/"}}
   293  	schema, err := vr.mysqld.GetSchema(ctx, vr.dbClient.DBName(), req)
   294  	if err != nil {
   295  		return nil, err
   296  	}
   297  	queryTemplate := "select character_set_name, collation_name, column_name, data_type, column_type, extra from information_schema.columns where table_schema=%s and table_name=%s;"
   298  	colInfoMap := make(map[string][]*ColumnInfo)
   299  	for _, td := range schema.TableDefinitions {
   300  		query := fmt.Sprintf(queryTemplate, encodeString(vr.dbClient.DBName()), encodeString(td.Name))
   301  		qr, err := vr.mysqld.FetchSuperQuery(ctx, query)
   302  		if err != nil {
   303  			return nil, err
   304  		}
   305  		if len(qr.Rows) == 0 {
   306  			return nil, fmt.Errorf("no data returned from information_schema.columns")
   307  		}
   308  
   309  		var pks []string
   310  		if len(td.PrimaryKeyColumns) != 0 {
   311  			// Use the PK
   312  			pks = td.PrimaryKeyColumns
   313  		} else {
   314  			// Use a PK equivalent if one exists
   315  			if pks, err = vr.mysqld.GetPrimaryKeyEquivalentColumns(ctx, vr.dbClient.DBName(), td.Name); err != nil {
   316  				return nil, err
   317  			}
   318  			// Fall back to using every column in the table if there's no PK or PKE
   319  			if len(pks) == 0 {
   320  				pks = td.Columns
   321  			}
   322  		}
   323  		var colInfo []*ColumnInfo
   324  		for _, row := range qr.Rows {
   325  			charSet := ""
   326  			collation := ""
   327  			columnName := ""
   328  			isPK := false
   329  			isGenerated := false
   330  			var dataType, columnType string
   331  			columnName = row[2].ToString()
   332  			var currentField *querypb.Field
   333  			for _, field := range td.Fields {
   334  				if field.Name == columnName {
   335  					currentField = field
   336  					break
   337  				}
   338  			}
   339  			if currentField == nil {
   340  				continue
   341  			}
   342  			dataType = row[3].ToString()
   343  			columnType = row[4].ToString()
   344  			if sqltypes.IsText(currentField.Type) {
   345  				charSet = row[0].ToString()
   346  				collation = row[1].ToString()
   347  			}
   348  			if dataType == "" || columnType == "" {
   349  				return nil, fmt.Errorf("no dataType/columnType found in information_schema.columns for table %s, column %s", td.Name, columnName)
   350  			}
   351  			for _, pk := range pks {
   352  				if columnName == pk {
   353  					isPK = true
   354  				}
   355  			}
   356  			extra := strings.ToLower(row[5].ToString())
   357  			if strings.Contains(extra, "stored generated") || strings.Contains(extra, "virtual generated") {
   358  				isGenerated = true
   359  			}
   360  			colInfo = append(colInfo, &ColumnInfo{
   361  				Name:        columnName,
   362  				CharSet:     charSet,
   363  				Collation:   collation,
   364  				DataType:    dataType,
   365  				ColumnType:  columnType,
   366  				IsPK:        isPK,
   367  				IsGenerated: isGenerated,
   368  			})
   369  		}
   370  		colInfoMap[td.Name] = colInfo
   371  	}
   372  	return colInfoMap, nil
   373  }
   374  
   375  // Same as readSettings, but stores some of the results on this vr.
   376  func (vr *vreplicator) loadSettings(ctx context.Context, dbClient *vdbClient) (settings binlogplayer.VRSettings, numTablesToCopy int64, err error) {
   377  	settings, numTablesToCopy, err = vr.readSettings(ctx, dbClient)
   378  	if err == nil {
   379  		vr.WorkflowType = int32(settings.WorkflowType)
   380  		vr.WorkflowName = settings.WorkflowName
   381  	}
   382  	return settings, numTablesToCopy, err
   383  }
   384  
   385  func (vr *vreplicator) readSettings(ctx context.Context, dbClient *vdbClient) (settings binlogplayer.VRSettings, numTablesToCopy int64, err error) {
   386  	settings, err = binlogplayer.ReadVRSettings(dbClient, vr.id)
   387  	if err != nil {
   388  		return settings, numTablesToCopy, fmt.Errorf("error reading VReplication settings: %v", err)
   389  	}
   390  
   391  	query := fmt.Sprintf("select count(distinct table_name) from _vt.copy_state where vrepl_id=%d", vr.id)
   392  	qr, err := vr.dbClient.ExecuteFetch(query, maxRows)
   393  	if err != nil {
   394  		return settings, numTablesToCopy, err
   395  	}
   396  	if len(qr.Rows) == 0 || len(qr.Rows[0]) == 0 {
   397  		return settings, numTablesToCopy, fmt.Errorf("unexpected result from %s: %v", query, qr)
   398  	}
   399  	numTablesToCopy, err = evalengine.ToInt64(qr.Rows[0][0])
   400  	if err != nil {
   401  		return settings, numTablesToCopy, err
   402  	}
   403  	return settings, numTablesToCopy, nil
   404  }
   405  
   406  func (vr *vreplicator) setMessage(message string) error {
   407  	message = binlogplayer.MessageTruncate(message)
   408  	vr.stats.History.Add(&binlogplayer.StatsHistoryRecord{
   409  		Time:    time.Now(),
   410  		Message: message,
   411  	})
   412  	buf := sqlparser.NewTrackedBuffer(nil)
   413  	buf.Myprintf("update _vt.vreplication set message=%s where id=%s", encodeString(message), strconv.Itoa(int(vr.id)))
   414  	query := buf.ParsedQuery().Query
   415  	if _, err := vr.dbClient.Execute(query); err != nil {
   416  		return fmt.Errorf("could not set message: %v: %v", query, err)
   417  	}
   418  	if err := insertLog(vr.dbClient, LogMessage, vr.id, vr.state, message); err != nil {
   419  		return err
   420  	}
   421  	return nil
   422  }
   423  
   424  func (vr *vreplicator) insertLog(typ, message string) error {
   425  	return insertLog(vr.dbClient, typ, vr.id, vr.state, message)
   426  }
   427  
   428  func (vr *vreplicator) setState(state, message string) error {
   429  	if message != "" {
   430  		vr.stats.History.Add(&binlogplayer.StatsHistoryRecord{
   431  			Time:    time.Now(),
   432  			Message: message,
   433  		})
   434  	}
   435  	vr.stats.State.Set(state)
   436  	query := fmt.Sprintf("update _vt.vreplication set state='%v', message=%v where id=%v", state, encodeString(binlogplayer.MessageTruncate(message)), vr.id)
   437  	if _, err := vr.dbClient.ExecuteFetch(query, 1); err != nil {
   438  		return fmt.Errorf("could not set state: %v: %v", query, err)
   439  	}
   440  	if state == vr.state {
   441  		return nil
   442  	}
   443  	if err := insertLog(vr.dbClient, LogStateChange, vr.id, state, message); err != nil {
   444  		return err
   445  	}
   446  	vr.state = state
   447  
   448  	return nil
   449  }
   450  
   451  func encodeString(in string) string {
   452  	var buf strings.Builder
   453  	sqltypes.NewVarChar(in).EncodeSQL(&buf)
   454  	return buf.String()
   455  }
   456  
   457  func (vr *vreplicator) getSettingFKCheck() error {
   458  	qr, err := vr.dbClient.Execute("select @@foreign_key_checks;")
   459  	if err != nil {
   460  		return err
   461  	}
   462  	if len(qr.Rows) != 1 || len(qr.Fields) != 1 {
   463  		return fmt.Errorf("unable to select @@foreign_key_checks")
   464  	}
   465  	vr.originalFKCheckSetting, err = evalengine.ToInt64(qr.Rows[0][0])
   466  	if err != nil {
   467  		return err
   468  	}
   469  	return nil
   470  }
   471  
   472  func (vr *vreplicator) resetFKCheckAfterCopy(dbClient *vdbClient) error {
   473  	_, err := dbClient.Execute(fmt.Sprintf("set foreign_key_checks=%d;", vr.originalFKCheckSetting))
   474  	return err
   475  }
   476  
   477  func (vr *vreplicator) setSQLMode(ctx context.Context, dbClient *vdbClient) (func(), error) {
   478  	resetFunc := func() {}
   479  	// First save the original SQL mode if we have not already done so
   480  	if vr.originalSQLMode == "" {
   481  		res, err := dbClient.Execute(getSQLModeQuery)
   482  		if err != nil || len(res.Rows) != 1 {
   483  			return resetFunc, fmt.Errorf("could not get the original sql_mode on target: %v", err)
   484  		}
   485  		vr.originalSQLMode = res.Named().Row().AsString("sql_mode", "")
   486  	}
   487  
   488  	// Create a callback function for resetting the original
   489  	// SQL mode back at the end of the vreplication operation.
   490  	// You should defer this callback wherever you call setSQLMode()
   491  	resetFunc = func() {
   492  		query := fmt.Sprintf(setSQLModeQueryf, vr.originalSQLMode)
   493  		_, err := dbClient.Execute(query)
   494  		if err != nil {
   495  			log.Warningf("Could not reset sql_mode on target using %s: %v", query, err)
   496  		}
   497  	}
   498  	vreplicationSQLMode := SQLMode
   499  	settings, _, err := vr.readSettings(ctx, dbClient)
   500  	if err != nil {
   501  		return resetFunc, err
   502  	}
   503  	if settings.WorkflowType == int32(binlogdatapb.VReplicationWorkflowType_OnlineDDL) {
   504  		vreplicationSQLMode = StrictSQLMode
   505  	}
   506  
   507  	// Now set it to a permissive mode that will allow us to recreate
   508  	// any database object that exists on the source in full on the
   509  	// target
   510  	query := fmt.Sprintf(setSQLModeQueryf, vreplicationSQLMode)
   511  	if _, err := dbClient.Execute(query); err != nil {
   512  		return resetFunc, fmt.Errorf("could not set the permissive sql_mode on target using %s: %v", query, err)
   513  	}
   514  
   515  	return resetFunc, nil
   516  }
   517  
   518  // throttlerAppName returns the app name to be used by throttlerClient for this particular workflow
   519  // example results:
   520  //   - "vreplication" for most flows
   521  //   - "vreplication:online-ddl" for online ddl flows.
   522  //     Note that with such name, it's possible to throttle
   523  //     the worflow by either /throttler/throttle-app?app=vreplication and/or /throttler/throttle-app?app=online-ddl
   524  //     This is useful when we want to throttle all migrations. We throttle "online-ddl" and that applies to both vreplication
   525  //     migrations as well as gh-ost migrations.
   526  func (vr *vreplicator) throttlerAppName() string {
   527  	names := []string{vr.WorkflowName, throttlerVReplicationAppName}
   528  	if vr.WorkflowType == int32(binlogdatapb.VReplicationWorkflowType_OnlineDDL) {
   529  		names = append(names, throttlerOnlineDDLAppName)
   530  	}
   531  	return strings.Join(names, ":")
   532  }
   533  
   534  func (vr *vreplicator) updateTimeThrottled(componentThrottled ComponentName) error {
   535  	err := vr.throttleUpdatesRateLimiter.Do(func() error {
   536  		tm := time.Now().Unix()
   537  		update, err := binlogplayer.GenerateUpdateTimeThrottled(vr.id, tm, string(componentThrottled))
   538  		if err != nil {
   539  			return err
   540  		}
   541  		if _, err := vr.dbClient.ExecuteFetch(update, maxRows); err != nil {
   542  			return fmt.Errorf("error %v updating time throttled", err)
   543  		}
   544  		return nil
   545  	})
   546  	return err
   547  }
   548  
   549  func (vr *vreplicator) updateHeartbeatTime(tm int64) error {
   550  	update, err := binlogplayer.GenerateUpdateHeartbeat(vr.id, tm)
   551  	if err != nil {
   552  		return err
   553  	}
   554  	if _, err := vr.dbClient.ExecuteFetch(update, maxRows); err != nil {
   555  		return fmt.Errorf("error %v updating time", err)
   556  	}
   557  	return nil
   558  }
   559  
   560  func (vr *vreplicator) clearFKCheck(dbClient *vdbClient) error {
   561  	_, err := dbClient.Execute("set foreign_key_checks=0;")
   562  	return err
   563  }
   564  
   565  func recalculatePKColsInfoByColumnNames(uniqueKeyColumnNames []string, colInfos []*ColumnInfo) (pkColInfos []*ColumnInfo) {
   566  	pkColInfos = colInfos[:]
   567  	columnOrderMap := map[string]int64{}
   568  	for _, colInfo := range pkColInfos {
   569  		columnOrderMap[colInfo.Name] = math.MaxInt64
   570  	}
   571  
   572  	isPKMap := map[string]bool{}
   573  	for i, colName := range uniqueKeyColumnNames {
   574  		columnOrderMap[colName] = int64(i)
   575  		isPKMap[colName] = true
   576  	}
   577  	sort.SliceStable(pkColInfos, func(i, j int) bool { return columnOrderMap[pkColInfos[i].Name] < columnOrderMap[pkColInfos[j].Name] })
   578  	for i := range pkColInfos {
   579  		pkColInfos[i].IsPK = isPKMap[pkColInfos[i].Name]
   580  	}
   581  	return pkColInfos
   582  }
   583  
   584  // stashSecondaryKeys temporarily DROPs all secondary keys from the table schema
   585  // and stashes an ALTER TABLE statement that will be used to recreate them at the
   586  // end of the copy phase.
   587  func (vr *vreplicator) stashSecondaryKeys(ctx context.Context, tableName string) error {
   588  	if !vr.supportsDeferredSecondaryKeys() {
   589  		return fmt.Errorf("deferring secondary key creation is not supported for %s workflows",
   590  			binlogdatapb.VReplicationWorkflowType_name[vr.WorkflowType])
   591  	}
   592  	secondaryKeys, err := vr.getTableSecondaryKeys(ctx, tableName)
   593  	if err != nil {
   594  		return err
   595  	}
   596  	if len(secondaryKeys) > 0 {
   597  		alterDrop := &sqlparser.AlterTable{
   598  			Table: sqlparser.TableName{
   599  				Qualifier: sqlparser.NewIdentifierCS(vr.dbClient.DBName()),
   600  				Name:      sqlparser.NewIdentifierCS(tableName),
   601  			},
   602  		}
   603  		alterReAdd := &sqlparser.AlterTable{
   604  			Table: sqlparser.TableName{
   605  				Qualifier: sqlparser.NewIdentifierCS(vr.dbClient.DBName()),
   606  				Name:      sqlparser.NewIdentifierCS(tableName),
   607  			},
   608  		}
   609  		for _, secondaryKey := range secondaryKeys {
   610  			// Primary should never happen. Fulltext keys are
   611  			// not supported for deferral and retained during
   612  			// the copy phase as they have some unique
   613  			// behaviors and constraints:
   614  			// - Adding a fulltext key requires a full table
   615  			//   rebuild to add the internal FTS_DOC_ID field
   616  			//   to each record.
   617  			// - You can not add/remove multiple fulltext keys
   618  			//   in a single ALTER statement.
   619  			if secondaryKey.Info.Primary || secondaryKey.Info.Fulltext {
   620  				continue
   621  			}
   622  			alterDrop.AlterOptions = append(alterDrop.AlterOptions,
   623  				&sqlparser.DropKey{
   624  					Name: secondaryKey.Info.Name,
   625  					Type: sqlparser.NormalKeyType,
   626  				},
   627  			)
   628  			alterReAdd.AlterOptions = append(alterReAdd.AlterOptions,
   629  				&sqlparser.AddIndexDefinition{
   630  					IndexDefinition: secondaryKey,
   631  				},
   632  			)
   633  		}
   634  		action, err := json.Marshal(PostCopyAction{
   635  			Type: PostCopyActionSQL,
   636  			Task: sqlparser.String(alterReAdd),
   637  		})
   638  		if err != nil {
   639  			return err
   640  		}
   641  		insert, err := sqlparser.ParseAndBind(sqlCreatePostCopyAction, sqltypes.Uint32BindVariable(vr.id),
   642  			sqltypes.StringBindVariable(tableName), sqltypes.StringBindVariable(string(action)))
   643  		if err != nil {
   644  			return err
   645  		}
   646  		// Use a new DB client to avoid interfering with open transactions
   647  		// in the shared client as DDL includes an implied commit.
   648  		// We're also NOT using a DBA connection here because we want to
   649  		// be sure that the commit fails if the instance is somehow in
   650  		// READ-ONLY mode.
   651  		dbClient, err := vr.newClientConnection(ctx)
   652  		if err != nil {
   653  			log.Errorf("Unable to connect to the database when saving secondary keys for deferred creation on the %q table in the %q VReplication workflow: %v",
   654  				tableName, vr.WorkflowName, err)
   655  			return vterrors.Wrap(err, "unable to connect to the database when saving secondary keys for deferred creation")
   656  		}
   657  		defer dbClient.Close()
   658  		if _, err := dbClient.ExecuteFetch(insert, 1); err != nil {
   659  			return err
   660  		}
   661  		if _, err := dbClient.ExecuteFetch(sqlparser.String(alterDrop), 1); err != nil {
   662  			// If they've already been dropped, e.g. by another controller running on the tablet
   663  			// when doing a shard merge, then we can ignore the error.
   664  			if sqlErr, ok := err.(*mysql.SQLError); ok && sqlErr.Num == mysql.ERCantDropFieldOrKey {
   665  				secondaryKeys, err := vr.getTableSecondaryKeys(ctx, tableName)
   666  				if err == nil && len(secondaryKeys) == 0 {
   667  					return nil
   668  				}
   669  			}
   670  			return err
   671  		}
   672  	}
   673  
   674  	return nil
   675  }
   676  
   677  func (vr *vreplicator) getTableSecondaryKeys(ctx context.Context, tableName string) ([]*sqlparser.IndexDefinition, error) {
   678  	req := &tabletmanagerdatapb.GetSchemaRequest{Tables: []string{tableName}}
   679  	schema, err := vr.mysqld.GetSchema(ctx, vr.dbClient.DBName(), req)
   680  	if err != nil {
   681  		return nil, err
   682  	}
   683  	// schema should never be nil, but check to be extra safe.
   684  	if schema == nil || len(schema.TableDefinitions) != 1 {
   685  		return nil, fmt.Errorf("unexpected number of table definitions returned from GetSchema call for table %q: %d",
   686  			tableName, len(schema.TableDefinitions))
   687  	}
   688  	tableSchema := schema.TableDefinitions[0].Schema
   689  	var secondaryKeys []*sqlparser.IndexDefinition
   690  	parsedDDL, err := sqlparser.ParseStrictDDL(tableSchema)
   691  	if err != nil {
   692  		return secondaryKeys, err
   693  	}
   694  	createTable, ok := parsedDDL.(*sqlparser.CreateTable)
   695  	// createTable or createTable.TableSpec should never be nil
   696  	// if it was a valid cast, but check to be extra safe.
   697  	if !ok || createTable == nil || createTable.GetTableSpec() == nil {
   698  		return nil, fmt.Errorf("could not determine CREATE TABLE statement from table schema %q", tableSchema)
   699  	}
   700  
   701  	for _, index := range createTable.GetTableSpec().Indexes {
   702  		if !index.Info.Primary {
   703  			secondaryKeys = append(secondaryKeys, index)
   704  		}
   705  	}
   706  	return secondaryKeys, err
   707  }
   708  
   709  func (vr *vreplicator) execPostCopyActions(ctx context.Context, tableName string) error {
   710  	defer vr.stats.PhaseTimings.Record("postCopyActions", time.Now())
   711  
   712  	// Use a new DB client to avoid interfering with open transactions
   713  	// in the shared client as DDL includes an implied commit.
   714  	// We're also NOT using a DBA connection here because we want to be
   715  	// sure that the work fails if the instance is somehow in READ-ONLY
   716  	// mode.
   717  	dbClient, err := vr.newClientConnection(ctx)
   718  	if err != nil {
   719  		log.Errorf("Unable to connect to the database when executing post copy actions on the %q table in the %q VReplication workflow: %v",
   720  			tableName, vr.WorkflowName, err)
   721  		return vterrors.Wrap(err, "unable to connect to the database when executing post copy actions")
   722  	}
   723  	defer dbClient.Close()
   724  
   725  	query, err := sqlparser.ParseAndBind(sqlGetPostCopyActions, sqltypes.Uint32BindVariable(vr.id),
   726  		sqltypes.StringBindVariable(tableName))
   727  	if err != nil {
   728  		return err
   729  	}
   730  	qr, err := dbClient.ExecuteFetch(query, -1)
   731  	if err != nil {
   732  		return err
   733  	}
   734  	// qr should never be nil, but check anyway to be extra safe.
   735  	if qr == nil || len(qr.Rows) == 0 {
   736  		return nil
   737  	}
   738  
   739  	if err := vr.insertLog(LogCopyStart, fmt.Sprintf("Executing %d post copy action(s) for %s table",
   740  		len(qr.Rows), tableName)); err != nil {
   741  		return err
   742  	}
   743  
   744  	// Save our connection ID so we can use it to easily KILL any
   745  	// running SQL action we may perform later if needed.
   746  	idqr, err := dbClient.ExecuteFetch("select connection_id()", 1)
   747  	if err != nil {
   748  		return err
   749  	}
   750  	// qr should never be nil, but check anyway to be extra safe.
   751  	if idqr == nil || len(idqr.Rows) != 1 {
   752  		return fmt.Errorf("unexpected number of rows returned (%d) from connection_id() query", len(idqr.Rows))
   753  	}
   754  	connID, err := idqr.Rows[0][0].ToUint64()
   755  	if err != nil || connID == 0 {
   756  		return fmt.Errorf("unexpected result (%d) from connection_id() query, error: %v", connID, err)
   757  	}
   758  
   759  	deleteAction := func(dbc *vdbClient, id int64, vid uint32, tn string) error {
   760  		delq, err := sqlparser.ParseAndBind(sqlDeletePostCopyAction, sqltypes.Uint32BindVariable(vid),
   761  			sqltypes.StringBindVariable(tn), sqltypes.Int64BindVariable(id))
   762  		if err != nil {
   763  			return err
   764  		}
   765  		if _, err := dbc.ExecuteFetch(delq, 1); err != nil {
   766  			return fmt.Errorf("failed to delete post copy action for the %q table with id %d: %v",
   767  				tableName, id, err)
   768  		}
   769  		return nil
   770  	}
   771  
   772  	// This could take hours so we start a monitoring goroutine to
   773  	// listen for context cancellation, which would indicate that
   774  	// the controller is stopping due to engine shutdown (tablet
   775  	// shutdown or transition). If that happens we attempt to KILL
   776  	// the running ALTER statement using a DBA connection.
   777  	// If we don't do this then we could e.g. cause a PRS to fail as
   778  	// the running ALTER will block setting [super_]read_only.
   779  	// A failed/killed ALTER will be tried again when the copy
   780  	// phase starts up again on the (new) PRIMARY.
   781  	var action PostCopyAction
   782  	done := make(chan struct{})
   783  	defer close(done)
   784  	killAction := func(ak PostCopyAction) error {
   785  		// If we're executing an SQL query then KILL the
   786  		// connection being used to execute it.
   787  		if ak.Type == PostCopyActionSQL {
   788  			if connID < 1 {
   789  				return fmt.Errorf("invalid connection ID found (%d) when attempting to kill %q", connID, ak.Task)
   790  			}
   791  			killdbc := vr.vre.dbClientFactoryDba()
   792  			if err := killdbc.Connect(); err != nil {
   793  				return fmt.Errorf("unable to connect to the database when attempting to kill %q: %v", ak.Task, err)
   794  			}
   795  			defer killdbc.Close()
   796  			_, err = killdbc.ExecuteFetch(fmt.Sprintf("kill %d", connID), 1)
   797  			return err
   798  		}
   799  		// We may support non-SQL actions in the future.
   800  		return nil
   801  	}
   802  	go func() {
   803  		select {
   804  		// Only cancel an ongoing ALTER if the engine is closing.
   805  		case <-vr.vre.ctx.Done():
   806  			log.Infof("Copy of the %q table stopped when performing the following post copy action in the %q VReplication workflow: %+v",
   807  				tableName, vr.WorkflowName, action)
   808  			if err := killAction(action); err != nil {
   809  				log.Errorf("Failed to kill post copy action on the %q table in the %q VReplication workflow: %v",
   810  					tableName, vr.WorkflowName, err)
   811  			}
   812  			return
   813  		case <-done:
   814  			// We're done, so no longer need to listen for cancellation.
   815  			return
   816  		}
   817  	}()
   818  
   819  	for _, row := range qr.Named().Rows {
   820  		select {
   821  		// Stop any further actions if the vreplicator's context is
   822  		// cancelled -- most likely due to hitting the
   823  		// vreplication_copy_phase_duration
   824  		case <-ctx.Done():
   825  			return vterrors.Errorf(vtrpcpb.Code_CANCELED, "context has expired")
   826  		default:
   827  		}
   828  		id, err := row["id"].ToInt64()
   829  		if err != nil {
   830  			return err
   831  		}
   832  		action = PostCopyAction{}
   833  		actionBytes, err := row["action"].ToBytes()
   834  		if err != nil {
   835  			return err
   836  		}
   837  		if err := json.Unmarshal(actionBytes, &action); err != nil {
   838  			return err
   839  		}
   840  
   841  		// There can be multiple vreplicators/controllers running on
   842  		// the same tablet for the same table e.g. when doing shard
   843  		// merges. Let's check for that and if there are still others
   844  		// that have not finished the copy phase for the table, with
   845  		// the same action, then we skip executing it as an individual
   846  		// action on a table should only be done by the last vreplicator
   847  		// to finish. We use a transaction because we select matching
   848  		// rows with FOR UPDATE in order to serialize the execution of
   849  		// the post copy actions for the same workflow and table.
   850  		// This ensures that the actions are only performed once after
   851  		// all streams have completed the copy phase for the table.
   852  		redundant := false
   853  		_, err = dbClient.ExecuteFetch("start transaction", 1)
   854  		if err != nil {
   855  			return err
   856  		}
   857  		vrsq, err := sqlparser.ParseAndBind(sqlGetAndLockPostCopyActionsForTable, sqltypes.StringBindVariable(tableName))
   858  		if err != nil {
   859  			return err
   860  		}
   861  		vrsres, err := dbClient.ExecuteFetch(vrsq, -1)
   862  		if err != nil {
   863  			return fmt.Errorf("failed to get post copy actions for the %q table: %v", tableName, err)
   864  		}
   865  		if vrsres != nil && len(vrsres.Rows) > 1 {
   866  			// We have more than one planned post copy action on the table.
   867  			for _, row := range vrsres.Named().Rows {
   868  				vrid, err := row["vrepl_id"].ToUint64()
   869  				if err != nil {
   870  					return err
   871  				}
   872  				ctlaction := row["action"].ToString()
   873  				// Let's make sure that it's a different controller/vreplicator
   874  				// and that the action is the same.
   875  				if uint32(vrid) != vr.id && strings.EqualFold(ctlaction, string(actionBytes)) {
   876  					// We know that there's another controller/vreplicator yet
   877  					// to finish its copy phase for the table and it will perform
   878  					// the same action on the same table when it completes, so we
   879  					// skip doing the action and simply delete our action record
   880  					// to mark this controller/vreplicator's post copy action work
   881  					// as being done for the table before it finishes the copy
   882  					// phase for the table.
   883  					if err := deleteAction(dbClient, id, vr.id, tableName); err != nil {
   884  						return err
   885  					}
   886  					redundant = true
   887  					break
   888  				}
   889  			}
   890  		}
   891  		_, err = dbClient.ExecuteFetch("commit", 1)
   892  		if err != nil {
   893  			return err
   894  		}
   895  		if redundant {
   896  			// Skip this action as it will be executed by a later vreplicator.
   897  			continue
   898  		}
   899  
   900  		switch action.Type {
   901  		case PostCopyActionSQL:
   902  			log.Infof("Executing post copy SQL action on the %q table in the %q VReplication workflow: %s",
   903  				tableName, vr.WorkflowName, action.Task)
   904  			// This will return an io.EOF / MySQL CRServerLost (errno 2013)
   905  			// error if it is killed by the monitoring goroutine.
   906  			if _, err := dbClient.ExecuteFetch(action.Task, -1); err != nil {
   907  				failedAlterErr := err
   908  				// It's possible that we previously executed the ALTER but
   909  				// the subsequent DELETE of the post_copy_action record failed.
   910  				// For example, the context could be cancelled in-between.
   911  				// It's also possible that the user has modified the schema on
   912  				// the target side.
   913  				// If we get a duplicate key/index error then let's see if the
   914  				// index definitions that we would have added already exist in
   915  				// the table schema and if so move forward and delete the
   916  				// post_copy_action record.
   917  				if sqlErr, ok := err.(*mysql.SQLError); ok && sqlErr.Number() == mysql.ERDupKeyName {
   918  					stmt, err := sqlparser.ParseStrictDDL(action.Task)
   919  					if err != nil {
   920  						return failedAlterErr
   921  					}
   922  					alterStmt, ok := stmt.(*sqlparser.AlterTable)
   923  					if !ok {
   924  						return failedAlterErr
   925  					}
   926  					currentSKs, err := vr.getTableSecondaryKeys(ctx, tableName)
   927  					if err != nil {
   928  						return failedAlterErr
   929  					}
   930  					if len(currentSKs) < len(alterStmt.AlterOptions) {
   931  						return failedAlterErr
   932  					}
   933  					for _, alterOption := range alterStmt.AlterOptions {
   934  						addKey, ok := alterOption.(*sqlparser.AddIndexDefinition)
   935  						if !ok {
   936  							return failedAlterErr
   937  						}
   938  						found := false
   939  						for _, currentSK := range currentSKs {
   940  							if sqlparser.Equals.RefOfIndexDefinition(addKey.IndexDefinition, currentSK) {
   941  								found = true
   942  								break
   943  							}
   944  						}
   945  						if !found {
   946  							return failedAlterErr
   947  						}
   948  					}
   949  					// All of the keys we wanted to add in the ALTER already
   950  					// exist in the live table schema.
   951  				} else {
   952  					return failedAlterErr
   953  				}
   954  			}
   955  			if err := deleteAction(dbClient, id, vr.id, tableName); err != nil {
   956  				return err
   957  			}
   958  		default:
   959  			return fmt.Errorf("unsupported post copy action type: %v", action.Type)
   960  		}
   961  	}
   962  
   963  	if err := vr.insertLog(LogCopyStart, fmt.Sprintf("Completed all post copy actions for %s table",
   964  		tableName)); err != nil {
   965  		return err
   966  	}
   967  
   968  	return nil
   969  }
   970  
   971  // supportsDeferredSecondaryKeys tells you if related work should be done
   972  // for the workflow. Deferring secondary index generation is only supported
   973  // with MoveTables, Migrate, and Reshard.
   974  func (vr *vreplicator) supportsDeferredSecondaryKeys() bool {
   975  	return vr.WorkflowType == int32(binlogdatapb.VReplicationWorkflowType_MoveTables) ||
   976  		vr.WorkflowType == int32(binlogdatapb.VReplicationWorkflowType_Migrate) ||
   977  		vr.WorkflowType == int32(binlogdatapb.VReplicationWorkflowType_Reshard)
   978  }
   979  
   980  func (vr *vreplicator) newClientConnection(ctx context.Context) (*vdbClient, error) {
   981  	dbc := vr.vre.dbClientFactoryFiltered()
   982  	if err := dbc.Connect(); err != nil {
   983  		return nil, vterrors.Wrap(err, "can't connect to database")
   984  	}
   985  	dbClient := newVDBClient(dbc, vr.stats)
   986  	if _, err := vr.setSQLMode(ctx, dbClient); err != nil {
   987  		return nil, vterrors.Wrap(err, "failed to set sql_mode")
   988  	}
   989  	if err := vr.clearFKCheck(dbClient); err != nil {
   990  		return nil, vterrors.Wrap(err, "failed to clear foreign key check")
   991  	}
   992  	return dbClient, nil
   993  }