github.com/pingcap/br@v5.3.0-alpha.0.20220125034240-ec59c7b6ce30+incompatible/pkg/lightning/backend/importer/importer.go (about)

     1  // Copyright 2019 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package importer
    15  
    16  import (
    17  	"context"
    18  	"strings"
    19  	"sync"
    20  	"time"
    21  
    22  	"github.com/coreos/go-semver/semver"
    23  	"github.com/google/uuid"
    24  	"github.com/pingcap/errors"
    25  	"github.com/pingcap/kvproto/pkg/import_kvpb"
    26  	"github.com/pingcap/parser/model"
    27  	"github.com/pingcap/tidb/table"
    28  	"github.com/tikv/client-go/v2/oracle"
    29  	pd "github.com/tikv/pd/client"
    30  	"go.uber.org/zap"
    31  	"google.golang.org/grpc"
    32  
    33  	"github.com/pingcap/br/pkg/lightning/backend"
    34  	"github.com/pingcap/br/pkg/lightning/backend/kv"
    35  	"github.com/pingcap/br/pkg/lightning/common"
    36  	"github.com/pingcap/br/pkg/lightning/log"
    37  	"github.com/pingcap/br/pkg/lightning/tikv"
    38  	"github.com/pingcap/br/pkg/version"
    39  )
    40  
    41  const (
    42  	defaultRetryBackoffTime = time.Second * 3
    43  	writeRowsMaxRetryTimes  = 3
    44  )
    45  
    46  var (
    47  	// Importer backend is compatible with TiDB [2.1.0, NextMajorVersion).
    48  	requiredMinTiDBVersion = *semver.New("2.1.0")
    49  	requiredMinPDVersion   = *semver.New("2.1.0")
    50  	requiredMinTiKVVersion = *semver.New("2.1.0")
    51  	requiredMaxTiDBVersion = version.NextMajorVersion()
    52  	requiredMaxPDVersion   = version.NextMajorVersion()
    53  	requiredMaxTiKVVersion = version.NextMajorVersion()
    54  )
    55  
    56  // importer represents a gRPC connection to tikv-importer. This type is
    57  // goroutine safe: you can share this instance and execute any method anywhere.
    58  type importer struct {
    59  	conn   *grpc.ClientConn
    60  	cli    import_kvpb.ImportKVClient
    61  	pdAddr string
    62  	tls    *common.TLS
    63  
    64  	mutationPool sync.Pool
    65  	// lock ensures ImportEngine are runs serially
    66  	lock sync.Mutex
    67  
    68  	tsMap sync.Map // engineUUID -> commitTS
    69  	// For testing convenience.
    70  	getTSFunc func(ctx context.Context) (uint64, error)
    71  }
    72  
    73  // NewImporter creates a new connection to tikv-importer. A single connection
    74  // per tidb-lightning instance is enough.
    75  func NewImporter(ctx context.Context, tls *common.TLS, importServerAddr string, pdAddr string) (backend.Backend, error) {
    76  	conn, err := grpc.DialContext(ctx, importServerAddr, tls.ToGRPCDialOption())
    77  	if err != nil {
    78  		return backend.MakeBackend(nil), errors.Trace(err)
    79  	}
    80  
    81  	getTSFunc := func(ctx context.Context) (uint64, error) {
    82  		pdCli, err := pd.NewClientWithContext(ctx, []string{pdAddr}, tls.ToPDSecurityOption())
    83  		if err != nil {
    84  			return 0, err
    85  		}
    86  		defer pdCli.Close()
    87  
    88  		physical, logical, err := pdCli.GetTS(ctx)
    89  		if err != nil {
    90  			return 0, err
    91  		}
    92  		return oracle.ComposeTS(physical, logical), nil
    93  	}
    94  
    95  	return backend.MakeBackend(&importer{
    96  		conn:         conn,
    97  		cli:          import_kvpb.NewImportKVClient(conn),
    98  		pdAddr:       pdAddr,
    99  		tls:          tls,
   100  		mutationPool: sync.Pool{New: func() interface{} { return &import_kvpb.Mutation{} }},
   101  		getTSFunc:    getTSFunc,
   102  	}), nil
   103  }
   104  
   105  // NewMockImporter creates an *unconnected* importer based on a custom
   106  // ImportKVClient. This is provided for testing only. Do not use this function
   107  // outside of tests.
   108  func NewMockImporter(cli import_kvpb.ImportKVClient, pdAddr string) backend.Backend {
   109  	return backend.MakeBackend(&importer{
   110  		conn:         nil,
   111  		cli:          cli,
   112  		pdAddr:       pdAddr,
   113  		mutationPool: sync.Pool{New: func() interface{} { return &import_kvpb.Mutation{} }},
   114  		getTSFunc: func(ctx context.Context) (uint64, error) {
   115  			return uint64(time.Now().UnixNano()), nil
   116  		},
   117  	})
   118  }
   119  
   120  // Close the importer connection.
   121  func (importer *importer) Close() {
   122  	if importer.conn != nil {
   123  		if err := importer.conn.Close(); err != nil {
   124  			log.L().Warn("close importer gRPC connection failed", zap.Error(err))
   125  		}
   126  	}
   127  }
   128  
   129  func (*importer) RetryImportDelay() time.Duration {
   130  	return defaultRetryBackoffTime
   131  }
   132  
   133  func (*importer) MaxChunkSize() int {
   134  	// 31 MB. hardcoded by importer, so do we
   135  	return 31 << 10
   136  }
   137  
   138  func (*importer) ShouldPostProcess() bool {
   139  	return true
   140  }
   141  
   142  // isIgnorableOpenCloseEngineError checks if the error from
   143  // CloseEngine can be safely ignored.
   144  func isIgnorableOpenCloseEngineError(err error) bool {
   145  	// We allow "FileExists" error. This happens when the engine has been
   146  	// closed before. This error typically arise when resuming from a
   147  	// checkpoint with a partially-imported engine.
   148  	//
   149  	// If the error is legit in a no-checkpoints settings, the later WriteEngine
   150  	// API will bail us out to keep us safe.
   151  	return err == nil || strings.Contains(err.Error(), "FileExists")
   152  }
   153  
   154  func (importer *importer) OpenEngine(ctx context.Context, cfg *backend.EngineConfig, engineUUID uuid.UUID) error {
   155  	req := &import_kvpb.OpenEngineRequest{
   156  		Uuid: engineUUID[:],
   157  	}
   158  
   159  	_, err := importer.cli.OpenEngine(ctx, req)
   160  	if err != nil {
   161  		return errors.Trace(err)
   162  	}
   163  	if err = importer.allocateTSIfNotExists(ctx, engineUUID); err != nil {
   164  		return errors.Trace(err)
   165  	}
   166  	return nil
   167  }
   168  
   169  func (importer *importer) getEngineTS(engineUUID uuid.UUID) uint64 {
   170  	if v, ok := importer.tsMap.Load(engineUUID); ok {
   171  		return v.(uint64)
   172  	}
   173  	return 0
   174  }
   175  
   176  func (importer *importer) allocateTSIfNotExists(ctx context.Context, engineUUID uuid.UUID) error {
   177  	if importer.getEngineTS(engineUUID) > 0 {
   178  		return nil
   179  	}
   180  	ts, err := importer.getTSFunc(ctx)
   181  	if err != nil {
   182  		return errors.Trace(err)
   183  	}
   184  	importer.tsMap.LoadOrStore(engineUUID, ts)
   185  	return nil
   186  }
   187  
   188  func (importer *importer) CloseEngine(ctx context.Context, cfg *backend.EngineConfig, engineUUID uuid.UUID) error {
   189  	req := &import_kvpb.CloseEngineRequest{
   190  		Uuid: engineUUID[:],
   191  	}
   192  
   193  	_, err := importer.cli.CloseEngine(ctx, req)
   194  	if !isIgnorableOpenCloseEngineError(err) {
   195  		return errors.Trace(err)
   196  	}
   197  	return nil
   198  }
   199  
   200  func (importer *importer) Flush(_ context.Context, _ uuid.UUID) error {
   201  	return nil
   202  }
   203  
   204  func (importer *importer) ImportEngine(ctx context.Context, engineUUID uuid.UUID) error {
   205  	importer.lock.Lock()
   206  	defer importer.lock.Unlock()
   207  	req := &import_kvpb.ImportEngineRequest{
   208  		Uuid:   engineUUID[:],
   209  		PdAddr: importer.pdAddr,
   210  	}
   211  
   212  	_, err := importer.cli.ImportEngine(ctx, req)
   213  	return errors.Trace(err)
   214  }
   215  
   216  func (importer *importer) CleanupEngine(ctx context.Context, engineUUID uuid.UUID) error {
   217  	req := &import_kvpb.CleanupEngineRequest{
   218  		Uuid: engineUUID[:],
   219  	}
   220  
   221  	_, err := importer.cli.CleanupEngine(ctx, req)
   222  	if err == nil {
   223  		importer.tsMap.Delete(engineUUID)
   224  	}
   225  	return errors.Trace(err)
   226  }
   227  
   228  func (importer *importer) CollectLocalDuplicateRows(ctx context.Context, tbl table.Table) error {
   229  	panic("Unsupported Operation")
   230  }
   231  
   232  func (importer *importer) CollectRemoteDuplicateRows(ctx context.Context, tbl table.Table) error {
   233  	panic("Unsupported Operation")
   234  }
   235  
   236  func (importer *importer) WriteRows(
   237  	ctx context.Context,
   238  	engineUUID uuid.UUID,
   239  	tableName string,
   240  	_ []string,
   241  	rows kv.Rows,
   242  ) (finalErr error) {
   243  	var err error
   244  	ts := importer.getEngineTS(engineUUID)
   245  outside:
   246  	for _, r := range rows.SplitIntoChunks(importer.MaxChunkSize()) {
   247  		for i := 0; i < writeRowsMaxRetryTimes; i++ {
   248  			err = importer.WriteRowsToImporter(ctx, engineUUID, ts, r)
   249  			switch {
   250  			case err == nil:
   251  				continue outside
   252  			case common.IsRetryableError(err):
   253  				// retry next loop
   254  			default:
   255  				return err
   256  			}
   257  		}
   258  		return errors.Annotatef(err, "[%s] write rows reach max retry %d and still failed", tableName, writeRowsMaxRetryTimes)
   259  	}
   260  	return nil
   261  }
   262  
   263  func (importer *importer) WriteRowsToImporter(
   264  	ctx context.Context,
   265  	//nolint:interfacer // false positive
   266  	engineUUID uuid.UUID,
   267  	ts uint64,
   268  	rows kv.Rows,
   269  ) (finalErr error) {
   270  	kvs := kv.KvPairsFromRows(rows)
   271  	if len(kvs) == 0 {
   272  		return nil
   273  	}
   274  
   275  	wstream, err := importer.cli.WriteEngine(ctx)
   276  	if err != nil {
   277  		return errors.Trace(err)
   278  	}
   279  
   280  	logger := log.With(zap.Stringer("engineUUID", engineUUID))
   281  
   282  	defer func() {
   283  		resp, closeErr := wstream.CloseAndRecv()
   284  		if closeErr == nil && resp != nil && resp.Error != nil {
   285  			closeErr = errors.Errorf("Engine '%s' not found", resp.Error.EngineNotFound.Uuid)
   286  		}
   287  		if closeErr != nil {
   288  			if finalErr == nil {
   289  				finalErr = errors.Trace(closeErr)
   290  			} else {
   291  				// just log the close error, we need to propagate the earlier error instead
   292  				logger.Warn("close write stream failed", log.ShortError(closeErr))
   293  			}
   294  		}
   295  	}()
   296  
   297  	// Bind uuid for this write request
   298  	req := &import_kvpb.WriteEngineRequest{
   299  		Chunk: &import_kvpb.WriteEngineRequest_Head{
   300  			Head: &import_kvpb.WriteHead{
   301  				Uuid: engineUUID[:],
   302  			},
   303  		},
   304  	}
   305  	if err := wstream.Send(req); err != nil {
   306  		return errors.Trace(err)
   307  	}
   308  
   309  	// Send kv paris as write request content
   310  	mutations := make([]*import_kvpb.Mutation, len(kvs))
   311  	for i, pair := range kvs {
   312  		mutations[i] = importer.mutationPool.Get().(*import_kvpb.Mutation)
   313  		mutations[i].Op = import_kvpb.Mutation_Put
   314  		mutations[i].Key = pair.Key
   315  		mutations[i].Value = pair.Val
   316  	}
   317  
   318  	req.Reset()
   319  	req.Chunk = &import_kvpb.WriteEngineRequest_Batch{
   320  		Batch: &import_kvpb.WriteBatch{
   321  			CommitTs:  ts,
   322  			Mutations: mutations,
   323  		},
   324  	}
   325  
   326  	err = wstream.Send(req)
   327  	for _, mutation := range mutations {
   328  		importer.mutationPool.Put(mutation)
   329  	}
   330  
   331  	if err != nil {
   332  		return errors.Trace(err)
   333  	}
   334  
   335  	return nil
   336  }
   337  
   338  func (*importer) MakeEmptyRows() kv.Rows {
   339  	return kv.MakeRowsFromKvPairs(nil)
   340  }
   341  
   342  func (*importer) NewEncoder(tbl table.Table, options *kv.SessionOptions) (kv.Encoder, error) {
   343  	return kv.NewTableKVEncoder(tbl, options)
   344  }
   345  
   346  func (importer *importer) CheckRequirements(ctx context.Context, _ *backend.CheckCtx) error {
   347  	if err := checkTiDBVersionByTLS(ctx, importer.tls, requiredMinTiDBVersion, requiredMaxTiDBVersion); err != nil {
   348  		return err
   349  	}
   350  	if err := tikv.CheckPDVersion(ctx, importer.tls, importer.pdAddr, requiredMinPDVersion, requiredMaxPDVersion); err != nil {
   351  		return err
   352  	}
   353  	if err := tikv.CheckTiKVVersion(ctx, importer.tls, importer.pdAddr, requiredMinTiKVVersion, requiredMaxTiKVVersion); err != nil {
   354  		return err
   355  	}
   356  	return nil
   357  }
   358  
   359  func checkTiDBVersionByTLS(ctx context.Context, tls *common.TLS, requiredMinVersion, requiredMaxVersion semver.Version) error {
   360  	var status struct{ Version string }
   361  	err := tls.GetJSON(ctx, "/status", &status)
   362  	if err != nil {
   363  		return err
   364  	}
   365  
   366  	return version.CheckTiDBVersion(status.Version, requiredMinVersion, requiredMaxVersion)
   367  }
   368  
   369  func (importer *importer) FetchRemoteTableModels(ctx context.Context, schema string) ([]*model.TableInfo, error) {
   370  	return tikv.FetchRemoteTableModelsFromTLS(ctx, importer.tls, schema)
   371  }
   372  
   373  func (importer *importer) EngineFileSizes() []backend.EngineFileSize {
   374  	return nil
   375  }
   376  
   377  func (importer *importer) FlushEngine(context.Context, uuid.UUID) error {
   378  	return nil
   379  }
   380  
   381  func (importer *importer) FlushAllEngines(context.Context) error {
   382  	return nil
   383  }
   384  
   385  func (importer *importer) ResetEngine(context.Context, uuid.UUID) error {
   386  	return errors.New("cannot reset an engine in importer backend")
   387  }
   388  
   389  func (importer *importer) LocalWriter(_ context.Context, _ *backend.LocalWriterConfig, engineUUID uuid.UUID) (backend.EngineWriter, error) {
   390  	return &Writer{importer: importer, engineUUID: engineUUID}, nil
   391  }
   392  
   393  type Writer struct {
   394  	importer   *importer
   395  	engineUUID uuid.UUID
   396  }
   397  
   398  func (w *Writer) Close(ctx context.Context) (backend.ChunkFlushStatus, error) {
   399  	return nil, nil
   400  }
   401  
   402  func (w *Writer) AppendRows(ctx context.Context, tableName string, columnNames []string, rows kv.Rows) error {
   403  	return w.importer.WriteRows(ctx, w.engineUUID, tableName, columnNames, rows)
   404  }
   405  
   406  func (w *Writer) IsSynced() bool {
   407  	return true
   408  }