github.com/matrixorigin/matrixone@v0.7.0/pkg/vm/engine/tae/logtail/collector.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 logtail 16 17 import ( 18 "bytes" 19 "fmt" 20 "sync" 21 "sync/atomic" 22 23 "github.com/matrixorigin/matrixone/pkg/common/moerr" 24 "github.com/matrixorigin/matrixone/pkg/container/types" 25 "github.com/matrixorigin/matrixone/pkg/logutil" 26 "github.com/matrixorigin/matrixone/pkg/txn/clock" 27 "github.com/matrixorigin/matrixone/pkg/vm/engine/tae/catalog" 28 "github.com/matrixorigin/matrixone/pkg/vm/engine/tae/common" 29 "github.com/tidwall/btree" 30 ) 31 32 type Collector interface { 33 String() string 34 Run() 35 ScanInRange(from, to types.TS) (*DirtyTreeEntry, int) 36 ScanInRangePruned(from, to types.TS) *DirtyTreeEntry 37 GetAndRefreshMerged() *DirtyTreeEntry 38 Merge() *DirtyTreeEntry 39 GetMaxLSN(from, to types.TS) uint64 40 Init(maxts types.TS) 41 } 42 43 type DirtyEntryInterceptor = catalog.Processor 44 45 type DirtyTreeEntry struct { 46 sync.RWMutex 47 start, end types.TS 48 tree *common.Tree 49 } 50 51 func NewEmptyDirtyTreeEntry() *DirtyTreeEntry { 52 return &DirtyTreeEntry{ 53 tree: common.NewTree(), 54 } 55 } 56 57 func NewDirtyTreeEntry(start, end types.TS, tree *common.Tree) *DirtyTreeEntry { 58 entry := NewEmptyDirtyTreeEntry() 59 entry.start = start 60 entry.end = end 61 entry.tree = tree 62 return entry 63 } 64 65 func (entry *DirtyTreeEntry) Merge(o *DirtyTreeEntry) { 66 if entry.start.Greater(o.start) { 67 entry.start = o.start 68 } 69 if entry.end.Less(o.end) { 70 entry.end = o.end 71 } 72 entry.tree.Merge(o.tree) 73 } 74 75 func (entry *DirtyTreeEntry) IsEmpty() bool { 76 return entry.tree.IsEmpty() 77 } 78 79 func (entry *DirtyTreeEntry) GetTimeRange() (from, to types.TS) { 80 return entry.start, entry.end 81 } 82 83 func (entry *DirtyTreeEntry) GetTree() (tree *common.Tree) { 84 return entry.tree 85 } 86 87 func (entry *DirtyTreeEntry) String() string { 88 var buf bytes.Buffer 89 _, _ = buf.WriteString( 90 fmt.Sprintf("DirtyTreeEntry[%s=>%s]\n", 91 entry.start.ToString(), 92 entry.end.ToString())) 93 _, _ = buf.WriteString(entry.tree.String()) 94 return buf.String() 95 } 96 97 type dirtyCollector struct { 98 // sourcer 99 sourcer *Manager 100 101 // context 102 catalog *catalog.Catalog 103 clock *types.TsAlloctor 104 interceptor DirtyEntryInterceptor 105 106 // storage 107 storage struct { 108 sync.RWMutex 109 entries *btree.BTreeG[*DirtyTreeEntry] 110 maxTs types.TS 111 } 112 merged atomic.Pointer[DirtyTreeEntry] 113 } 114 115 func NewDirtyCollector( 116 sourcer *Manager, 117 clock clock.Clock, 118 catalog *catalog.Catalog, 119 interceptor DirtyEntryInterceptor) *dirtyCollector { 120 collector := &dirtyCollector{ 121 sourcer: sourcer, 122 catalog: catalog, 123 interceptor: interceptor, 124 clock: types.NewTsAlloctor(clock), 125 } 126 collector.storage.entries = btree.NewBTreeGOptions( 127 func(a, b *DirtyTreeEntry) bool { 128 return a.start.Less(b.start) && a.end.Less(b.end) 129 }, btree.Options{ 130 NoLocks: true, 131 }) 132 133 collector.merged.Store(NewEmptyDirtyTreeEntry()) 134 return collector 135 } 136 func (d *dirtyCollector) Init(maxts types.TS) { 137 d.storage.maxTs = maxts 138 } 139 func (d *dirtyCollector) Run() { 140 from, to := d.findRange() 141 142 // stale range found, skip this run 143 if to.IsEmpty() { 144 return 145 } 146 147 d.rangeScanAndUpdate(from, to) 148 d.cleanupStorage() 149 d.GetAndRefreshMerged() 150 } 151 152 func (d *dirtyCollector) ScanInRangePruned(from, to types.TS) ( 153 tree *DirtyTreeEntry) { 154 tree, _ = d.ScanInRange(from, to) 155 if err := d.tryCompactTree(d.interceptor, tree.tree); err != nil { 156 panic(err) 157 } 158 return 159 } 160 161 func (d *dirtyCollector) GetMaxLSN(from, to types.TS) uint64 { 162 reader := d.sourcer.GetReader(from, to) 163 return reader.GetMaxLSN() 164 } 165 func (d *dirtyCollector) ScanInRange(from, to types.TS) ( 166 entry *DirtyTreeEntry, count int) { 167 reader := d.sourcer.GetReader(from, to) 168 tree, count := reader.GetDirty() 169 170 // make a entry 171 entry = &DirtyTreeEntry{ 172 start: from, 173 end: to, 174 tree: tree, 175 } 176 return 177 } 178 179 // DirtyCount returns unflushed table, segment, block count 180 func (d *dirtyCollector) DirtyCount() (tblCnt, segCnt, blkCnt int) { 181 merged := d.GetAndRefreshMerged() 182 tblCnt = merged.tree.TableCount() 183 for _, tblTree := range merged.tree.Tables { 184 segCnt += len(tblTree.Segs) 185 for _, segTree := range tblTree.Segs { 186 blkCnt += len(segTree.Blks) 187 } 188 } 189 return 190 } 191 192 func (d *dirtyCollector) String() string { 193 merged := d.GetAndRefreshMerged() 194 return merged.tree.String() 195 } 196 197 func (d *dirtyCollector) GetAndRefreshMerged() (merged *DirtyTreeEntry) { 198 merged = d.merged.Load() 199 d.storage.RLock() 200 maxTs := d.storage.maxTs 201 d.storage.RUnlock() 202 if maxTs.LessEq(merged.end) { 203 return 204 } 205 merged = d.Merge() 206 d.tryUpdateMerged(merged) 207 return 208 } 209 210 func (d *dirtyCollector) Merge() *DirtyTreeEntry { 211 // get storage snapshot and work on it 212 snapshot, maxTs := d.getStorageSnapshot() 213 214 merged := NewEmptyDirtyTreeEntry() 215 merged.end = maxTs 216 217 // scan base on the snapshot 218 // merge all trees of the entry 219 snapshot.Scan(func(entry *DirtyTreeEntry) bool { 220 entry.RLock() 221 defer entry.RUnlock() 222 merged.tree.Merge(entry.tree) 223 return true 224 }) 225 226 return merged 227 } 228 229 func (d *dirtyCollector) tryUpdateMerged(merged *DirtyTreeEntry) (updated bool) { 230 var old *DirtyTreeEntry 231 for { 232 old = d.merged.Load() 233 if old.end.GreaterEq(merged.end) { 234 break 235 } 236 if d.merged.CompareAndSwap(old, merged) { 237 updated = true 238 break 239 } 240 } 241 return 242 } 243 244 func (d *dirtyCollector) findRange() (from, to types.TS) { 245 now := d.clock.Alloc() 246 d.storage.RLock() 247 defer d.storage.RUnlock() 248 if now.LessEq(d.storage.maxTs) { 249 return 250 } 251 from, to = d.storage.maxTs.Next(), now 252 return 253 } 254 255 func (d *dirtyCollector) rangeScanAndUpdate(from, to types.TS) (updated bool) { 256 entry, _ := d.ScanInRange(from, to) 257 258 // try to store the entry 259 updated = d.tryStoreEntry(entry) 260 return 261 } 262 263 func (d *dirtyCollector) tryStoreEntry(entry *DirtyTreeEntry) (ok bool) { 264 ok = true 265 d.storage.Lock() 266 defer d.storage.Unlock() 267 268 // storage was updated before 269 if !entry.start.Equal(d.storage.maxTs.Next()) { 270 ok = false 271 return 272 } 273 274 // update storage maxTs 275 d.storage.maxTs = entry.end 276 277 // don't store empty entry 278 if entry.tree.IsEmpty() { 279 return 280 } 281 282 d.storage.entries.Set(entry) 283 return 284 } 285 286 func (d *dirtyCollector) getStorageSnapshot() (ss *btree.BTreeG[*DirtyTreeEntry], ts types.TS) { 287 d.storage.Lock() 288 defer d.storage.Unlock() 289 ss = d.storage.entries.Copy() 290 ts = d.storage.maxTs 291 return 292 } 293 294 // Scan current dirty entries, remove all flushed or not found ones, and drive interceptor on remaining block entries. 295 func (d *dirtyCollector) cleanupStorage() { 296 toDeletes := make([]*DirtyTreeEntry, 0) 297 298 // get a snapshot of entries 299 entries, _ := d.getStorageSnapshot() 300 301 // scan all entries in the storage 302 // try compact the dirty tree for each entry 303 // if the dirty tree is empty, delete the specified entry from the storage 304 entries.Scan(func(entry *DirtyTreeEntry) bool { 305 entry.Lock() 306 defer entry.Unlock() 307 // dirty blocks within the time range has been flushed 308 // exclude the related dirty tree from the foreset 309 if entry.tree.IsEmpty() { 310 toDeletes = append(toDeletes, entry) 311 return true 312 } 313 if err := d.tryCompactTree(d.interceptor, entry.tree); err != nil { 314 logutil.Warnf("error: interceptor on dirty tree: %v", err) 315 } 316 if entry.tree.IsEmpty() { 317 toDeletes = append(toDeletes, entry) 318 } 319 return true 320 }) 321 322 if len(toDeletes) == 0 { 323 return 324 } 325 326 // remove entries with empty dirty tree from the storage 327 d.storage.Lock() 328 defer d.storage.Unlock() 329 for _, tree := range toDeletes { 330 d.storage.entries.Delete(tree) 331 } 332 } 333 334 // iter the tree and call interceptor to process block. flushed block, empty seg and table will be removed from the tree 335 func (d *dirtyCollector) tryCompactTree( 336 interceptor DirtyEntryInterceptor, 337 tree *common.Tree) (err error) { 338 var ( 339 db *catalog.DBEntry 340 tbl *catalog.TableEntry 341 seg *catalog.SegmentEntry 342 blk *catalog.BlockEntry 343 ) 344 for id, dirtyTable := range tree.Tables { 345 // remove empty tables 346 if dirtyTable.Compact() { 347 tree.Shrink(id) 348 continue 349 } 350 351 if db, err = d.catalog.GetDatabaseByID(dirtyTable.DbID); err != nil { 352 if moerr.IsMoErrCode(err, moerr.OkExpectedEOB) { 353 tree.Shrink(id) 354 err = nil 355 continue 356 } 357 break 358 } 359 if tbl, err = db.GetTableEntryByID(dirtyTable.ID); err != nil { 360 if moerr.IsMoErrCode(err, moerr.OkExpectedEOB) { 361 tree.Shrink(id) 362 err = nil 363 continue 364 } 365 break 366 } 367 368 for id, dirtySeg := range dirtyTable.Segs { 369 // remove empty segs 370 if dirtySeg.IsEmpty() { 371 dirtyTable.Shrink(id) 372 continue 373 } 374 if seg, err = tbl.GetSegmentByID(dirtySeg.ID); err != nil { 375 if moerr.IsMoErrCode(err, moerr.OkExpectedEOB) { 376 dirtyTable.Shrink(id) 377 err = nil 378 continue 379 } 380 return 381 } 382 for id := range dirtySeg.Blks { 383 if blk, err = seg.GetBlockEntryByID(id); err != nil { 384 if moerr.IsMoErrCode(err, moerr.OkExpectedEOB) { 385 dirtySeg.Shrink(id) 386 err = nil 387 continue 388 } 389 return 390 } 391 if blk.GetBlockData().RunCalibration() == 0 { 392 // TODO: may be put it to post replay process 393 // FIXME 394 if blk.HasPersistedData() { 395 blk.GetBlockData().TryUpgrade() 396 } 397 dirtySeg.Shrink(id) 398 continue 399 } 400 if err = interceptor.OnBlock(blk); err != nil { 401 return 402 } 403 } 404 } 405 } 406 tree.Compact() 407 return 408 }