vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletmanager/vreplication/vdbclient.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  	"io"
    21  	"time"
    22  
    23  	"context"
    24  
    25  	"vitess.io/vitess/go/mysql"
    26  	"vitess.io/vitess/go/sqltypes"
    27  	"vitess.io/vitess/go/vt/binlog/binlogplayer"
    28  	"vitess.io/vitess/go/vt/log"
    29  )
    30  
    31  // vdbClient is a wrapper on binlogplayer.DBClient.
    32  // It allows us to retry a failed transactions on lock errors.
    33  type vdbClient struct {
    34  	binlogplayer.DBClient
    35  	stats         *binlogplayer.Stats
    36  	InTransaction bool
    37  	startTime     time.Time
    38  	queries       []string
    39  }
    40  
    41  func newVDBClient(dbclient binlogplayer.DBClient, stats *binlogplayer.Stats) *vdbClient {
    42  	return &vdbClient{
    43  		DBClient: dbclient,
    44  		stats:    stats,
    45  	}
    46  }
    47  
    48  func (vc *vdbClient) Begin() error {
    49  	if vc.InTransaction {
    50  		return nil
    51  	}
    52  	if err := vc.DBClient.Begin(); err != nil {
    53  		return err
    54  	}
    55  	vc.queries = append(vc.queries, "begin")
    56  	vc.InTransaction = true
    57  	vc.startTime = time.Now()
    58  	return nil
    59  }
    60  
    61  func (vc *vdbClient) Commit() error {
    62  	if err := vc.DBClient.Commit(); err != nil {
    63  		return err
    64  	}
    65  	vc.InTransaction = false
    66  	vc.queries = nil
    67  	vc.stats.Timings.Record(binlogplayer.BlplTransaction, vc.startTime)
    68  	return nil
    69  }
    70  
    71  func (vc *vdbClient) Rollback() error {
    72  	if !vc.InTransaction {
    73  		return nil
    74  	}
    75  	if err := vc.DBClient.Rollback(); err != nil {
    76  		return err
    77  	}
    78  	vc.InTransaction = false
    79  	// Don't reset queries to allow for vplayer to retry.
    80  	return nil
    81  }
    82  
    83  func (vc *vdbClient) ExecuteFetch(query string, maxrows int) (*sqltypes.Result, error) {
    84  	defer vc.stats.Timings.Record(binlogplayer.BlplQuery, time.Now())
    85  
    86  	if !vc.InTransaction {
    87  		vc.queries = []string{query}
    88  	} else {
    89  		vc.queries = append(vc.queries, query)
    90  	}
    91  	return vc.DBClient.ExecuteFetch(query, maxrows)
    92  }
    93  
    94  // Execute is ExecuteFetch without the maxrows.
    95  func (vc *vdbClient) Execute(query string) (*sqltypes.Result, error) {
    96  	// Number of rows should never exceed relayLogMaxItems.
    97  	return vc.ExecuteFetch(query, relayLogMaxItems)
    98  }
    99  
   100  func (vc *vdbClient) ExecuteWithRetry(ctx context.Context, query string) (*sqltypes.Result, error) {
   101  	qr, err := vc.Execute(query)
   102  	for err != nil {
   103  		if sqlErr, ok := err.(*mysql.SQLError); ok && sqlErr.Number() == mysql.ERLockDeadlock || sqlErr.Number() == mysql.ERLockWaitTimeout {
   104  			log.Infof("retryable error: %v, waiting for %v and retrying", sqlErr, dbLockRetryDelay)
   105  			if err := vc.Rollback(); err != nil {
   106  				return nil, err
   107  			}
   108  			time.Sleep(dbLockRetryDelay)
   109  			// Check context here. Otherwise this can become an infinite loop.
   110  			select {
   111  			case <-ctx.Done():
   112  				return nil, io.EOF
   113  			default:
   114  			}
   115  			qr, err = vc.Retry()
   116  			continue
   117  		}
   118  		return qr, err
   119  	}
   120  	return qr, nil
   121  }
   122  
   123  func (vc *vdbClient) Retry() (*sqltypes.Result, error) {
   124  	var qr *sqltypes.Result
   125  	for _, q := range vc.queries {
   126  		if q == "begin" {
   127  			if err := vc.Begin(); err != nil {
   128  				return nil, err
   129  			}
   130  			continue
   131  		}
   132  		// Number of rows should never exceed relayLogMaxItems.
   133  		result, err := vc.DBClient.ExecuteFetch(q, relayLogMaxItems)
   134  		if err != nil {
   135  			return nil, err
   136  		}
   137  		qr = result
   138  	}
   139  	return qr, nil
   140  }