github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/soliton/chunk/row_container.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 chunk 15 16 import ( 17 "errors" 18 "sort" 19 "sync" 20 "time" 21 22 "github.com/whtcorpsinc/failpoint" 23 "github.com/whtcorpsinc/milevadb/types" 24 "github.com/whtcorpsinc/milevadb/soliton/disk" 25 "github.com/whtcorpsinc/milevadb/soliton/logutil" 26 "github.com/whtcorpsinc/milevadb/soliton/memory" 27 "go.uber.org/zap" 28 ) 29 30 // RowContainer provides a place for many rows, so many that we might want to spill them into disk. 31 type RowContainer struct { 32 m struct { 33 // RWMutex guarantees spill and get operator for rowContainer is mutually exclusive. 34 sync.RWMutex 35 // records stores the chunks in memory. 36 records *List 37 // recordsInDisk stores the chunks in disk. 38 recordsInDisk *ListInDisk 39 // spillError stores the error when spilling. 40 spillError error 41 } 42 43 fieldType []*types.FieldType 44 chunkSize int 45 numRow int 46 47 memTracker *memory.Tracker 48 diskTracker *disk.Tracker 49 actionSpill *SpillDiskCausetAction 50 } 51 52 // NewRowContainer creates a new RowContainer in memory. 53 func NewRowContainer(fieldType []*types.FieldType, chunkSize int) *RowContainer { 54 li := NewList(fieldType, chunkSize, chunkSize) 55 rc := &RowContainer{fieldType: fieldType, chunkSize: chunkSize} 56 rc.m.records = li 57 rc.memTracker = li.memTracker 58 rc.diskTracker = disk.NewTracker(memory.LabelForRowContainer, -1) 59 return rc 60 } 61 62 // SpillToDisk spills data to disk. This function may be called in parallel. 63 func (c *RowContainer) SpillToDisk() { 64 c.m.Lock() 65 defer c.m.Unlock() 66 if c.alreadySpilled() { 67 return 68 } 69 // c.actionSpill may be nil when testing SpillToDisk directly. 70 if c.actionSpill != nil { 71 if c.actionSpill.getStatus() == spilledYet { 72 // The rowContainer has been closed. 73 return 74 } 75 c.actionSpill.setStatus(spilling) 76 defer c.actionSpill.cond.Broadcast() 77 defer c.actionSpill.setStatus(spilledYet) 78 } 79 var err error 80 N := c.m.records.NumChunks() 81 c.m.recordsInDisk = NewListInDisk(c.m.records.FieldTypes()) 82 c.m.recordsInDisk.diskTracker.AttachTo(c.diskTracker) 83 for i := 0; i < N; i++ { 84 chk := c.m.records.GetChunk(i) 85 err = c.m.recordsInDisk.Add(chk) 86 if err != nil { 87 c.m.spillError = err 88 return 89 } 90 } 91 c.m.records.Clear() 92 return 93 } 94 95 // Reset resets RowContainer. 96 func (c *RowContainer) Reset() error { 97 c.m.Lock() 98 defer c.m.Unlock() 99 if c.alreadySpilled() { 100 err := c.m.recordsInDisk.Close() 101 c.m.recordsInDisk = nil 102 if err != nil { 103 return err 104 } 105 c.actionSpill.Reset() 106 } else { 107 c.m.records.Reset() 108 } 109 return nil 110 } 111 112 // alreadySpilled indicates that records have spilled out into disk. 113 func (c *RowContainer) alreadySpilled() bool { 114 return c.m.recordsInDisk != nil 115 } 116 117 // AlreadySpilledSafeForTest indicates that records have spilled out into disk. It's thread-safe. 118 // The function is only used for test. 119 func (c *RowContainer) AlreadySpilledSafeForTest() bool { 120 c.m.RLock() 121 defer c.m.RUnlock() 122 return c.m.recordsInDisk != nil 123 } 124 125 // NumRow returns the number of rows in the container 126 func (c *RowContainer) NumRow() int { 127 c.m.RLock() 128 defer c.m.RUnlock() 129 if c.alreadySpilled() { 130 return c.m.recordsInDisk.Len() 131 } 132 return c.m.records.Len() 133 } 134 135 // NumRowsOfChunk returns the number of rows of a chunk in the ListInDisk. 136 func (c *RowContainer) NumRowsOfChunk(chkID int) int { 137 c.m.RLock() 138 defer c.m.RUnlock() 139 if c.alreadySpilled() { 140 return c.m.recordsInDisk.NumRowsOfChunk(chkID) 141 } 142 return c.m.records.NumRowsOfChunk(chkID) 143 } 144 145 // NumChunks returns the number of chunks in the container. 146 func (c *RowContainer) NumChunks() int { 147 c.m.RLock() 148 defer c.m.RUnlock() 149 if c.alreadySpilled() { 150 return c.m.recordsInDisk.NumChunks() 151 } 152 return c.m.records.NumChunks() 153 } 154 155 // Add appends a chunk into the RowContainer. 156 func (c *RowContainer) Add(chk *Chunk) (err error) { 157 c.m.RLock() 158 defer c.m.RUnlock() 159 failpoint.Inject("testRowContainerDeadLock", func(val failpoint.Value) { 160 if val.(bool) { 161 time.Sleep(time.Second) 162 } 163 }) 164 if c.alreadySpilled() { 165 if c.m.spillError != nil { 166 return c.m.spillError 167 } 168 err = c.m.recordsInDisk.Add(chk) 169 } else { 170 c.m.records.Add(chk) 171 } 172 return 173 } 174 175 // AllocChunk allocates a new chunk from RowContainer. 176 func (c *RowContainer) AllocChunk() (chk *Chunk) { 177 return c.m.records.allocChunk() 178 } 179 180 // GetChunk returns chkIdx th chunk of in memory records. 181 func (c *RowContainer) GetChunk(chkIdx int) (*Chunk, error) { 182 c.m.RLock() 183 defer c.m.RUnlock() 184 if !c.alreadySpilled() { 185 return c.m.records.GetChunk(chkIdx), nil 186 } 187 if c.m.spillError != nil { 188 return nil, c.m.spillError 189 } 190 return c.m.recordsInDisk.GetChunk(chkIdx) 191 } 192 193 // GetRow returns the event the ptr pointed to. 194 func (c *RowContainer) GetRow(ptr RowPtr) (Row, error) { 195 c.m.RLock() 196 defer c.m.RUnlock() 197 if c.alreadySpilled() { 198 if c.m.spillError != nil { 199 return Row{}, c.m.spillError 200 } 201 return c.m.recordsInDisk.GetRow(ptr) 202 } 203 return c.m.records.GetRow(ptr), nil 204 } 205 206 // GetMemTracker returns the memory tracker in records, panics if the RowContainer has already spilled. 207 func (c *RowContainer) GetMemTracker() *memory.Tracker { 208 return c.memTracker 209 } 210 211 // GetDiskTracker returns the underlying disk usage tracker in recordsInDisk. 212 func (c *RowContainer) GetDiskTracker() *disk.Tracker { 213 return c.diskTracker 214 } 215 216 // Close close the RowContainer 217 func (c *RowContainer) Close() (err error) { 218 c.m.RLock() 219 defer c.m.RUnlock() 220 if c.actionSpill != nil { 221 // Set status to spilledYet to avoid spilling. 222 c.actionSpill.setStatus(spilledYet) 223 c.actionSpill.cond.Broadcast() 224 } 225 if c.alreadySpilled() { 226 err = c.m.recordsInDisk.Close() 227 c.m.recordsInDisk = nil 228 } 229 c.m.records.Clear() 230 return 231 } 232 233 // CausetActionSpill returns a SpillDiskCausetAction for spilling over to disk. 234 func (c *RowContainer) CausetActionSpill() *SpillDiskCausetAction { 235 if c.actionSpill == nil { 236 c.actionSpill = &SpillDiskCausetAction{ 237 c: c, 238 cond: spillStatusCond{sync.NewCond(new(sync.Mutex)), notSpilled}} 239 } 240 return c.actionSpill 241 } 242 243 // CausetActionSpillForTest returns a SpillDiskCausetAction for spilling over to disk for test. 244 func (c *RowContainer) CausetActionSpillForTest() *SpillDiskCausetAction { 245 c.actionSpill = &SpillDiskCausetAction{ 246 c: c, 247 testSyncInputFunc: func() { 248 c.actionSpill.testWg.Add(1) 249 }, 250 testSyncOutputFunc: func() { 251 c.actionSpill.testWg.Done() 252 }, 253 cond: spillStatusCond{sync.NewCond(new(sync.Mutex)), notSpilled}, 254 } 255 return c.actionSpill 256 } 257 258 // SpillDiskCausetAction implements memory.SuperCowOrNoCausetOnExceed for chunk.List. If 259 // the memory quota of a query is exceeded, SpillDiskCausetAction.CausetAction is 260 // triggered. 261 type SpillDiskCausetAction struct { 262 c *RowContainer 263 fallbackCausetAction memory.SuperCowOrNoCausetOnExceed 264 m sync.Mutex 265 once sync.Once 266 cond spillStatusCond 267 268 // test function only used for test sync. 269 testSyncInputFunc func() 270 testSyncOutputFunc func() 271 testWg sync.WaitGroup 272 } 273 274 type spillStatusCond struct { 275 *sync.Cond 276 // status indicates different stages for the CausetAction 277 // notSpilled indicates the rowContainer is not spilled. 278 // spilling indicates the rowContainer is spilling. 279 // spilledYet indicates thr rowContainer is spilled. 280 status spillStatus 281 } 282 283 type spillStatus uint32 284 285 const ( 286 notSpilled spillStatus = iota 287 spilling 288 spilledYet 289 ) 290 291 func (a *SpillDiskCausetAction) setStatus(status spillStatus) { 292 a.cond.L.Lock() 293 defer a.cond.L.Unlock() 294 a.cond.status = status 295 } 296 297 func (a *SpillDiskCausetAction) getStatus() spillStatus { 298 a.cond.L.Lock() 299 defer a.cond.L.Unlock() 300 return a.cond.status 301 } 302 303 // CausetAction sends a signal to trigger spillToDisk method of RowContainer 304 // and if it is already triggered before, call its fallbackCausetAction. 305 func (a *SpillDiskCausetAction) CausetAction(t *memory.Tracker) { 306 a.m.Lock() 307 defer a.m.Unlock() 308 309 if a.getStatus() == notSpilled { 310 a.once.Do(func() { 311 logutil.BgLogger().Info("memory exceeds quota, spill to disk now.", 312 zap.Int64("consumed", t.BytesConsumed()), zap.Int64("quota", t.GetBytesLimit())) 313 if a.testSyncInputFunc != nil { 314 a.testSyncInputFunc() 315 c := a.c 316 go func() { 317 c.SpillToDisk() 318 a.testSyncOutputFunc() 319 }() 320 return 321 } 322 go a.c.SpillToDisk() 323 }) 324 return 325 } 326 327 a.cond.L.Lock() 328 for a.cond.status == spilling { 329 a.cond.Wait() 330 } 331 a.cond.L.Unlock() 332 333 if !t.CheckExceed() { 334 return 335 } 336 if a.fallbackCausetAction != nil { 337 a.fallbackCausetAction.CausetAction(t) 338 } 339 } 340 341 // Reset resets the status for SpillDiskCausetAction. 342 func (a *SpillDiskCausetAction) Reset() { 343 a.m.Lock() 344 defer a.m.Unlock() 345 a.setStatus(notSpilled) 346 a.once = sync.Once{} 347 } 348 349 // SetFallback sets the fallback action. 350 func (a *SpillDiskCausetAction) SetFallback(fallback memory.SuperCowOrNoCausetOnExceed) { 351 a.fallbackCausetAction = fallback 352 } 353 354 // SetLogHook sets the hook, it does nothing just to form the memory.SuperCowOrNoCausetOnExceed interface. 355 func (a *SpillDiskCausetAction) SetLogHook(hook func(uint64)) {} 356 357 // WaitForTest waits all goroutine have gone. 358 func (a *SpillDiskCausetAction) WaitForTest() { 359 a.testWg.Wait() 360 } 361 362 // ErrCannotAddBecauseSorted indicate that the SortedRowContainer is sorted and prohibit inserting data. 363 var ErrCannotAddBecauseSorted = errors.New("can not add because sorted") 364 365 // SortedRowContainer provides a place for many rows, so many that we might want to sort and spill them into disk. 366 type SortedRowContainer struct { 367 *RowContainer 368 ptrM struct { 369 sync.RWMutex 370 // rowPtrs causetstore the chunk index and event index for each event. 371 // rowPtrs != nil indicates the pointer is initialized and sorted. 372 // It will get an ErrCannotAddBecauseSorted when trying to insert data if rowPtrs != nil. 373 rowPtrs []RowPtr 374 } 375 376 ByItemsDesc []bool 377 // keyDeferredCausets is the defCausumn index of the by items. 378 keyDeferredCausets []int 379 // keyCmpFuncs is used to compare each ByItem. 380 keyCmpFuncs []CompareFunc 381 382 actionSpill *SortAndSpillDiskCausetAction 383 } 384 385 // NewSortedRowContainer creates a new SortedRowContainer in memory. 386 func NewSortedRowContainer(fieldType []*types.FieldType, chunkSize int, ByItemsDesc []bool, 387 keyDeferredCausets []int, keyCmpFuncs []CompareFunc) *SortedRowContainer { 388 return &SortedRowContainer{RowContainer: NewRowContainer(fieldType, chunkSize), 389 ByItemsDesc: ByItemsDesc, keyDeferredCausets: keyDeferredCausets, keyCmpFuncs: keyCmpFuncs} 390 } 391 392 // Close close the SortedRowContainer 393 func (c *SortedRowContainer) Close() error { 394 c.ptrM.Lock() 395 defer c.ptrM.Unlock() 396 c.GetMemTracker().Consume(int64(-8 * cap(c.ptrM.rowPtrs))) 397 c.ptrM.rowPtrs = nil 398 return c.RowContainer.Close() 399 } 400 401 func (c *SortedRowContainer) lessRow(rowI, rowJ Row) bool { 402 for i, defCausIdx := range c.keyDeferredCausets { 403 cmpFunc := c.keyCmpFuncs[i] 404 cmp := cmpFunc(rowI, defCausIdx, rowJ, defCausIdx) 405 if c.ByItemsDesc[i] { 406 cmp = -cmp 407 } 408 if cmp < 0 { 409 return true 410 } else if cmp > 0 { 411 return false 412 } 413 } 414 return false 415 } 416 417 // keyDeferredCausetsLess is the less function for key defCausumns. 418 func (c *SortedRowContainer) keyDeferredCausetsLess(i, j int) bool { 419 rowI := c.m.records.GetRow(c.ptrM.rowPtrs[i]) 420 rowJ := c.m.records.GetRow(c.ptrM.rowPtrs[j]) 421 return c.lessRow(rowI, rowJ) 422 } 423 424 // Sort inits pointers and sorts the records. 425 func (c *SortedRowContainer) Sort() { 426 c.ptrM.Lock() 427 defer c.ptrM.Unlock() 428 if c.ptrM.rowPtrs != nil { 429 return 430 } 431 c.ptrM.rowPtrs = make([]RowPtr, 0, c.NumRow()) 432 for chkIdx := 0; chkIdx < c.NumChunks(); chkIdx++ { 433 rowChk, err := c.GetChunk(chkIdx) 434 // err must be nil, because the chunk is in memory. 435 if err != nil { 436 panic(err) 437 } 438 for rowIdx := 0; rowIdx < rowChk.NumRows(); rowIdx++ { 439 c.ptrM.rowPtrs = append(c.ptrM.rowPtrs, RowPtr{ChkIdx: uint32(chkIdx), RowIdx: uint32(rowIdx)}) 440 } 441 } 442 sort.Slice(c.ptrM.rowPtrs, c.keyDeferredCausetsLess) 443 c.GetMemTracker().Consume(int64(8 * c.numRow)) 444 } 445 446 func (c *SortedRowContainer) sortAndSpillToDisk() { 447 c.Sort() 448 c.RowContainer.SpillToDisk() 449 return 450 } 451 452 // Add appends a chunk into the SortedRowContainer. 453 func (c *SortedRowContainer) Add(chk *Chunk) (err error) { 454 c.ptrM.RLock() 455 defer c.ptrM.RUnlock() 456 if c.ptrM.rowPtrs != nil { 457 return ErrCannotAddBecauseSorted 458 } 459 return c.RowContainer.Add(chk) 460 } 461 462 // GetSortedRow returns the event the idx pointed to. 463 func (c *SortedRowContainer) GetSortedRow(idx int) (Row, error) { 464 c.ptrM.RLock() 465 defer c.ptrM.RUnlock() 466 ptr := c.ptrM.rowPtrs[idx] 467 return c.RowContainer.GetRow(ptr) 468 } 469 470 // CausetActionSpill returns a SortAndSpillDiskCausetAction for sorting and spilling over to disk. 471 func (c *SortedRowContainer) CausetActionSpill() *SortAndSpillDiskCausetAction { 472 if c.actionSpill == nil { 473 c.actionSpill = &SortAndSpillDiskCausetAction{ 474 c: c, 475 SpillDiskCausetAction: c.RowContainer.CausetActionSpill(), 476 } 477 } 478 return c.actionSpill 479 } 480 481 // CausetActionSpillForTest returns a SortAndSpillDiskCausetAction for sorting and spilling over to disk for test. 482 func (c *SortedRowContainer) CausetActionSpillForTest() *SortAndSpillDiskCausetAction { 483 c.actionSpill = &SortAndSpillDiskCausetAction{ 484 c: c, 485 SpillDiskCausetAction: c.RowContainer.CausetActionSpillForTest(), 486 } 487 return c.actionSpill 488 } 489 490 // SortAndSpillDiskCausetAction implements memory.SuperCowOrNoCausetOnExceed for chunk.List. If 491 // the memory quota of a query is exceeded, SortAndSpillDiskCausetAction.CausetAction is 492 // triggered. 493 type SortAndSpillDiskCausetAction struct { 494 c *SortedRowContainer 495 *SpillDiskCausetAction 496 } 497 498 // CausetAction sends a signal to trigger sortAndSpillToDisk method of RowContainer 499 // and if it is already triggered before, call its fallbackCausetAction. 500 func (a *SortAndSpillDiskCausetAction) CausetAction(t *memory.Tracker) { 501 a.m.Lock() 502 defer a.m.Unlock() 503 // Guarantee that each partition size is at least 10% of the threshold, to avoid opening too many files. 504 if a.getStatus() == notSpilled && a.c.GetMemTracker().BytesConsumed() > t.GetBytesLimit()/10 { 505 a.once.Do(func() { 506 logutil.BgLogger().Info("memory exceeds quota, spill to disk now.", 507 zap.Int64("consumed", t.BytesConsumed()), zap.Int64("quota", t.GetBytesLimit())) 508 if a.testSyncInputFunc != nil { 509 a.testSyncInputFunc() 510 c := a.c 511 go func() { 512 c.sortAndSpillToDisk() 513 a.testSyncOutputFunc() 514 }() 515 return 516 } 517 go a.c.sortAndSpillToDisk() 518 }) 519 return 520 } 521 522 a.cond.L.Lock() 523 for a.cond.status == spilling { 524 a.cond.Wait() 525 } 526 a.cond.L.Unlock() 527 528 if !t.CheckExceed() { 529 return 530 } 531 if a.fallbackCausetAction != nil { 532 a.fallbackCausetAction.CausetAction(t) 533 } 534 } 535 536 // SetFallback sets the fallback action. 537 func (a *SortAndSpillDiskCausetAction) SetFallback(fallback memory.SuperCowOrNoCausetOnExceed) { 538 a.fallbackCausetAction = fallback 539 } 540 541 // SetLogHook sets the hook, it does nothing just to form the memory.SuperCowOrNoCausetOnExceed interface. 542 func (a *SortAndSpillDiskCausetAction) SetLogHook(hook func(uint64)) {} 543 544 // WaitForTest waits all goroutine have gone. 545 func (a *SortAndSpillDiskCausetAction) WaitForTest() { 546 a.testWg.Wait() 547 }