github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/interlock/sort.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 interlock 15 16 import ( 17 "container/heap" 18 "context" 19 "errors" 20 "sort" 21 22 "github.com/whtcorpsinc/failpoint" 23 causetembedded "github.com/whtcorpsinc/milevadb/causet/embedded" 24 "github.com/whtcorpsinc/milevadb/causet/soliton" 25 "github.com/whtcorpsinc/milevadb/config" 26 "github.com/whtcorpsinc/milevadb/memex" 27 "github.com/whtcorpsinc/milevadb/soliton/chunk" 28 "github.com/whtcorpsinc/milevadb/soliton/disk" 29 "github.com/whtcorpsinc/milevadb/soliton/memory" 30 "github.com/whtcorpsinc/milevadb/types" 31 ) 32 33 // SortInterDirc represents sorting interlock. 34 type SortInterDirc struct { 35 baseInterlockingDirectorate 36 37 ByItems []*soliton.ByItems 38 Idx int 39 fetched bool 40 schemaReplicant *memex.Schema 41 42 keyExprs []memex.Expression 43 keyTypes []*types.FieldType 44 // keyDeferredCausets is the defCausumn index of the by items. 45 keyDeferredCausets []int 46 // keyCmpFuncs is used to compare each ByItem. 47 keyCmpFuncs []chunk.CompareFunc 48 // rowChunks is the chunks to causetstore event values. 49 rowChunks *chunk.SortedEventContainer 50 51 memTracker *memory.Tracker 52 diskTracker *disk.Tracker 53 54 // partitionList is the chunks to causetstore event values for partitions. Every partition is a sorted list. 55 partitionList []*chunk.SortedEventContainer 56 57 // multiWayMerge uses multi-way merge for spill disk. 58 // The multi-way merge algorithm can refer to https://en.wikipedia.org/wiki/K-way_merge_algorithm 59 multiWayMerge *multiWayMerge 60 // spillCausetAction save the CausetAction for spill disk. 61 spillCausetAction *chunk.SortAndSpillDiskCausetAction 62 } 63 64 // Close implements the InterlockingDirectorate Close interface. 65 func (e *SortInterDirc) Close() error { 66 for _, container := range e.partitionList { 67 err := container.Close() 68 if err != nil { 69 return err 70 } 71 } 72 e.partitionList = e.partitionList[:0] 73 74 if e.rowChunks != nil { 75 e.memTracker.Consume(-e.rowChunks.GetMemTracker().BytesConsumed()) 76 e.rowChunks = nil 77 } 78 e.memTracker = nil 79 e.diskTracker = nil 80 e.multiWayMerge = nil 81 e.spillCausetAction = nil 82 return e.children[0].Close() 83 } 84 85 // Open implements the InterlockingDirectorate Open interface. 86 func (e *SortInterDirc) Open(ctx context.Context) error { 87 e.fetched = false 88 e.Idx = 0 89 90 // To avoid duplicated initialization for TopNInterDirc. 91 if e.memTracker == nil { 92 e.memTracker = memory.NewTracker(e.id, -1) 93 e.memTracker.AttachTo(e.ctx.GetStochastikVars().StmtCtx.MemTracker) 94 e.diskTracker = memory.NewTracker(e.id, -1) 95 e.diskTracker.AttachTo(e.ctx.GetStochastikVars().StmtCtx.DiskTracker) 96 } 97 e.partitionList = e.partitionList[:0] 98 return e.children[0].Open(ctx) 99 } 100 101 // Next implements the InterlockingDirectorate Next interface. 102 // Sort constructs the result following these step: 103 // 1. Read as mush as rows into memory. 104 // 2. If memory quota is triggered, sort these rows in memory and put them into disk as partition 1, then reset 105 // the memory quota trigger and return to step 1 106 // 3. If memory quota is not triggered and child is consumed, sort these rows in memory as partition N. 107 // 4. Merge sort if the count of partitions is larger than 1. If there is only one partition in step 4, it works 108 // just like in-memory sort before. 109 func (e *SortInterDirc) Next(ctx context.Context, req *chunk.Chunk) error { 110 req.Reset() 111 if !e.fetched { 112 e.initCompareFuncs() 113 e.buildKeyDeferredCausets() 114 err := e.fetchEventChunks(ctx) 115 if err != nil { 116 return err 117 } 118 e.fetched = true 119 } 120 121 if len(e.partitionList) == 0 { 122 return nil 123 } 124 if len(e.partitionList) > 1 { 125 if err := e.externalSorting(req); err != nil { 126 return err 127 } 128 } else { 129 for !req.IsFull() && e.Idx < e.partitionList[0].NumEvent() { 130 event, err := e.partitionList[0].GetSortedEvent(e.Idx) 131 if err != nil { 132 return err 133 } 134 req.AppendEvent(event) 135 e.Idx++ 136 } 137 } 138 return nil 139 } 140 141 type partitionPointer struct { 142 event chunk.Event 143 partitionID int 144 consumed int 145 } 146 147 type multiWayMerge struct { 148 lessEventFunction func(rowI chunk.Event, rowJ chunk.Event) bool 149 elements []partitionPointer 150 } 151 152 func (h *multiWayMerge) Less(i, j int) bool { 153 rowI := h.elements[i].event 154 rowJ := h.elements[j].event 155 return h.lessEventFunction(rowI, rowJ) 156 } 157 158 func (h *multiWayMerge) Len() int { 159 return len(h.elements) 160 } 161 162 func (h *multiWayMerge) Push(x interface{}) { 163 // Should never be called. 164 } 165 166 func (h *multiWayMerge) Pop() interface{} { 167 h.elements = h.elements[:len(h.elements)-1] 168 return nil 169 } 170 171 func (h *multiWayMerge) Swap(i, j int) { 172 h.elements[i], h.elements[j] = h.elements[j], h.elements[i] 173 } 174 175 func (e *SortInterDirc) externalSorting(req *chunk.Chunk) (err error) { 176 if e.multiWayMerge == nil { 177 e.multiWayMerge = &multiWayMerge{e.lessEvent, make([]partitionPointer, 0, len(e.partitionList))} 178 for i := 0; i < len(e.partitionList); i++ { 179 event, err := e.partitionList[i].GetSortedEvent(0) 180 if err != nil { 181 return err 182 } 183 e.multiWayMerge.elements = append(e.multiWayMerge.elements, partitionPointer{event: event, partitionID: i, consumed: 0}) 184 } 185 heap.Init(e.multiWayMerge) 186 } 187 188 for !req.IsFull() && e.multiWayMerge.Len() > 0 { 189 partitionPtr := e.multiWayMerge.elements[0] 190 req.AppendEvent(partitionPtr.event) 191 partitionPtr.consumed++ 192 if partitionPtr.consumed >= e.partitionList[partitionPtr.partitionID].NumEvent() { 193 heap.Remove(e.multiWayMerge, 0) 194 continue 195 } 196 partitionPtr.event, err = e.partitionList[partitionPtr.partitionID]. 197 GetSortedEvent(partitionPtr.consumed) 198 if err != nil { 199 return err 200 } 201 e.multiWayMerge.elements[0] = partitionPtr 202 heap.Fix(e.multiWayMerge, 0) 203 } 204 return nil 205 } 206 207 func (e *SortInterDirc) fetchEventChunks(ctx context.Context) error { 208 fields := retTypes(e) 209 byItemsDesc := make([]bool, len(e.ByItems)) 210 for i, byItem := range e.ByItems { 211 byItemsDesc[i] = byItem.Desc 212 } 213 e.rowChunks = chunk.NewSortedEventContainer(fields, e.maxChunkSize, byItemsDesc, e.keyDeferredCausets, e.keyCmpFuncs) 214 e.rowChunks.GetMemTracker().AttachTo(e.memTracker) 215 e.rowChunks.GetMemTracker().SetLabel(memory.LabelForEventChunks) 216 if config.GetGlobalConfig().OOMUseTmpStorage { 217 e.spillCausetAction = e.rowChunks.CausetActionSpill() 218 failpoint.Inject("testSortedEventContainerSpill", func(val failpoint.Value) { 219 if val.(bool) { 220 e.spillCausetAction = e.rowChunks.CausetActionSpillForTest() 221 defer e.spillCausetAction.WaitForTest() 222 } 223 }) 224 e.ctx.GetStochastikVars().StmtCtx.MemTracker.FallbackOldAndSetNewCausetAction(e.spillCausetAction) 225 e.rowChunks.GetDiskTracker().AttachTo(e.diskTracker) 226 e.rowChunks.GetDiskTracker().SetLabel(memory.LabelForEventChunks) 227 } 228 for { 229 chk := newFirstChunk(e.children[0]) 230 err := Next(ctx, e.children[0], chk) 231 if err != nil { 232 return err 233 } 234 rowCount := chk.NumEvents() 235 if rowCount == 0 { 236 break 237 } 238 if err := e.rowChunks.Add(chk); err != nil { 239 if errors.Is(err, chunk.ErrCannotAddBecauseSorted) { 240 e.partitionList = append(e.partitionList, e.rowChunks) 241 e.rowChunks = chunk.NewSortedEventContainer(fields, e.maxChunkSize, byItemsDesc, e.keyDeferredCausets, e.keyCmpFuncs) 242 e.rowChunks.GetMemTracker().AttachTo(e.memTracker) 243 e.rowChunks.GetMemTracker().SetLabel(memory.LabelForEventChunks) 244 e.rowChunks.GetDiskTracker().AttachTo(e.diskTracker) 245 e.rowChunks.GetDiskTracker().SetLabel(memory.LabelForEventChunks) 246 e.spillCausetAction = e.rowChunks.CausetActionSpill() 247 failpoint.Inject("testSortedEventContainerSpill", func(val failpoint.Value) { 248 if val.(bool) { 249 e.spillCausetAction = e.rowChunks.CausetActionSpillForTest() 250 defer e.spillCausetAction.WaitForTest() 251 } 252 }) 253 e.ctx.GetStochastikVars().StmtCtx.MemTracker.FallbackOldAndSetNewCausetAction(e.spillCausetAction) 254 err = e.rowChunks.Add(chk) 255 } 256 if err != nil { 257 return err 258 } 259 } 260 } 261 if e.rowChunks.NumEvent() > 0 { 262 e.rowChunks.Sort() 263 e.partitionList = append(e.partitionList, e.rowChunks) 264 } 265 return nil 266 } 267 268 func (e *SortInterDirc) initCompareFuncs() { 269 e.keyCmpFuncs = make([]chunk.CompareFunc, len(e.ByItems)) 270 for i := range e.ByItems { 271 keyType := e.ByItems[i].Expr.GetType() 272 e.keyCmpFuncs[i] = chunk.GetCompareFunc(keyType) 273 } 274 } 275 276 func (e *SortInterDirc) buildKeyDeferredCausets() { 277 e.keyDeferredCausets = make([]int, 0, len(e.ByItems)) 278 for _, by := range e.ByItems { 279 defCaus := by.Expr.(*memex.DeferredCauset) 280 e.keyDeferredCausets = append(e.keyDeferredCausets, defCaus.Index) 281 } 282 } 283 284 func (e *SortInterDirc) lessEvent(rowI, rowJ chunk.Event) bool { 285 for i, defCausIdx := range e.keyDeferredCausets { 286 cmpFunc := e.keyCmpFuncs[i] 287 cmp := cmpFunc(rowI, defCausIdx, rowJ, defCausIdx) 288 if e.ByItems[i].Desc { 289 cmp = -cmp 290 } 291 if cmp < 0 { 292 return true 293 } else if cmp > 0 { 294 return false 295 } 296 } 297 return false 298 } 299 300 // TopNInterDirc implements a Top-N algorithm and it is built from a SELECT memex with ORDER BY and LIMIT. 301 // Instead of sorting all the rows fetched from the causet, it keeps the Top-N elements only in a heap to reduce memory usage. 302 type TopNInterDirc struct { 303 SortInterDirc 304 limit *causetembedded.PhysicalLimit 305 totalLimit uint64 306 307 // rowChunks is the chunks to causetstore event values. 308 rowChunks *chunk.List 309 // rowPointer causetstore the chunk index and event index for each event. 310 rowPtrs []chunk.EventPtr 311 312 chkHeap *topNChunkHeap 313 } 314 315 // topNChunkHeap implements heap.Interface. 316 type topNChunkHeap struct { 317 *TopNInterDirc 318 } 319 320 // Less implement heap.Interface, but since we mantains a max heap, 321 // this function returns true if event i is greater than event j. 322 func (h *topNChunkHeap) Less(i, j int) bool { 323 rowI := h.rowChunks.GetEvent(h.rowPtrs[i]) 324 rowJ := h.rowChunks.GetEvent(h.rowPtrs[j]) 325 return h.greaterEvent(rowI, rowJ) 326 } 327 328 func (h *topNChunkHeap) greaterEvent(rowI, rowJ chunk.Event) bool { 329 for i, defCausIdx := range h.keyDeferredCausets { 330 cmpFunc := h.keyCmpFuncs[i] 331 cmp := cmpFunc(rowI, defCausIdx, rowJ, defCausIdx) 332 if h.ByItems[i].Desc { 333 cmp = -cmp 334 } 335 if cmp > 0 { 336 return true 337 } else if cmp < 0 { 338 return false 339 } 340 } 341 return false 342 } 343 344 func (h *topNChunkHeap) Len() int { 345 return len(h.rowPtrs) 346 } 347 348 func (h *topNChunkHeap) Push(x interface{}) { 349 // Should never be called. 350 } 351 352 func (h *topNChunkHeap) Pop() interface{} { 353 h.rowPtrs = h.rowPtrs[:len(h.rowPtrs)-1] 354 // We don't need the popped value, return nil to avoid memory allocation. 355 return nil 356 } 357 358 func (h *topNChunkHeap) Swap(i, j int) { 359 h.rowPtrs[i], h.rowPtrs[j] = h.rowPtrs[j], h.rowPtrs[i] 360 } 361 362 // keyDeferredCausetsLess is the less function for key defCausumns. 363 func (e *TopNInterDirc) keyDeferredCausetsLess(i, j int) bool { 364 rowI := e.rowChunks.GetEvent(e.rowPtrs[i]) 365 rowJ := e.rowChunks.GetEvent(e.rowPtrs[j]) 366 return e.lessEvent(rowI, rowJ) 367 } 368 369 func (e *TopNInterDirc) initPointers() { 370 e.rowPtrs = make([]chunk.EventPtr, 0, e.rowChunks.Len()) 371 e.memTracker.Consume(int64(8 * e.rowChunks.Len())) 372 for chkIdx := 0; chkIdx < e.rowChunks.NumChunks(); chkIdx++ { 373 rowChk := e.rowChunks.GetChunk(chkIdx) 374 for rowIdx := 0; rowIdx < rowChk.NumEvents(); rowIdx++ { 375 e.rowPtrs = append(e.rowPtrs, chunk.EventPtr{ChkIdx: uint32(chkIdx), EventIdx: uint32(rowIdx)}) 376 } 377 } 378 } 379 380 // Open implements the InterlockingDirectorate Open interface. 381 func (e *TopNInterDirc) Open(ctx context.Context) error { 382 e.memTracker = memory.NewTracker(e.id, -1) 383 e.memTracker.AttachTo(e.ctx.GetStochastikVars().StmtCtx.MemTracker) 384 385 e.fetched = false 386 e.Idx = 0 387 388 return e.children[0].Open(ctx) 389 } 390 391 // Next implements the InterlockingDirectorate Next interface. 392 func (e *TopNInterDirc) Next(ctx context.Context, req *chunk.Chunk) error { 393 req.Reset() 394 if !e.fetched { 395 e.totalLimit = e.limit.Offset + e.limit.Count 396 e.Idx = int(e.limit.Offset) 397 err := e.loadChunksUntilTotalLimit(ctx) 398 if err != nil { 399 return err 400 } 401 err = e.executeTopN(ctx) 402 if err != nil { 403 return err 404 } 405 e.fetched = true 406 } 407 if e.Idx >= len(e.rowPtrs) { 408 return nil 409 } 410 for !req.IsFull() && e.Idx < len(e.rowPtrs) { 411 event := e.rowChunks.GetEvent(e.rowPtrs[e.Idx]) 412 req.AppendEvent(event) 413 e.Idx++ 414 } 415 return nil 416 } 417 418 func (e *TopNInterDirc) loadChunksUntilTotalLimit(ctx context.Context) error { 419 e.chkHeap = &topNChunkHeap{e} 420 e.rowChunks = chunk.NewList(retTypes(e), e.initCap, e.maxChunkSize) 421 e.rowChunks.GetMemTracker().AttachTo(e.memTracker) 422 e.rowChunks.GetMemTracker().SetLabel(memory.LabelForEventChunks) 423 for uint64(e.rowChunks.Len()) < e.totalLimit { 424 srcChk := newFirstChunk(e.children[0]) 425 // adjust required rows by total limit 426 srcChk.SetRequiredEvents(int(e.totalLimit-uint64(e.rowChunks.Len())), e.maxChunkSize) 427 err := Next(ctx, e.children[0], srcChk) 428 if err != nil { 429 return err 430 } 431 if srcChk.NumEvents() == 0 { 432 break 433 } 434 e.rowChunks.Add(srcChk) 435 } 436 e.initPointers() 437 e.initCompareFuncs() 438 e.buildKeyDeferredCausets() 439 return nil 440 } 441 442 const topNCompactionFactor = 4 443 444 func (e *TopNInterDirc) executeTopN(ctx context.Context) error { 445 heap.Init(e.chkHeap) 446 for uint64(len(e.rowPtrs)) > e.totalLimit { 447 // The number of rows we loaded may exceeds total limit, remove greatest rows by Pop. 448 heap.Pop(e.chkHeap) 449 } 450 childEventChk := newFirstChunk(e.children[0]) 451 for { 452 err := Next(ctx, e.children[0], childEventChk) 453 if err != nil { 454 return err 455 } 456 if childEventChk.NumEvents() == 0 { 457 break 458 } 459 err = e.processChildChk(childEventChk) 460 if err != nil { 461 return err 462 } 463 if e.rowChunks.Len() > len(e.rowPtrs)*topNCompactionFactor { 464 err = e.doCompaction() 465 if err != nil { 466 return err 467 } 468 } 469 } 470 sort.Slice(e.rowPtrs, e.keyDeferredCausetsLess) 471 return nil 472 } 473 474 func (e *TopNInterDirc) processChildChk(childEventChk *chunk.Chunk) error { 475 for i := 0; i < childEventChk.NumEvents(); i++ { 476 heapMaxPtr := e.rowPtrs[0] 477 var heapMax, next chunk.Event 478 heapMax = e.rowChunks.GetEvent(heapMaxPtr) 479 next = childEventChk.GetEvent(i) 480 if e.chkHeap.greaterEvent(heapMax, next) { 481 // Evict heap max, keep the next event. 482 e.rowPtrs[0] = e.rowChunks.AppendEvent(childEventChk.GetEvent(i)) 483 heap.Fix(e.chkHeap, 0) 484 } 485 } 486 return nil 487 } 488 489 // doCompaction rebuild the chunks and event pointers to release memory. 490 // If we don't do compaction, in a extreme case like the child data is already ascending sorted 491 // but we want descending top N, then we will keep all data in memory. 492 // But if data is distributed randomly, this function will be called log(n) times. 493 func (e *TopNInterDirc) doCompaction() error { 494 newEventChunks := chunk.NewList(retTypes(e), e.initCap, e.maxChunkSize) 495 newEventPtrs := make([]chunk.EventPtr, 0, e.rowChunks.Len()) 496 for _, rowPtr := range e.rowPtrs { 497 newEventPtr := newEventChunks.AppendEvent(e.rowChunks.GetEvent(rowPtr)) 498 newEventPtrs = append(newEventPtrs, newEventPtr) 499 } 500 newEventChunks.GetMemTracker().SetLabel(memory.LabelForEventChunks) 501 e.memTracker.ReplaceChild(e.rowChunks.GetMemTracker(), newEventChunks.GetMemTracker()) 502 e.rowChunks = newEventChunks 503 504 e.memTracker.Consume(int64(-8 * len(e.rowPtrs))) 505 e.memTracker.Consume(int64(8 * len(newEventPtrs))) 506 e.rowPtrs = newEventPtrs 507 return nil 508 }