github.com/matrixorigin/matrixone@v0.7.0/pkg/vm/engine/tae/db/scannerop.go (about) 1 // Copyright 2021 Matrix Origin 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 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package db 16 17 import ( 18 "container/heap" 19 "fmt" 20 "sort" 21 "time" 22 23 "github.com/matrixorigin/matrixone/pkg/common/moerr" 24 "go.uber.org/zap" 25 26 "github.com/matrixorigin/matrixone/pkg/logutil" 27 "github.com/matrixorigin/matrixone/pkg/vm/engine/tae/catalog" 28 "github.com/matrixorigin/matrixone/pkg/vm/engine/tae/common" 29 "github.com/matrixorigin/matrixone/pkg/vm/engine/tae/iface/txnif" 30 "github.com/matrixorigin/matrixone/pkg/vm/engine/tae/options" 31 "github.com/matrixorigin/matrixone/pkg/vm/engine/tae/tables/jobs" 32 "github.com/matrixorigin/matrixone/pkg/vm/engine/tae/tasks" 33 ) 34 35 type ScannerOp interface { 36 catalog.Processor 37 PreExecute() error 38 PostExecute() error 39 } 40 41 const ( 42 constMergeRightNow = int(options.DefaultBlockMaxRows) * int(options.DefaultBlocksPerSegment) 43 constMergeWaitDuration = 3 * time.Minute 44 constMergeMinBlks = 3 45 constMergeMinRows = 3000 46 constHeapCapacity = 300 47 ) 48 49 // min heap item 50 type mItem struct { 51 row int 52 entry *catalog.BlockEntry 53 } 54 55 type itemSet []*mItem 56 57 func (is itemSet) Len() int { return len(is) } 58 59 func (is itemSet) Less(i, j int) bool { 60 return is[i].row < is[j].row 61 } 62 63 func (is itemSet) Swap(i, j int) { 64 is[i], is[j] = is[j], is[i] 65 } 66 67 func (is *itemSet) Push(x any) { 68 item := x.(*mItem) 69 *is = append(*is, item) 70 } 71 72 func (is *itemSet) Pop() any { 73 old := *is 74 n := len(old) 75 item := old[n-1] 76 old[n-1] = nil // avoid memory leak 77 *is = old[0 : n-1] 78 return item 79 } 80 81 func (is *itemSet) Clear() { 82 old := *is 83 *is = old[:0] 84 } 85 86 // mergedBlkBuilder founds out blocks to be merged via maintaining a min heap holding 87 // up to default 300 items. 88 type mergedBlkBuilder struct { 89 blocks itemSet 90 cap int 91 } 92 93 func (h *mergedBlkBuilder) reset() { 94 h.blocks.Clear() 95 } 96 97 func (h *mergedBlkBuilder) push(item *mItem) { 98 heap.Push(&h.blocks, item) 99 if h.blocks.Len() > h.cap { 100 heap.Pop(&h.blocks) 101 } 102 } 103 104 // copy out the items in the heap 105 func (h *mergedBlkBuilder) finish() []*catalog.BlockEntry { 106 ret := make([]*catalog.BlockEntry, h.blocks.Len()) 107 for i, item := range h.blocks { 108 ret[i] = item.entry 109 } 110 return ret 111 } 112 113 // deletableSegBuilder founds deletable segemnts of a table. 114 // if a segment has no any non-dropped blocks, it can be deleted. except the 115 // segment has the max segment id, appender may creates block in it. 116 type deletableSegBuilder struct { 117 segHasNonDropBlk bool 118 maxSegId uint64 119 segCandids []*catalog.SegmentEntry // appendable 120 nsegCandids []*catalog.SegmentEntry // non-appendable 121 } 122 123 func (d *deletableSegBuilder) reset() { 124 d.segHasNonDropBlk = false 125 d.maxSegId = 0 126 d.segCandids = d.segCandids[:0] 127 d.nsegCandids = d.nsegCandids[:0] 128 } 129 130 func (d *deletableSegBuilder) resetForNewSeg() { 131 d.segHasNonDropBlk = false 132 } 133 134 // call this when a non dropped block was found when iterating blocks of a segment, 135 // which make the builder skip this segment 136 func (d *deletableSegBuilder) hintNonDropBlock() { 137 d.segHasNonDropBlk = true 138 } 139 140 func (d *deletableSegBuilder) push(entry *catalog.SegmentEntry) { 141 isAppendable := entry.IsAppendable() 142 if isAppendable && d.maxSegId < entry.ID { 143 d.maxSegId = entry.ID 144 } 145 if d.segHasNonDropBlk { 146 return 147 } 148 // all blocks has been dropped 149 if isAppendable { 150 d.segCandids = append(d.segCandids, entry) 151 } else { 152 d.nsegCandids = append(d.nsegCandids, entry) 153 } 154 } 155 156 // copy out segment entries expect the one with max segment id. 157 func (d *deletableSegBuilder) finish() []*catalog.SegmentEntry { 158 sort.Slice(d.segCandids, func(i, j int) bool { return d.segCandids[i].ID < d.segCandids[j].ID }) 159 if last := len(d.segCandids) - 1; last >= 0 && d.segCandids[last].ID == d.maxSegId { 160 d.segCandids = d.segCandids[:last] 161 } 162 if len(d.segCandids) == 0 && len(d.nsegCandids) == 0 { 163 return nil 164 } 165 ret := make([]*catalog.SegmentEntry, len(d.segCandids)+len(d.nsegCandids)) 166 copy(ret[:len(d.segCandids)], d.segCandids) 167 copy(ret[len(d.segCandids):], d.nsegCandids) 168 if cnt := len(d.nsegCandids); cnt != 0 { 169 logutil.Info("Mergeblocks deletable nseg", zap.Int("cnt", cnt)) 170 } 171 return ret 172 } 173 174 type stat struct { 175 ttl time.Time 176 lastTotalRow int 177 } 178 179 func (st *stat) String() string { 180 return fmt.Sprintf("row%d[%s]", st.lastTotalRow, st.ttl) 181 } 182 183 // mergeLimiter consider update rate and time to decide to merge or not. 184 type mergeLimiter struct { 185 stats map[uint64]*stat 186 } 187 188 // merge immediately if it has enough rows, skip if: 189 // 1. has only a few rows or blocks 190 // 2. is actively updating, which means total rows changes obviously compared with last time 191 // in other cases, wait some time to merge 192 func (ml *mergeLimiter) canMerge(tid uint64, totalRow int, blks int) bool { 193 if totalRow > constMergeRightNow { 194 logutil.Infof("Mergeblocks %d merge right now: %d rows %d blks", tid, totalRow, blks) 195 delete(ml.stats, tid) 196 return true 197 } 198 if blks < constMergeMinBlks || totalRow < constMergeMinRows { 199 return false 200 } 201 202 if st, ok := ml.stats[tid]; !ok { 203 ml.stats[tid] = &stat{ 204 ttl: ml.ttl(totalRow), 205 lastTotalRow: totalRow, 206 } 207 return false 208 } else if d := totalRow - st.lastTotalRow; d > 5 || d < -5 { 209 // a lot of things happened in the past scan interval... 210 st.ttl = ml.ttl(totalRow) 211 st.lastTotalRow = totalRow 212 logutil.Infof("Mergeblocks delta %d on table %d, resched to %v", d, tid, st.ttl) 213 return false 214 } else { 215 // this table is quiet finally, check ttl 216 return st.ttl.Before(time.Now()) 217 } 218 } 219 220 func (ml *mergeLimiter) ttl(totalRow int) time.Time { 221 return time.Now().Add(time.Duration( 222 (float32(constMergeWaitDuration) / float32(constMergeRightNow)) * 223 (float32(constMergeRightNow) - float32(totalRow)))) 224 } 225 226 // prune old stat entry 227 func (ml *mergeLimiter) pruneStale() { 228 staleIds := make([]uint64, 0) 229 t := time.Now().Add(-10 * time.Minute) 230 for id, st := range ml.stats { 231 if st.ttl.Before(t) { 232 staleIds = append(staleIds, id) 233 } 234 } 235 for _, id := range staleIds { 236 delete(ml.stats, id) 237 } 238 } 239 240 func (ml *mergeLimiter) String() string { 241 return fmt.Sprintf("%v", ml.stats) 242 } 243 244 type MergeTaskBuilder struct { 245 db *DB 246 *catalog.LoopProcessor 247 runCnt int 248 tableRowCnt int 249 tid uint64 250 limiter *mergeLimiter 251 segBuilder *deletableSegBuilder 252 blkBuilder *mergedBlkBuilder 253 } 254 255 func newMergeTaskBuiler(db *DB) *MergeTaskBuilder { 256 op := &MergeTaskBuilder{ 257 db: db, 258 LoopProcessor: new(catalog.LoopProcessor), 259 limiter: &mergeLimiter{ 260 stats: make(map[uint64]*stat), 261 }, 262 segBuilder: &deletableSegBuilder{ 263 segCandids: make([]*catalog.SegmentEntry, 0), 264 nsegCandids: make([]*catalog.SegmentEntry, 0), 265 }, 266 blkBuilder: &mergedBlkBuilder{ 267 blocks: make(itemSet, 0, constHeapCapacity), 268 cap: constHeapCapacity, 269 }, 270 } 271 272 op.TableFn = op.onTable 273 op.BlockFn = op.onBlock 274 op.SegmentFn = op.onSegment 275 op.PostSegmentFn = op.onPostSegment 276 return op 277 } 278 279 func (s *MergeTaskBuilder) trySchedMergeTask() { 280 if s.tid == 0 { 281 return 282 } 283 // compactable blks 284 mergedBlks := s.blkBuilder.finish() 285 // deletable segs 286 mergedSegs := s.segBuilder.finish() 287 hasDelSeg := len(mergedSegs) > 0 288 hasMergeBlk := s.limiter.canMerge(s.tid, s.tableRowCnt, len(mergedBlks)) 289 if !hasDelSeg && !hasMergeBlk { 290 return 291 } 292 293 segScopes := make([]common.ID, len(mergedSegs)) 294 for i, s := range mergedSegs { 295 segScopes[i] = *s.AsCommonID() 296 } 297 298 // remove stale segments only 299 if hasDelSeg && !hasMergeBlk { 300 factory := func(ctx *tasks.Context, txn txnif.AsyncTxn) (tasks.Task, error) { 301 return jobs.NewDelSegTask(ctx, txn, mergedSegs), nil 302 } 303 _, err := s.db.Scheduler.ScheduleMultiScopedTxnTask(nil, tasks.DataCompactionTask, segScopes, factory) 304 if err != nil { 305 logutil.Infof("[Mergeblocks] Schedule del seg errinfo=%v", err) 306 return 307 } 308 logutil.Infof("[Mergeblocks] Scheduled | del %d seg", len(mergedSegs)) 309 return 310 } 311 312 scopes := make([]common.ID, len(mergedBlks)) 313 for i, blk := range mergedBlks { 314 scopes[i] = *blk.AsCommonID() 315 } 316 317 factory := func(ctx *tasks.Context, txn txnif.AsyncTxn) (tasks.Task, error) { 318 return jobs.NewMergeBlocksTask(ctx, txn, mergedBlks, mergedSegs, nil, s.db.Scheduler) 319 } 320 _, err := s.db.Scheduler.ScheduleMultiScopedTxnTask(nil, tasks.DataCompactionTask, scopes, factory) 321 if err != nil { 322 logutil.Infof("[Mergeblocks] Schedule errinfo=%v", err) 323 } else { 324 logutil.Infof("[Mergeblocks] Scheduled | Scopes=[%d],[%d]%s", 325 len(segScopes), len(scopes), 326 common.BlockIDArraryString(scopes[:constMergeMinBlks])) 327 } 328 } 329 330 func (s *MergeTaskBuilder) resetForTable(tid uint64) { 331 s.tableRowCnt = 0 332 s.tid = tid 333 s.segBuilder.reset() 334 s.blkBuilder.reset() 335 } 336 337 func (s *MergeTaskBuilder) PreExecute() error { 338 // clean stale stats for every 10min (default) 339 if s.runCnt++; s.runCnt >= 120 { 340 s.runCnt = 0 341 s.limiter.pruneStale() 342 } 343 344 // print stats for every 50s (default) 345 if s.runCnt%10 == 0 { 346 logutil.Infof("Mergeblocks stats: %s", s.limiter.String()) 347 } 348 return nil 349 } 350 func (s *MergeTaskBuilder) PostExecute() error { 351 s.trySchedMergeTask() 352 s.resetForTable(0) 353 return nil 354 } 355 356 func (s *MergeTaskBuilder) onTable(tableEntry *catalog.TableEntry) (err error) { 357 s.trySchedMergeTask() 358 s.resetForTable(tableEntry.ID) 359 if !tableEntry.IsActive() { 360 err = moerr.GetOkStopCurrRecur() 361 } 362 return 363 } 364 365 func (s *MergeTaskBuilder) onSegment(segmentEntry *catalog.SegmentEntry) (err error) { 366 if !segmentEntry.IsActive() || (!segmentEntry.IsAppendable() && segmentEntry.IsSorted()) { 367 return moerr.GetOkStopCurrRecur() 368 } 369 if !segmentEntry.IsAppendable() { 370 logutil.Info("Mergeblocks scan nseg", zap.Uint64("seg", segmentEntry.ID)) 371 } 372 // handle appendable segs and unsorted non-appendable segs(which was written by cn) 373 s.segBuilder.resetForNewSeg() 374 return 375 } 376 377 func (s *MergeTaskBuilder) onPostSegment(segmentEntry *catalog.SegmentEntry) (err error) { 378 s.segBuilder.push(segmentEntry) 379 return nil 380 } 381 382 func (s *MergeTaskBuilder) onBlock(entry *catalog.BlockEntry) (err error) { 383 if !entry.IsActive() { 384 return 385 } 386 s.segBuilder.hintNonDropBlock() 387 388 entry.RLock() 389 defer entry.RUnlock() 390 391 // Skip uncommitted entries and appendable block 392 if !entry.IsCommitted() || 393 !catalog.ActiveWithNoTxnFilter(entry.MetaBaseEntry) || 394 !catalog.NonAppendableBlkFilter(entry) { 395 return 396 } 397 398 rows := entry.GetBlockData().Rows() 399 s.tableRowCnt += rows 400 s.blkBuilder.push(&mItem{row: rows, entry: entry}) 401 return nil 402 }