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

     1  /*
     2  Copyright 2021 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  	"strconv"
    23  
    24  	"vitess.io/vitess/go/vt/log"
    25  
    26  	"vitess.io/vitess/go/mysql"
    27  	"vitess.io/vitess/go/vt/sqlparser"
    28  
    29  	"vitess.io/vitess/go/sqltypes"
    30  	"vitess.io/vitess/go/vt/vtgate/evalengine"
    31  )
    32  
    33  const (
    34  	vreplicationLogTableName = "_vt.vreplication_log"
    35  )
    36  
    37  const (
    38  	// Enum values for type column of _vt.vreplication_log
    39  
    40  	// LogStreamCreate is used when a row in _vt.vreplication is inserted via VReplicationExec
    41  	LogStreamCreate = "Stream Created"
    42  	// LogStreamUpdate is used when a row in _vt.vreplication is updated via VReplicationExec
    43  	LogStreamUpdate = "Stream Updated"
    44  	// LogStreamDelete is used when a row in _vt.vreplication is deleted via VReplicationExec
    45  	LogStreamDelete = "Stream Deleted"
    46  	// LogMessage is used for generic log messages
    47  	LogMessage = "Message"
    48  	// LogCopyStart is used when the copy phase is started
    49  	LogCopyStart = "Started Copy Phase"
    50  	// LogCopyEnd is used when the copy phase is done
    51  	LogCopyEnd = "Ended Copy Phase"
    52  	// LogStateChange is used when the state of the stream changes
    53  	LogStateChange = "State Changed"
    54  
    55  	// TODO: LogError is not used atm. Currently irrecoverable errors, resumable errors and informational messages
    56  	//  are all treated the same: the message column is updated and state left as Running.
    57  	//  Every five seconds we reset the message and retry streaming so that we can automatically resume from a temporary
    58  	//  loss of connectivity or a reparent. We need to detect if errors are not recoverable and set an Error status.
    59  	//  Since this device (of overloading the message) is strewn across the code, and incorrectly flagging resumable
    60  	//  errors can stall workflows, we have deferred implementing it.
    61  
    62  	// LogError indicates that there is an error from which we cannot recover and the operator needs to intervene.
    63  	LogError = "Error"
    64  )
    65  
    66  func getLastLog(dbClient *vdbClient, vreplID uint32) (id int64, typ, state, message string, err error) {
    67  	var qr *sqltypes.Result
    68  	query := fmt.Sprintf("select id, type, state, message from _vt.vreplication_log where vrepl_id = %d order by id desc limit 1", vreplID)
    69  	if qr, err = dbClient.Execute(query); err != nil {
    70  		return 0, "", "", "", err
    71  	}
    72  	if len(qr.Rows) != 1 {
    73  		return 0, "", "", "", nil
    74  	}
    75  	row := qr.Rows[0]
    76  	id, _ = evalengine.ToInt64(row[0])
    77  	typ = row[1].ToString()
    78  	state = row[2].ToString()
    79  	message = row[3].ToString()
    80  	return id, typ, state, message, nil
    81  }
    82  
    83  func insertLog(dbClient *vdbClient, typ string, vreplID uint32, state, message string) error {
    84  	// getLastLog returns the last log for a stream. During insertion, if the type/state/message match we do not insert
    85  	// a new log but increment the count. This prevents spamming of the log table in case the same message is logged continuously.
    86  	id, _, lastLogState, lastLogMessage, err := getLastLog(dbClient, vreplID)
    87  	if err != nil {
    88  		return err
    89  	}
    90  	if typ == LogStateChange && state == lastLogState {
    91  		// handles case where current state is Running, controller restarts after an error and initializes the state Running
    92  		return nil
    93  	}
    94  	var query string
    95  	if id > 0 && message == lastLogMessage {
    96  		query = fmt.Sprintf("update _vt.vreplication_log set count = count + 1 where id = %d", id)
    97  	} else {
    98  		buf := sqlparser.NewTrackedBuffer(nil)
    99  		buf.Myprintf("insert into _vt.vreplication_log(vrepl_id, type, state, message) values(%s, %s, %s, %s)",
   100  			strconv.Itoa(int(vreplID)), encodeString(typ), encodeString(state), encodeString(message))
   101  		query = buf.ParsedQuery().Query
   102  	}
   103  	if _, err = dbClient.ExecuteFetch(query, 10000); err != nil {
   104  		return fmt.Errorf("could not insert into log table: %v: %v", query, err)
   105  	}
   106  	return nil
   107  }
   108  
   109  // insertLogWithParams is called when a stream is created. The attributes of the stream are stored as a json string
   110  func insertLogWithParams(dbClient *vdbClient, action string, vreplID uint32, params map[string]string) error {
   111  	var message string
   112  	if params != nil {
   113  		obj, _ := json.Marshal(params)
   114  		message = string(obj)
   115  	}
   116  	if err := insertLog(dbClient, action, vreplID, params["state"], message); err != nil {
   117  		return err
   118  	}
   119  	return nil
   120  }
   121  
   122  // isUnrecoverableError returns true if vreplication cannot recover from the given error and should completely terminate
   123  func isUnrecoverableError(err error) bool {
   124  	if err == nil {
   125  		return false
   126  	}
   127  	sqlErr, isSQLErr := mysql.NewSQLErrorFromError(err).(*mysql.SQLError)
   128  	if !isSQLErr {
   129  		return false
   130  	}
   131  	if sqlErr.Num == mysql.ERUnknownError {
   132  		return false
   133  	}
   134  	switch sqlErr.Num {
   135  	case
   136  		// in case-insensitive alphabetical order
   137  		mysql.ERAccessDeniedError,
   138  		mysql.ERBadFieldError,
   139  		mysql.ERBadNullError,
   140  		mysql.ERCantDropFieldOrKey,
   141  		mysql.ERDataOutOfRange,
   142  		mysql.ERDataTooLong,
   143  		mysql.ERDBAccessDenied,
   144  		mysql.ERDupEntry,
   145  		mysql.ERDupFieldName,
   146  		mysql.ERDupKeyName,
   147  		mysql.ERDupUnique,
   148  		mysql.ERFeatureDisabled,
   149  		mysql.ERFunctionNotDefined,
   150  		mysql.ERIllegalValueForType,
   151  		mysql.ERInvalidCastToJSON,
   152  		mysql.ERInvalidJSONBinaryData,
   153  		mysql.ERInvalidJSONCharset,
   154  		mysql.ERInvalidJSONText,
   155  		mysql.ERInvalidJSONTextInParams,
   156  		mysql.ERJSONDocumentTooDeep,
   157  		mysql.ERJSONValueTooBig,
   158  		mysql.ERNoDefault,
   159  		mysql.ERNoDefaultForField,
   160  		mysql.ERNonUniq,
   161  		mysql.ERNonUpdateableTable,
   162  		mysql.ERNoSuchTable,
   163  		mysql.ERNotAllowedCommand,
   164  		mysql.ERNotSupportedYet,
   165  		mysql.EROptionPreventsStatement,
   166  		mysql.ERParseError,
   167  		mysql.ERPrimaryCantHaveNull,
   168  		mysql.ErrCantCreateGeometryObject,
   169  		mysql.ErrGISDataWrongEndianess,
   170  		mysql.ErrNonPositiveRadius,
   171  		mysql.ErrNotImplementedForCartesianSRS,
   172  		mysql.ErrNotImplementedForProjectedSRS,
   173  		mysql.ErrWrongValueForType,
   174  		mysql.ERSPDoesNotExist,
   175  		mysql.ERSpecifiedAccessDenied,
   176  		mysql.ERSyntaxError,
   177  		mysql.ERTooBigRowSize,
   178  		mysql.ERTooBigSet,
   179  		mysql.ERTruncatedWrongValue,
   180  		mysql.ERTruncatedWrongValueForField,
   181  		mysql.ERUnknownCollation,
   182  		mysql.ERUnknownProcedure,
   183  		mysql.ERUnknownTable,
   184  		mysql.ERWarnDataOutOfRange,
   185  		mysql.ERWarnDataTruncated,
   186  		mysql.ERWrongFKDef,
   187  		mysql.ERWrongFieldSpec,
   188  		mysql.ERWrongParamCountToProcedure,
   189  		mysql.ERWrongParametersToProcedure,
   190  		mysql.ERWrongUsage,
   191  		mysql.ERWrongValue,
   192  		mysql.ERWrongValueCountOnRow:
   193  		log.Errorf("Got unrecoverable error: %v", sqlErr)
   194  		return true
   195  	}
   196  	return false
   197  }