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 }