github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/causetstore/milevadb-server/einsteindb/batch_coprocessor.go (about) 1 // Copyright 2020 WHTCORPS INC, 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 einsteindb 15 16 import ( 17 "context" 18 "io" 19 "sync" 20 "sync/atomic" 21 "time" 22 23 "github.com/whtcorpsinc/ekvproto/pkg/ekvrpcpb" 24 "github.com/whtcorpsinc/ekvproto/pkg/interlock" 25 "github.com/whtcorpsinc/ekvproto/pkg/spacetimepb" 26 "github.com/whtcorpsinc/errors" 27 "github.com/whtcorpsinc/milevadb/causetstore/einsteindb/einsteindbrpc" 28 "github.com/whtcorpsinc/milevadb/ekv" 29 "github.com/whtcorpsinc/milevadb/soliton/logutil" 30 "github.com/whtcorpsinc/milevadb/soliton/memory" 31 "go.uber.org/zap" 32 ) 33 34 // batchCopTask comprises of multiple copTask that will send to same causetstore. 35 type batchCopTask struct { 36 storeAddr string 37 cmdType einsteindbrpc.CmdType 38 39 CausetTasks []copTaskAndRPCContext 40 } 41 42 type batchCopResponse struct { 43 pbResp *interlock.BatchResponse 44 detail *CopRuntimeStats 45 46 // batch Causet Response is yet to return startKey. So batchCop cannot retry partially. 47 startKey ekv.Key 48 err error 49 respSize int64 50 respTime time.Duration 51 } 52 53 // GetData implements the ekv.ResultSubset GetData interface. 54 func (rs *batchCopResponse) GetData() []byte { 55 return rs.pbResp.Data 56 } 57 58 // GetStartKey implements the ekv.ResultSubset GetStartKey interface. 59 func (rs *batchCopResponse) GetStartKey() ekv.Key { 60 return rs.startKey 61 } 62 63 // GetInterDircDetails is unavailable currently, because TiFlash has not collected exec details for batch cop. 64 // TODO: Will fix in near future. 65 func (rs *batchCopResponse) GetCopRuntimeStats() *CopRuntimeStats { 66 return rs.detail 67 } 68 69 // MemSize returns how many bytes of memory this response use 70 func (rs *batchCopResponse) MemSize() int64 { 71 if rs.respSize != 0 { 72 return rs.respSize 73 } 74 75 // ignore rs.err 76 rs.respSize += int64(cap(rs.startKey)) 77 if rs.detail != nil { 78 rs.respSize += int64(sizeofInterDircDetails) 79 } 80 if rs.pbResp != nil { 81 // Using a approximate size since it's hard to get a accurate value. 82 rs.respSize += int64(rs.pbResp.Size()) 83 } 84 return rs.respSize 85 } 86 87 func (rs *batchCopResponse) RespTime() time.Duration { 88 return rs.respTime 89 } 90 91 type copTaskAndRPCContext struct { 92 task *copTask 93 ctx *RPCContext 94 } 95 96 func buildBatchCausetTasks(bo *Backoffer, cache *RegionCache, ranges *copRanges, req *ekv.Request) ([]*batchCopTask, error) { 97 start := time.Now() 98 const cmdType = einsteindbrpc.CmdBatchCop 99 rangesLen := ranges.len() 100 for { 101 var tasks []*copTask 102 appendTask := func(regionWithRangeInfo *KeyLocation, ranges *copRanges) { 103 tasks = append(tasks, &copTask{ 104 region: regionWithRangeInfo.Region, 105 ranges: ranges, 106 cmdType: cmdType, 107 storeType: req.StoreType, 108 }) 109 } 110 111 err := splitRanges(bo, cache, ranges, appendTask) 112 if err != nil { 113 return nil, errors.Trace(err) 114 } 115 116 var batchTasks []*batchCopTask 117 118 storeTaskMap := make(map[string]*batchCopTask) 119 needRetry := false 120 for _, task := range tasks { 121 rpcCtx, err := cache.GetTiFlashRPCContext(bo, task.region) 122 if err != nil { 123 return nil, errors.Trace(err) 124 } 125 // If the region is not found in cache, it must be out 126 // of date and already be cleaned up. We should retry and generate new tasks. 127 if rpcCtx == nil { 128 needRetry = true 129 err = bo.Backoff(BoRegionMiss, errors.New("Cannot find region or TiFlash peer")) 130 logutil.BgLogger().Info("retry for TiFlash peer or region missing", zap.Uint64("region id", task.region.GetID())) 131 if err != nil { 132 return nil, errors.Trace(err) 133 } 134 break 135 } 136 if batchCop, ok := storeTaskMap[rpcCtx.Addr]; ok { 137 batchCop.CausetTasks = append(batchCop.CausetTasks, copTaskAndRPCContext{task: task, ctx: rpcCtx}) 138 } else { 139 batchTask := &batchCopTask{ 140 storeAddr: rpcCtx.Addr, 141 cmdType: cmdType, 142 CausetTasks: []copTaskAndRPCContext{{task, rpcCtx}}, 143 } 144 storeTaskMap[rpcCtx.Addr] = batchTask 145 } 146 } 147 if needRetry { 148 continue 149 } 150 for _, task := range storeTaskMap { 151 batchTasks = append(batchTasks, task) 152 } 153 154 if elapsed := time.Since(start); elapsed > time.Millisecond*500 { 155 logutil.BgLogger().Warn("buildBatchCausetTasks takes too much time", 156 zap.Duration("elapsed", elapsed), 157 zap.Int("range len", rangesLen), 158 zap.Int("task len", len(batchTasks))) 159 } 160 einsteindbTxnRegionsNumHistogramWithBatchCoprocessor.Observe(float64(len(batchTasks))) 161 return batchTasks, nil 162 } 163 } 164 165 func (c *CopClient) sendBatch(ctx context.Context, req *ekv.Request, vars *ekv.Variables) ekv.Response { 166 if req.KeepOrder || req.Desc { 167 return copErrorResponse{errors.New("batch interlock cannot prove keep order or desc property")} 168 } 169 ctx = context.WithValue(ctx, txnStartKey, req.StartTs) 170 bo := NewBackofferWithVars(ctx, copBuildTaskMaxBackoff, vars) 171 tasks, err := buildBatchCausetTasks(bo, c.causetstore.regionCache, &copRanges{mid: req.KeyRanges}, req) 172 if err != nil { 173 return copErrorResponse{err} 174 } 175 it := &batchCopIterator{ 176 causetstore: c.causetstore, 177 req: req, 178 finishCh: make(chan struct{}), 179 vars: vars, 180 memTracker: req.MemTracker, 181 clientHelper: clientHelper{ 182 LockResolver: c.causetstore.lockResolver, 183 RegionCache: c.causetstore.regionCache, 184 Client: c.causetstore.client, 185 minCommitTSPushed: &minCommitTSPushed{data: make(map[uint64]struct{}, 5)}, 186 }, 187 rpcCancel: NewRPCanceller(), 188 } 189 ctx = context.WithValue(ctx, RPCCancellerCtxKey{}, it.rpcCancel) 190 it.tasks = tasks 191 it.respChan = make(chan *batchCopResponse, 2048) 192 go it.run(ctx) 193 return it 194 } 195 196 type batchCopIterator struct { 197 clientHelper 198 199 causetstore *einsteindbStore 200 req *ekv.Request 201 finishCh chan struct{} 202 203 tasks []*batchCopTask 204 205 // Batch results are stored in respChan. 206 respChan chan *batchCopResponse 207 208 vars *ekv.Variables 209 210 memTracker *memory.Tracker 211 212 replicaReadSeed uint32 213 214 rpcCancel *RPCCanceller 215 216 wg sync.WaitGroup 217 // closed represents when the Close is called. 218 // There are two cases we need to close the `finishCh` channel, one is when context is done, the other one is 219 // when the Close is called. we use atomic.CompareAndSwap `closed` to to make sure the channel is not closed twice. 220 closed uint32 221 } 222 223 func (b *batchCopIterator) run(ctx context.Context) { 224 // We run workers for every batch cop. 225 for _, task := range b.tasks { 226 b.wg.Add(1) 227 bo := NewBackofferWithVars(ctx, copNextMaxBackoff, b.vars) 228 go b.handleTask(ctx, bo, task) 229 } 230 b.wg.Wait() 231 close(b.respChan) 232 } 233 234 // Next returns next interlock result. 235 // NOTE: Use nil to indicate finish, so if the returned ResultSubset is not nil, reader should continue to call Next(). 236 func (b *batchCopIterator) Next(ctx context.Context) (ekv.ResultSubset, error) { 237 var ( 238 resp *batchCopResponse 239 ok bool 240 closed bool 241 ) 242 243 // Get next fetched resp from chan 244 resp, ok, closed = b.recvFromRespCh(ctx) 245 if !ok || closed { 246 return nil, nil 247 } 248 249 if resp.err != nil { 250 return nil, errors.Trace(resp.err) 251 } 252 253 err := b.causetstore.CheckVisibility(b.req.StartTs) 254 if err != nil { 255 return nil, errors.Trace(err) 256 } 257 return resp, nil 258 } 259 260 func (b *batchCopIterator) recvFromRespCh(ctx context.Context) (resp *batchCopResponse, ok bool, exit bool) { 261 ticker := time.NewTicker(3 * time.Second) 262 defer ticker.Stop() 263 for { 264 select { 265 case resp, ok = <-b.respChan: 266 return 267 case <-ticker.C: 268 if atomic.LoadUint32(b.vars.Killed) == 1 { 269 resp = &batchCopResponse{err: ErrQueryInterrupted} 270 ok = true 271 return 272 } 273 case <-b.finishCh: 274 exit = true 275 return 276 case <-ctx.Done(): 277 // We select the ctx.Done() in the thread of `Next` instead of in the worker to avoid the cost of `WithCancel`. 278 if atomic.CompareAndSwapUint32(&b.closed, 0, 1) { 279 close(b.finishCh) 280 } 281 exit = true 282 return 283 } 284 } 285 } 286 287 // Close releases the resource. 288 func (b *batchCopIterator) Close() error { 289 if atomic.CompareAndSwapUint32(&b.closed, 0, 1) { 290 close(b.finishCh) 291 } 292 b.rpcCancel.CancelAll() 293 b.wg.Wait() 294 return nil 295 } 296 297 func (b *batchCopIterator) handleTask(ctx context.Context, bo *Backoffer, task *batchCopTask) { 298 logutil.BgLogger().Debug("handle batch task") 299 tasks := []*batchCopTask{task} 300 for idx := 0; idx < len(tasks); idx++ { 301 ret, err := b.handleTaskOnce(ctx, bo, tasks[idx]) 302 if err != nil { 303 resp := &batchCopResponse{err: errors.Trace(err), detail: new(CopRuntimeStats)} 304 b.sendToRespCh(resp) 305 break 306 } 307 tasks = append(tasks, ret...) 308 } 309 b.wg.Done() 310 } 311 312 // Merge all ranges and request again. 313 func (b *batchCopIterator) retryBatchCopTask(ctx context.Context, bo *Backoffer, batchTask *batchCopTask) ([]*batchCopTask, error) { 314 ranges := &copRanges{} 315 for _, taskCtx := range batchTask.CausetTasks { 316 taskCtx.task.ranges.do(func(ran *ekv.KeyRange) { 317 ranges.mid = append(ranges.mid, *ran) 318 }) 319 } 320 return buildBatchCausetTasks(bo, b.RegionCache, ranges, b.req) 321 } 322 323 func (b *batchCopIterator) handleTaskOnce(ctx context.Context, bo *Backoffer, task *batchCopTask) ([]*batchCopTask, error) { 324 logutil.BgLogger().Debug("handle batch task once") 325 sender := NewRegionBatchRequestSender(b.causetstore.regionCache, b.causetstore.client) 326 var regionInfos []*interlock.RegionInfo 327 for _, task := range task.CausetTasks { 328 regionInfos = append(regionInfos, &interlock.RegionInfo{ 329 RegionId: task.task.region.id, 330 RegionEpoch: &spacetimepb.RegionEpoch{ 331 ConfVer: task.task.region.confVer, 332 Version: task.task.region.ver, 333 }, 334 Ranges: task.task.ranges.toPBRanges(), 335 }) 336 } 337 338 copReq := interlock.BatchRequest{ 339 Tp: b.req.Tp, 340 StartTs: b.req.StartTs, 341 Data: b.req.Data, 342 SchemaVer: b.req.SchemaVar, 343 Regions: regionInfos, 344 } 345 346 req := einsteindbrpc.NewRequest(task.cmdType, &copReq, ekvrpcpb.Context{ 347 IsolationLevel: pbIsolationLevel(b.req.IsolationLevel), 348 Priority: ekvPriorityToCommandPri(b.req.Priority), 349 NotFillCache: b.req.NotFillCache, 350 HandleTime: true, 351 ScanDetail: true, 352 TaskId: b.req.TaskID, 353 }) 354 req.StoreTp = ekv.TiFlash 355 356 logutil.BgLogger().Debug("send batch request to ", zap.String("req info", req.String()), zap.Int("cop task len", len(task.CausetTasks))) 357 resp, retry, cancel, err := sender.sendStreamReqToAddr(bo, task.CausetTasks, req, ReadTimeoutUltraLong) 358 // If there are causetstore errors, we should retry for all regions. 359 if retry { 360 return b.retryBatchCopTask(ctx, bo, task) 361 } 362 if err != nil { 363 return nil, errors.Trace(err) 364 } 365 defer cancel() 366 return nil, b.handleStreamedBatchCopResponse(ctx, bo, resp.Resp.(*einsteindbrpc.BatchCopStreamResponse), task) 367 } 368 369 func (b *batchCopIterator) handleStreamedBatchCopResponse(ctx context.Context, bo *Backoffer, response *einsteindbrpc.BatchCopStreamResponse, task *batchCopTask) (err error) { 370 defer response.Close() 371 resp := response.BatchResponse 372 if resp == nil { 373 // streaming request returns io.EOF, so the first Response is nil. 374 return 375 } 376 for { 377 err = b.handleBatchCopResponse(bo, resp, task) 378 if err != nil { 379 return errors.Trace(err) 380 } 381 resp, err = response.Recv() 382 if err != nil { 383 if errors.Cause(err) == io.EOF { 384 return nil 385 } 386 387 if err1 := bo.Backoff(boEinsteinDBRPC, errors.Errorf("recv stream response error: %v, task causetstore addr: %s", err, task.storeAddr)); err1 != nil { 388 return errors.Trace(err) 389 } 390 391 // No interlock.Response for network error, rebuild task based on the last success one. 392 if errors.Cause(err) == context.Canceled { 393 logutil.BgLogger().Info("stream recv timeout", zap.Error(err)) 394 } else { 395 logutil.BgLogger().Info("stream unknown error", zap.Error(err)) 396 } 397 return errors.Trace(err) 398 } 399 } 400 } 401 402 func (b *batchCopIterator) handleBatchCopResponse(bo *Backoffer, response *interlock.BatchResponse, task *batchCopTask) (err error) { 403 if otherErr := response.GetOtherError(); otherErr != "" { 404 err = errors.Errorf("other error: %s", otherErr) 405 logutil.BgLogger().Warn("other error", 406 zap.Uint64("txnStartTS", b.req.StartTs), 407 zap.String("storeAddr", task.storeAddr), 408 zap.Error(err)) 409 return errors.Trace(err) 410 } 411 412 resp := batchCopResponse{ 413 pbResp: response, 414 detail: new(CopRuntimeStats), 415 } 416 417 resp.detail.BackoffTime = time.Duration(bo.totalSleep) * time.Millisecond 418 resp.detail.BackoffSleep = make(map[string]time.Duration, len(bo.backoffTimes)) 419 resp.detail.BackoffTimes = make(map[string]int, len(bo.backoffTimes)) 420 for backoff := range bo.backoffTimes { 421 backoffName := backoff.String() 422 resp.detail.BackoffTimes[backoffName] = bo.backoffTimes[backoff] 423 resp.detail.BackoffSleep[backoffName] = time.Duration(bo.backoffSleepMS[backoff]) * time.Millisecond 424 } 425 resp.detail.CalleeAddress = task.storeAddr 426 427 b.sendToRespCh(&resp) 428 429 return 430 } 431 432 func (b *batchCopIterator) sendToRespCh(resp *batchCopResponse) (exit bool) { 433 select { 434 case b.respChan <- resp: 435 case <-b.finishCh: 436 exit = true 437 } 438 return 439 }