github.com/ledgerwatch/erigon-lib@v1.0.0/state/inverted_index.go (about) 1 /* 2 Copyright 2022 Erigon contributors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package state 18 19 import ( 20 "bytes" 21 "container/heap" 22 "context" 23 "encoding/binary" 24 "fmt" 25 "math" 26 "os" 27 "path/filepath" 28 "regexp" 29 "strconv" 30 "sync/atomic" 31 "time" 32 33 "github.com/RoaringBitmap/roaring/roaring64" 34 "github.com/c2h5oh/datasize" 35 "github.com/ledgerwatch/erigon-lib/common/background" 36 "github.com/ledgerwatch/erigon-lib/common/cmp" 37 "github.com/ledgerwatch/erigon-lib/common/dir" 38 "github.com/ledgerwatch/erigon-lib/compress" 39 "github.com/ledgerwatch/erigon-lib/etl" 40 "github.com/ledgerwatch/erigon-lib/kv" 41 "github.com/ledgerwatch/erigon-lib/kv/bitmapdb" 42 "github.com/ledgerwatch/erigon-lib/kv/iter" 43 "github.com/ledgerwatch/erigon-lib/kv/order" 44 "github.com/ledgerwatch/erigon-lib/recsplit" 45 "github.com/ledgerwatch/erigon-lib/recsplit/eliasfano32" 46 "github.com/ledgerwatch/log/v3" 47 btree2 "github.com/tidwall/btree" 48 "golang.org/x/exp/slices" 49 "golang.org/x/sync/errgroup" 50 ) 51 52 type InvertedIndex struct { 53 files *btree2.BTreeG[*filesItem] // thread-safe, but maybe need 1 RWLock for all trees in AggregatorV3 54 55 // roFiles derivative from field `file`, but without garbage (canDelete=true, overlaps, etc...) 56 // MakeContext() using this field in zero-copy way 57 roFiles atomic.Pointer[[]ctxItem] 58 59 indexKeysTable string // txnNum_u64 -> key (k+auto_increment) 60 indexTable string // k -> txnNum_u64 , Needs to be table with DupSort 61 dir, tmpdir string // Directory where static files are created 62 filenameBase string 63 aggregationStep uint64 64 compressWorkers int 65 66 integrityFileExtensions []string 67 withLocalityIndex bool 68 localityIndex *LocalityIndex 69 tx kv.RwTx 70 71 garbageFiles []*filesItem // files that exist on disk, but ignored on opening folder - because they are garbage 72 73 // fields for history write 74 txNum uint64 75 txNumBytes [8]byte 76 wal *invertedIndexWAL 77 logger log.Logger 78 79 noFsync bool // fsync is enabled by default, but tests can manually disable 80 } 81 82 func NewInvertedIndex( 83 dir, tmpdir string, 84 aggregationStep uint64, 85 filenameBase string, 86 indexKeysTable string, 87 indexTable string, 88 withLocalityIndex bool, 89 integrityFileExtensions []string, 90 logger log.Logger, 91 ) (*InvertedIndex, error) { 92 ii := InvertedIndex{ 93 dir: dir, 94 tmpdir: tmpdir, 95 files: btree2.NewBTreeGOptions[*filesItem](filesItemLess, btree2.Options{Degree: 128, NoLocks: false}), 96 aggregationStep: aggregationStep, 97 filenameBase: filenameBase, 98 indexKeysTable: indexKeysTable, 99 indexTable: indexTable, 100 compressWorkers: 1, 101 integrityFileExtensions: integrityFileExtensions, 102 withLocalityIndex: withLocalityIndex, 103 logger: logger, 104 } 105 ii.roFiles.Store(&[]ctxItem{}) 106 107 if ii.withLocalityIndex { 108 var err error 109 ii.localityIndex, err = NewLocalityIndex(ii.dir, ii.tmpdir, ii.aggregationStep, ii.filenameBase, ii.logger) 110 if err != nil { 111 return nil, fmt.Errorf("NewHistory: %s, %w", ii.filenameBase, err) 112 } 113 } 114 return &ii, nil 115 } 116 117 func (ii *InvertedIndex) fileNamesOnDisk() ([]string, error) { 118 files, err := os.ReadDir(ii.dir) 119 if err != nil { 120 return nil, err 121 } 122 filteredFiles := make([]string, 0, len(files)) 123 for _, f := range files { 124 if !f.Type().IsRegular() { 125 continue 126 } 127 filteredFiles = append(filteredFiles, f.Name()) 128 } 129 return filteredFiles, nil 130 } 131 132 func (ii *InvertedIndex) OpenList(fNames []string) error { 133 if err := ii.localityIndex.OpenList(fNames); err != nil { 134 return err 135 } 136 ii.closeWhatNotInList(fNames) 137 ii.garbageFiles = ii.scanStateFiles(fNames) 138 if err := ii.openFiles(); err != nil { 139 return fmt.Errorf("NewHistory.openFiles: %s, %w", ii.filenameBase, err) 140 } 141 return nil 142 } 143 144 func (ii *InvertedIndex) OpenFolder() error { 145 files, err := ii.fileNamesOnDisk() 146 if err != nil { 147 return err 148 } 149 return ii.OpenList(files) 150 } 151 152 func (ii *InvertedIndex) scanStateFiles(fileNames []string) (garbageFiles []*filesItem) { 153 re := regexp.MustCompile("^" + ii.filenameBase + ".([0-9]+)-([0-9]+).ef$") 154 var err error 155 Loop: 156 for _, name := range fileNames { 157 subs := re.FindStringSubmatch(name) 158 if len(subs) != 3 { 159 if len(subs) != 0 { 160 ii.logger.Warn("File ignored by inverted index scan, more than 3 submatches", "name", name, "submatches", len(subs)) 161 } 162 continue 163 } 164 var startStep, endStep uint64 165 if startStep, err = strconv.ParseUint(subs[1], 10, 64); err != nil { 166 ii.logger.Warn("File ignored by inverted index scan, parsing startTxNum", "error", err, "name", name) 167 continue 168 } 169 if endStep, err = strconv.ParseUint(subs[2], 10, 64); err != nil { 170 ii.logger.Warn("File ignored by inverted index scan, parsing endTxNum", "error", err, "name", name) 171 continue 172 } 173 if startStep > endStep { 174 ii.logger.Warn("File ignored by inverted index scan, startTxNum > endTxNum", "name", name) 175 continue 176 } 177 178 startTxNum, endTxNum := startStep*ii.aggregationStep, endStep*ii.aggregationStep 179 var newFile = newFilesItem(startTxNum, endTxNum, ii.aggregationStep) 180 181 for _, ext := range ii.integrityFileExtensions { 182 requiredFile := fmt.Sprintf("%s.%d-%d.%s", ii.filenameBase, startStep, endStep, ext) 183 if !dir.FileExist(filepath.Join(ii.dir, requiredFile)) { 184 ii.logger.Debug(fmt.Sprintf("[snapshots] skip %s because %s doesn't exists", name, requiredFile)) 185 garbageFiles = append(garbageFiles, newFile) 186 continue Loop 187 } 188 } 189 190 if _, has := ii.files.Get(newFile); has { 191 continue 192 } 193 194 addNewFile := true 195 var subSets []*filesItem 196 ii.files.Walk(func(items []*filesItem) bool { 197 for _, item := range items { 198 if item.isSubsetOf(newFile) { 199 subSets = append(subSets, item) 200 continue 201 } 202 203 if newFile.isSubsetOf(item) { 204 if item.frozen { 205 addNewFile = false 206 garbageFiles = append(garbageFiles, newFile) 207 } 208 continue 209 } 210 } 211 return true 212 }) 213 //for _, subSet := range subSets { 214 // ii.files.Delete(subSet) 215 //} 216 if addNewFile { 217 ii.files.Set(newFile) 218 } 219 } 220 221 return garbageFiles 222 } 223 224 func ctxFiles(files *btree2.BTreeG[*filesItem]) (roItems []ctxItem) { 225 roFiles := make([]ctxItem, 0, files.Len()) 226 files.Walk(func(items []*filesItem) bool { 227 for _, item := range items { 228 if item.canDelete.Load() { 229 continue 230 } 231 232 // `kill -9` may leave small garbage files, but if big one already exists we assume it's good(fsynced) and no reason to merge again 233 // see super-set file, just drop sub-set files from list 234 for len(roFiles) > 0 && roFiles[len(roFiles)-1].src.isSubsetOf(item) { 235 roFiles[len(roFiles)-1].src = nil 236 roFiles = roFiles[:len(roFiles)-1] 237 } 238 roFiles = append(roFiles, ctxItem{ 239 startTxNum: item.startTxNum, 240 endTxNum: item.endTxNum, 241 i: len(roFiles), 242 src: item, 243 }) 244 } 245 return true 246 }) 247 if roFiles == nil { 248 roFiles = []ctxItem{} 249 } 250 return roFiles 251 } 252 253 func (ii *InvertedIndex) reCalcRoFiles() { 254 roFiles := ctxFiles(ii.files) 255 ii.roFiles.Store(&roFiles) 256 } 257 258 func (ii *InvertedIndex) missedIdxFiles() (l []*filesItem) { 259 ii.files.Walk(func(items []*filesItem) bool { 260 for _, item := range items { 261 fromStep, toStep := item.startTxNum/ii.aggregationStep, item.endTxNum/ii.aggregationStep 262 if !dir.FileExist(filepath.Join(ii.dir, fmt.Sprintf("%s.%d-%d.efi", ii.filenameBase, fromStep, toStep))) { 263 l = append(l, item) 264 } 265 } 266 return true 267 }) 268 return l 269 } 270 271 func (ii *InvertedIndex) buildEfi(ctx context.Context, item *filesItem, p *background.Progress) (err error) { 272 fromStep, toStep := item.startTxNum/ii.aggregationStep, item.endTxNum/ii.aggregationStep 273 fName := fmt.Sprintf("%s.%d-%d.efi", ii.filenameBase, fromStep, toStep) 274 idxPath := filepath.Join(ii.dir, fName) 275 p.Name.Store(&fName) 276 p.Total.Store(uint64(item.decompressor.Count())) 277 //ii.logger.Info("[snapshots] build idx", "file", fName) 278 return buildIndex(ctx, item.decompressor, idxPath, ii.tmpdir, item.decompressor.Count()/2, false, p, ii.logger, ii.noFsync) 279 } 280 281 // BuildMissedIndices - produce .efi/.vi/.kvi from .ef/.v/.kv 282 func (ii *InvertedIndex) BuildMissedIndices(ctx context.Context, g *errgroup.Group, ps *background.ProgressSet) { 283 missedFiles := ii.missedIdxFiles() 284 for _, item := range missedFiles { 285 item := item 286 g.Go(func() error { 287 p := &background.Progress{} 288 ps.Add(p) 289 defer ps.Delete(p) 290 return ii.buildEfi(ctx, item, p) 291 }) 292 } 293 } 294 295 func (ii *InvertedIndex) openFiles() error { 296 var err error 297 var totalKeys uint64 298 var invalidFileItems []*filesItem 299 ii.files.Walk(func(items []*filesItem) bool { 300 for _, item := range items { 301 if item.decompressor != nil { 302 continue 303 } 304 fromStep, toStep := item.startTxNum/ii.aggregationStep, item.endTxNum/ii.aggregationStep 305 datPath := filepath.Join(ii.dir, fmt.Sprintf("%s.%d-%d.ef", ii.filenameBase, fromStep, toStep)) 306 if !dir.FileExist(datPath) { 307 invalidFileItems = append(invalidFileItems, item) 308 continue 309 } 310 311 if item.decompressor, err = compress.NewDecompressor(datPath); err != nil { 312 ii.logger.Debug("InvertedIndex.openFiles: %w, %s", err, datPath) 313 continue 314 } 315 316 if item.index != nil { 317 continue 318 } 319 idxPath := filepath.Join(ii.dir, fmt.Sprintf("%s.%d-%d.efi", ii.filenameBase, fromStep, toStep)) 320 if dir.FileExist(idxPath) { 321 if item.index, err = recsplit.OpenIndex(idxPath); err != nil { 322 ii.logger.Debug("InvertedIndex.openFiles: %w, %s", err, idxPath) 323 return false 324 } 325 totalKeys += item.index.KeyCount() 326 } 327 } 328 return true 329 }) 330 for _, item := range invalidFileItems { 331 ii.files.Delete(item) 332 } 333 if err != nil { 334 return err 335 } 336 337 ii.reCalcRoFiles() 338 return nil 339 } 340 341 func (ii *InvertedIndex) closeWhatNotInList(fNames []string) { 342 var toDelete []*filesItem 343 ii.files.Walk(func(items []*filesItem) bool { 344 Loop1: 345 for _, item := range items { 346 for _, protectName := range fNames { 347 if item.decompressor != nil && item.decompressor.FileName() == protectName { 348 continue Loop1 349 } 350 } 351 toDelete = append(toDelete, item) 352 } 353 return true 354 }) 355 for _, item := range toDelete { 356 if item.decompressor != nil { 357 item.decompressor.Close() 358 item.decompressor = nil 359 } 360 if item.index != nil { 361 item.index.Close() 362 item.index = nil 363 } 364 ii.files.Delete(item) 365 } 366 } 367 368 func (ii *InvertedIndex) Close() { 369 ii.localityIndex.Close() 370 ii.closeWhatNotInList([]string{}) 371 ii.reCalcRoFiles() 372 } 373 374 // DisableFsync - just for tests 375 func (ii *InvertedIndex) DisableFsync() { ii.noFsync = true } 376 377 func (ii *InvertedIndex) Files() (res []string) { 378 ii.files.Walk(func(items []*filesItem) bool { 379 for _, item := range items { 380 if item.decompressor != nil { 381 res = append(res, item.decompressor.FileName()) 382 } 383 } 384 return true 385 }) 386 return res 387 } 388 389 func (ii *InvertedIndex) SetTx(tx kv.RwTx) { 390 ii.tx = tx 391 } 392 393 func (ii *InvertedIndex) SetTxNum(txNum uint64) { 394 ii.txNum = txNum 395 binary.BigEndian.PutUint64(ii.txNumBytes[:], ii.txNum) 396 } 397 398 // Add - !NotThreadSafe. Must use WalRLock/BatchHistoryWriteEnd 399 func (ii *InvertedIndex) Add(key []byte) error { 400 return ii.wal.add(key, key) 401 } 402 func (ii *InvertedIndex) add(key, indexKey []byte) error { //nolint 403 return ii.wal.add(key, indexKey) 404 } 405 406 func (ii *InvertedIndex) DiscardHistory(tmpdir string) { 407 ii.wal = ii.newWriter(tmpdir, false, true) 408 } 409 func (ii *InvertedIndex) StartWrites() { 410 ii.wal = ii.newWriter(ii.tmpdir, true, false) 411 } 412 func (ii *InvertedIndex) StartUnbufferedWrites() { 413 ii.wal = ii.newWriter(ii.tmpdir, false, false) 414 } 415 func (ii *InvertedIndex) FinishWrites() { 416 ii.wal.close() 417 ii.wal = nil 418 } 419 420 func (ii *InvertedIndex) Rotate() *invertedIndexWAL { 421 wal := ii.wal 422 if wal != nil { 423 ii.wal = ii.newWriter(ii.wal.tmpdir, ii.wal.buffered, ii.wal.discard) 424 } 425 return wal 426 } 427 428 type invertedIndexWAL struct { 429 ii *InvertedIndex 430 index *etl.Collector 431 indexKeys *etl.Collector 432 tmpdir string 433 buffered bool 434 discard bool 435 } 436 437 // loadFunc - is analog of etl.Identity, but it signaling to etl - use .Put instead of .AppendDup - to allow duplicates 438 // maybe in future we will improve etl, to sort dupSort values in the way that allow use .AppendDup 439 func loadFunc(k, v []byte, table etl.CurrentTableReader, next etl.LoadNextFunc) error { 440 return next(k, k, v) 441 } 442 443 func (ii *invertedIndexWAL) Flush(ctx context.Context, tx kv.RwTx) error { 444 if ii.discard || !ii.buffered { 445 return nil 446 } 447 if err := ii.index.Load(tx, ii.ii.indexTable, loadFunc, etl.TransformArgs{Quit: ctx.Done()}); err != nil { 448 return err 449 } 450 if err := ii.indexKeys.Load(tx, ii.ii.indexKeysTable, loadFunc, etl.TransformArgs{Quit: ctx.Done()}); err != nil { 451 return err 452 } 453 ii.close() 454 return nil 455 } 456 457 func (ii *invertedIndexWAL) close() { 458 if ii == nil { 459 return 460 } 461 if ii.index != nil { 462 ii.index.Close() 463 } 464 if ii.indexKeys != nil { 465 ii.indexKeys.Close() 466 } 467 } 468 469 // 3 history + 4 indices = 10 etl collectors, 10*256Mb/8 = 512mb - for all indices buffers 470 var WALCollectorRAM = 2 * (etl.BufferOptimalSize / 8) 471 472 func init() { 473 v, _ := os.LookupEnv("ERIGON_WAL_COLLETOR_RAM") 474 if v != "" { 475 var err error 476 WALCollectorRAM, err = datasize.ParseString(v) 477 if err != nil { 478 panic(err) 479 } 480 } 481 } 482 483 func (ii *InvertedIndex) newWriter(tmpdir string, buffered, discard bool) *invertedIndexWAL { 484 w := &invertedIndexWAL{ii: ii, 485 buffered: buffered, 486 discard: discard, 487 tmpdir: tmpdir, 488 } 489 if buffered { 490 // etl collector doesn't fsync: means if have enough ram, all files produced by all collectors will be in ram 491 w.index = etl.NewCollector(ii.indexTable, tmpdir, etl.NewSortableBuffer(WALCollectorRAM), ii.logger) 492 w.indexKeys = etl.NewCollector(ii.indexKeysTable, tmpdir, etl.NewSortableBuffer(WALCollectorRAM), ii.logger) 493 w.index.LogLvl(log.LvlTrace) 494 w.indexKeys.LogLvl(log.LvlTrace) 495 } 496 return w 497 } 498 499 func (ii *invertedIndexWAL) add(key, indexKey []byte) error { 500 if ii.discard { 501 return nil 502 } 503 504 if ii.buffered { 505 if err := ii.indexKeys.Collect(ii.ii.txNumBytes[:], key); err != nil { 506 return err 507 } 508 509 if err := ii.index.Collect(indexKey, ii.ii.txNumBytes[:]); err != nil { 510 return err 511 } 512 } else { 513 if err := ii.ii.tx.Put(ii.ii.indexKeysTable, ii.ii.txNumBytes[:], key); err != nil { 514 return err 515 } 516 if err := ii.ii.tx.Put(ii.ii.indexTable, indexKey, ii.ii.txNumBytes[:]); err != nil { 517 return err 518 } 519 } 520 return nil 521 } 522 523 func (ii *InvertedIndex) MakeContext() *InvertedIndexContext { 524 var ic = InvertedIndexContext{ 525 ii: ii, 526 files: *ii.roFiles.Load(), 527 loc: ii.localityIndex.MakeContext(), 528 } 529 for _, item := range ic.files { 530 if !item.src.frozen { 531 item.src.refcount.Add(1) 532 } 533 } 534 return &ic 535 } 536 func (ic *InvertedIndexContext) Close() { 537 for _, item := range ic.files { 538 if item.src.frozen { 539 continue 540 } 541 refCnt := item.src.refcount.Add(-1) 542 //GC: last reader responsible to remove useles files: close it and delete 543 if refCnt == 0 && item.src.canDelete.Load() { 544 item.src.closeFilesAndRemove() 545 } 546 } 547 548 for _, r := range ic.readers { 549 r.Close() 550 } 551 552 ic.loc.Close(ic.ii.logger) 553 } 554 555 type InvertedIndexContext struct { 556 ii *InvertedIndex 557 files []ctxItem // have no garbage (overlaps, etc...) 558 getters []*compress.Getter 559 readers []*recsplit.IndexReader 560 loc *ctxLocalityIdx 561 } 562 563 func (ic *InvertedIndexContext) statelessGetter(i int) *compress.Getter { 564 if ic.getters == nil { 565 ic.getters = make([]*compress.Getter, len(ic.files)) 566 } 567 r := ic.getters[i] 568 if r == nil { 569 r = ic.files[i].src.decompressor.MakeGetter() 570 ic.getters[i] = r 571 } 572 return r 573 } 574 func (ic *InvertedIndexContext) statelessIdxReader(i int) *recsplit.IndexReader { 575 if ic.readers == nil { 576 ic.readers = make([]*recsplit.IndexReader, len(ic.files)) 577 } 578 r := ic.readers[i] 579 if r == nil { 580 r = ic.files[i].src.index.GetReaderFromPool() 581 ic.readers[i] = r 582 } 583 return r 584 } 585 586 func (ic *InvertedIndexContext) getFile(from, to uint64) (it ctxItem, ok bool) { 587 for _, item := range ic.files { 588 if item.startTxNum == from && item.endTxNum == to { 589 return item, true 590 } 591 } 592 return it, false 593 } 594 595 // IdxRange - return range of txNums for given `key` 596 // is to be used in public API, therefore it relies on read-only transaction 597 // so that iteration can be done even when the inverted index is being updated. 598 // [startTxNum; endNumTx) 599 func (ic *InvertedIndexContext) IdxRange(key []byte, startTxNum, endTxNum int, asc order.By, limit int, roTx kv.Tx) (iter.U64, error) { 600 frozenIt, err := ic.iterateRangeFrozen(key, startTxNum, endTxNum, asc, limit) 601 if err != nil { 602 return nil, err 603 } 604 recentIt, err := ic.recentIterateRange(key, startTxNum, endTxNum, asc, limit, roTx) 605 if err != nil { 606 return nil, err 607 } 608 return iter.Union[uint64](frozenIt, recentIt, asc, limit), nil 609 } 610 611 func (ic *InvertedIndexContext) recentIterateRange(key []byte, startTxNum, endTxNum int, asc order.By, limit int, roTx kv.Tx) (iter.U64, error) { 612 //optimization: return empty pre-allocated iterator if range is frozen 613 if asc { 614 isFrozenRange := len(ic.files) > 0 && endTxNum >= 0 && ic.files[len(ic.files)-1].endTxNum >= uint64(endTxNum) 615 if isFrozenRange { 616 return iter.EmptyU64, nil 617 } 618 } else { 619 isFrozenRange := len(ic.files) > 0 && startTxNum >= 0 && ic.files[len(ic.files)-1].endTxNum >= uint64(startTxNum) 620 if isFrozenRange { 621 return iter.EmptyU64, nil 622 } 623 } 624 625 var from []byte 626 if startTxNum >= 0 { 627 from = make([]byte, 8) 628 binary.BigEndian.PutUint64(from, uint64(startTxNum)) 629 } 630 631 var to []byte 632 if endTxNum >= 0 { 633 to = make([]byte, 8) 634 binary.BigEndian.PutUint64(to, uint64(endTxNum)) 635 } 636 637 it, err := roTx.RangeDupSort(ic.ii.indexTable, key, from, to, asc, limit) 638 if err != nil { 639 return nil, err 640 } 641 return iter.TransformKV2U64(it, func(_, v []byte) (uint64, error) { 642 return binary.BigEndian.Uint64(v), nil 643 }), nil 644 } 645 646 // IdxRange is to be used in public API, therefore it relies on read-only transaction 647 // so that iteration can be done even when the inverted index is being updated. 648 // [startTxNum; endNumTx) 649 func (ic *InvertedIndexContext) iterateRangeFrozen(key []byte, startTxNum, endTxNum int, asc order.By, limit int) (*FrozenInvertedIdxIter, error) { 650 if asc && (startTxNum >= 0 && endTxNum >= 0) && startTxNum > endTxNum { 651 return nil, fmt.Errorf("startTxNum=%d epected to be lower than endTxNum=%d", startTxNum, endTxNum) 652 } 653 if !asc && (startTxNum >= 0 && endTxNum >= 0) && startTxNum < endTxNum { 654 return nil, fmt.Errorf("startTxNum=%d epected to be bigger than endTxNum=%d", startTxNum, endTxNum) 655 } 656 657 it := &FrozenInvertedIdxIter{ 658 key: key, 659 startTxNum: startTxNum, 660 endTxNum: endTxNum, 661 indexTable: ic.ii.indexTable, 662 orderAscend: asc, 663 limit: limit, 664 ef: eliasfano32.NewEliasFano(1, 1), 665 } 666 if asc { 667 for i := len(ic.files) - 1; i >= 0; i-- { 668 // [from,to) && from < to 669 if endTxNum >= 0 && int(ic.files[i].startTxNum) >= endTxNum { 670 continue 671 } 672 if startTxNum >= 0 && ic.files[i].endTxNum <= uint64(startTxNum) { 673 break 674 } 675 it.stack = append(it.stack, ic.files[i]) 676 it.stack[len(it.stack)-1].getter = it.stack[len(it.stack)-1].src.decompressor.MakeGetter() 677 it.stack[len(it.stack)-1].reader = it.stack[len(it.stack)-1].src.index.GetReaderFromPool() 678 it.hasNext = true 679 } 680 } else { 681 for i := 0; i < len(ic.files); i++ { 682 // [from,to) && from > to 683 if endTxNum >= 0 && int(ic.files[i].endTxNum) <= endTxNum { 684 continue 685 } 686 if startTxNum >= 0 && ic.files[i].startTxNum > uint64(startTxNum) { 687 break 688 } 689 690 it.stack = append(it.stack, ic.files[i]) 691 it.stack[len(it.stack)-1].getter = it.stack[len(it.stack)-1].src.decompressor.MakeGetter() 692 it.stack[len(it.stack)-1].reader = it.stack[len(it.stack)-1].src.index.GetReaderFromPool() 693 it.hasNext = true 694 } 695 } 696 it.advance() 697 return it, nil 698 } 699 700 // FrozenInvertedIdxIter allows iteration over range of tx numbers 701 // Iteration is not implmented via callback function, because there is often 702 // a requirement for interators to be composable (for example, to implement AND and OR for indices) 703 // FrozenInvertedIdxIter must be closed after use to prevent leaking of resources like cursor 704 type FrozenInvertedIdxIter struct { 705 key []byte 706 startTxNum, endTxNum int 707 limit int 708 orderAscend order.By 709 710 efIt iter.Unary[uint64] 711 indexTable string 712 stack []ctxItem 713 714 nextN uint64 715 hasNext bool 716 err error 717 718 ef *eliasfano32.EliasFano 719 } 720 721 func (it *FrozenInvertedIdxIter) Close() { 722 for _, item := range it.stack { 723 item.reader.Close() 724 } 725 } 726 727 func (it *FrozenInvertedIdxIter) advance() { 728 if it.orderAscend { 729 if it.hasNext { 730 it.advanceInFiles() 731 } 732 } else { 733 if it.hasNext { 734 it.advanceInFiles() 735 } 736 } 737 } 738 739 func (it *FrozenInvertedIdxIter) HasNext() bool { 740 if it.err != nil { // always true, then .Next() call will return this error 741 return true 742 } 743 if it.limit == 0 { // limit reached 744 return false 745 } 746 return it.hasNext 747 } 748 749 func (it *FrozenInvertedIdxIter) Next() (uint64, error) { return it.next(), nil } 750 751 func (it *FrozenInvertedIdxIter) next() uint64 { 752 it.limit-- 753 n := it.nextN 754 it.advance() 755 return n 756 } 757 758 func (it *FrozenInvertedIdxIter) advanceInFiles() { 759 for { 760 for it.efIt == nil { //TODO: this loop may be optimized by LocalityIndex 761 if len(it.stack) == 0 { 762 it.hasNext = false 763 return 764 } 765 item := it.stack[len(it.stack)-1] 766 it.stack = it.stack[:len(it.stack)-1] 767 offset := item.reader.Lookup(it.key) 768 g := item.getter 769 g.Reset(offset) 770 k, _ := g.NextUncompressed() 771 if bytes.Equal(k, it.key) { 772 eliasVal, _ := g.NextUncompressed() 773 it.ef.Reset(eliasVal) 774 if it.orderAscend { 775 efiter := it.ef.Iterator() 776 if it.startTxNum > 0 { 777 efiter.Seek(uint64(it.startTxNum)) 778 } 779 it.efIt = efiter 780 } else { 781 it.efIt = it.ef.ReverseIterator() 782 } 783 } 784 } 785 786 //TODO: add seek method 787 //Asc: [from, to) AND from > to 788 //Desc: [from, to) AND from < to 789 if it.orderAscend { 790 for it.efIt.HasNext() { 791 n, _ := it.efIt.Next() 792 if it.endTxNum >= 0 && int(n) >= it.endTxNum { 793 it.hasNext = false 794 return 795 } 796 if int(n) >= it.startTxNum { 797 it.hasNext = true 798 it.nextN = n 799 return 800 } 801 } 802 } else { 803 for it.efIt.HasNext() { 804 n, _ := it.efIt.Next() 805 if int(n) <= it.endTxNum { 806 it.hasNext = false 807 return 808 } 809 if it.startTxNum >= 0 && int(n) <= it.startTxNum { 810 it.hasNext = true 811 it.nextN = n 812 return 813 } 814 } 815 } 816 it.efIt = nil // Exhausted this iterator 817 } 818 } 819 820 // RecentInvertedIdxIter allows iteration over range of tx numbers 821 // Iteration is not implmented via callback function, because there is often 822 // a requirement for interators to be composable (for example, to implement AND and OR for indices) 823 type RecentInvertedIdxIter struct { 824 key []byte 825 startTxNum, endTxNum int 826 limit int 827 orderAscend order.By 828 829 roTx kv.Tx 830 cursor kv.CursorDupSort 831 indexTable string 832 833 nextN uint64 834 hasNext bool 835 err error 836 837 bm *roaring64.Bitmap 838 } 839 840 func (it *RecentInvertedIdxIter) Close() { 841 if it.cursor != nil { 842 it.cursor.Close() 843 } 844 bitmapdb.ReturnToPool64(it.bm) 845 } 846 847 func (it *RecentInvertedIdxIter) advanceInDB() { 848 var v []byte 849 var err error 850 if it.cursor == nil { 851 if it.cursor, err = it.roTx.CursorDupSort(it.indexTable); err != nil { 852 // TODO pass error properly around 853 panic(err) 854 } 855 var k []byte 856 if k, _, err = it.cursor.SeekExact(it.key); err != nil { 857 panic(err) 858 } 859 if k == nil { 860 it.hasNext = false 861 return 862 } 863 //Asc: [from, to) AND from > to 864 //Desc: [from, to) AND from < to 865 var keyBytes [8]byte 866 if it.startTxNum > 0 { 867 binary.BigEndian.PutUint64(keyBytes[:], uint64(it.startTxNum)) 868 } 869 if v, err = it.cursor.SeekBothRange(it.key, keyBytes[:]); err != nil { 870 panic(err) 871 } 872 if v == nil { 873 if !it.orderAscend { 874 _, v, _ = it.cursor.PrevDup() 875 if err != nil { 876 panic(err) 877 } 878 } 879 if v == nil { 880 it.hasNext = false 881 return 882 } 883 } 884 } else { 885 if it.orderAscend { 886 _, v, err = it.cursor.NextDup() 887 if err != nil { 888 // TODO pass error properly around 889 panic(err) 890 } 891 } else { 892 _, v, err = it.cursor.PrevDup() 893 if err != nil { 894 panic(err) 895 } 896 } 897 } 898 899 //Asc: [from, to) AND from > to 900 //Desc: [from, to) AND from < to 901 if it.orderAscend { 902 for ; v != nil; _, v, err = it.cursor.NextDup() { 903 if err != nil { 904 // TODO pass error properly around 905 panic(err) 906 } 907 n := binary.BigEndian.Uint64(v) 908 if it.endTxNum >= 0 && int(n) >= it.endTxNum { 909 it.hasNext = false 910 return 911 } 912 if int(n) >= it.startTxNum { 913 it.hasNext = true 914 it.nextN = n 915 return 916 } 917 } 918 } else { 919 for ; v != nil; _, v, err = it.cursor.PrevDup() { 920 if err != nil { 921 // TODO pass error properly around 922 panic(err) 923 } 924 n := binary.BigEndian.Uint64(v) 925 if int(n) <= it.endTxNum { 926 it.hasNext = false 927 return 928 } 929 if it.startTxNum >= 0 && int(n) <= it.startTxNum { 930 it.hasNext = true 931 it.nextN = n 932 return 933 } 934 } 935 } 936 937 it.hasNext = false 938 } 939 940 func (it *RecentInvertedIdxIter) advance() { 941 if it.orderAscend { 942 if it.hasNext { 943 it.advanceInDB() 944 } 945 } else { 946 if it.hasNext { 947 it.advanceInDB() 948 } 949 } 950 } 951 952 func (it *RecentInvertedIdxIter) HasNext() bool { 953 if it.err != nil { // always true, then .Next() call will return this error 954 return true 955 } 956 if it.limit == 0 { // limit reached 957 return false 958 } 959 return it.hasNext 960 } 961 962 func (it *RecentInvertedIdxIter) Next() (uint64, error) { 963 if it.err != nil { 964 return 0, it.err 965 } 966 it.limit-- 967 n := it.nextN 968 it.advance() 969 return n, nil 970 } 971 972 type InvertedIterator1 struct { 973 roTx kv.Tx 974 cursor kv.CursorDupSort 975 indexTable string 976 key []byte 977 h ReconHeap 978 nextKey []byte 979 nextFileKey []byte 980 nextDbKey []byte 981 endTxNum uint64 982 startTxNum uint64 983 startTxKey [8]byte 984 hasNextInDb bool 985 hasNextInFiles bool 986 } 987 988 func (it *InvertedIterator1) Close() { 989 if it.cursor != nil { 990 it.cursor.Close() 991 } 992 } 993 994 func (it *InvertedIterator1) advanceInFiles() { 995 for it.h.Len() > 0 { 996 top := heap.Pop(&it.h).(*ReconItem) 997 key := top.key 998 val, _ := top.g.NextUncompressed() 999 if top.g.HasNext() { 1000 top.key, _ = top.g.NextUncompressed() 1001 heap.Push(&it.h, top) 1002 } 1003 if !bytes.Equal(key, it.key) { 1004 ef, _ := eliasfano32.ReadEliasFano(val) 1005 min := ef.Get(0) 1006 max := ef.Max() 1007 if min < it.endTxNum && max >= it.startTxNum { // Intersection of [min; max) and [it.startTxNum; it.endTxNum) 1008 it.key = key 1009 it.nextFileKey = key 1010 return 1011 } 1012 } 1013 } 1014 it.hasNextInFiles = false 1015 } 1016 1017 func (it *InvertedIterator1) advanceInDb() { 1018 var k, v []byte 1019 var err error 1020 if it.cursor == nil { 1021 if it.cursor, err = it.roTx.CursorDupSort(it.indexTable); err != nil { 1022 // TODO pass error properly around 1023 panic(err) 1024 } 1025 if k, _, err = it.cursor.First(); err != nil { 1026 // TODO pass error properly around 1027 panic(err) 1028 } 1029 } else { 1030 if k, _, err = it.cursor.NextNoDup(); err != nil { 1031 panic(err) 1032 } 1033 } 1034 for k != nil { 1035 if v, err = it.cursor.SeekBothRange(k, it.startTxKey[:]); err != nil { 1036 panic(err) 1037 } 1038 if v != nil { 1039 txNum := binary.BigEndian.Uint64(v) 1040 if txNum < it.endTxNum { 1041 it.nextDbKey = append(it.nextDbKey[:0], k...) 1042 return 1043 } 1044 } 1045 if k, _, err = it.cursor.NextNoDup(); err != nil { 1046 panic(err) 1047 } 1048 } 1049 it.cursor.Close() 1050 it.cursor = nil 1051 it.hasNextInDb = false 1052 } 1053 1054 func (it *InvertedIterator1) advance() { 1055 if it.hasNextInFiles { 1056 if it.hasNextInDb { 1057 c := bytes.Compare(it.nextFileKey, it.nextDbKey) 1058 if c < 0 { 1059 it.nextKey = append(it.nextKey[:0], it.nextFileKey...) 1060 it.advanceInFiles() 1061 } else if c > 0 { 1062 it.nextKey = append(it.nextKey[:0], it.nextDbKey...) 1063 it.advanceInDb() 1064 } else { 1065 it.nextKey = append(it.nextKey[:0], it.nextFileKey...) 1066 it.advanceInDb() 1067 it.advanceInFiles() 1068 } 1069 } else { 1070 it.nextKey = append(it.nextKey[:0], it.nextFileKey...) 1071 it.advanceInFiles() 1072 } 1073 } else if it.hasNextInDb { 1074 it.nextKey = append(it.nextKey[:0], it.nextDbKey...) 1075 it.advanceInDb() 1076 } else { 1077 it.nextKey = nil 1078 } 1079 } 1080 1081 func (it *InvertedIterator1) HasNext() bool { 1082 return it.hasNextInFiles || it.hasNextInDb || it.nextKey != nil 1083 } 1084 1085 func (it *InvertedIterator1) Next(keyBuf []byte) []byte { 1086 result := append(keyBuf, it.nextKey...) 1087 it.advance() 1088 return result 1089 } 1090 1091 func (ic *InvertedIndexContext) IterateChangedKeys(startTxNum, endTxNum uint64, roTx kv.Tx) InvertedIterator1 { 1092 var ii1 InvertedIterator1 1093 ii1.hasNextInDb = true 1094 ii1.roTx = roTx 1095 ii1.indexTable = ic.ii.indexTable 1096 for _, item := range ic.files { 1097 if item.endTxNum <= startTxNum { 1098 continue 1099 } 1100 if item.startTxNum >= endTxNum { 1101 break 1102 } 1103 if item.endTxNum >= endTxNum { 1104 ii1.hasNextInDb = false 1105 } 1106 g := item.src.decompressor.MakeGetter() 1107 if g.HasNext() { 1108 key, _ := g.NextUncompressed() 1109 heap.Push(&ii1.h, &ReconItem{startTxNum: item.startTxNum, endTxNum: item.endTxNum, g: g, txNum: ^item.endTxNum, key: key}) 1110 ii1.hasNextInFiles = true 1111 } 1112 } 1113 binary.BigEndian.PutUint64(ii1.startTxKey[:], startTxNum) 1114 ii1.startTxNum = startTxNum 1115 ii1.endTxNum = endTxNum 1116 ii1.advanceInDb() 1117 ii1.advanceInFiles() 1118 ii1.advance() 1119 return ii1 1120 } 1121 1122 func (ii *InvertedIndex) collate(ctx context.Context, txFrom, txTo uint64, roTx kv.Tx) (map[string]*roaring64.Bitmap, error) { 1123 keysCursor, err := roTx.CursorDupSort(ii.indexKeysTable) 1124 if err != nil { 1125 return nil, fmt.Errorf("create %s keys cursor: %w", ii.filenameBase, err) 1126 } 1127 defer keysCursor.Close() 1128 indexBitmaps := map[string]*roaring64.Bitmap{} 1129 var txKey [8]byte 1130 binary.BigEndian.PutUint64(txKey[:], txFrom) 1131 var k, v []byte 1132 for k, v, err = keysCursor.Seek(txKey[:]); err == nil && k != nil; k, v, err = keysCursor.Next() { 1133 txNum := binary.BigEndian.Uint64(k) 1134 if txNum >= txTo { 1135 break 1136 } 1137 var bitmap *roaring64.Bitmap 1138 var ok bool 1139 if bitmap, ok = indexBitmaps[string(v)]; !ok { 1140 bitmap = bitmapdb.NewBitmap64() 1141 indexBitmaps[string(v)] = bitmap 1142 } 1143 bitmap.Add(txNum) 1144 1145 select { 1146 case <-ctx.Done(): 1147 return nil, ctx.Err() 1148 default: 1149 } 1150 } 1151 if err != nil { 1152 return nil, fmt.Errorf("iterate over %s keys cursor: %w", ii.filenameBase, err) 1153 } 1154 return indexBitmaps, nil 1155 } 1156 1157 type InvertedFiles struct { 1158 decomp *compress.Decompressor 1159 index *recsplit.Index 1160 } 1161 1162 func (sf InvertedFiles) Close() { 1163 if sf.decomp != nil { 1164 sf.decomp.Close() 1165 } 1166 if sf.index != nil { 1167 sf.index.Close() 1168 } 1169 } 1170 1171 func (ii *InvertedIndex) buildFiles(ctx context.Context, step uint64, bitmaps map[string]*roaring64.Bitmap, ps *background.ProgressSet) (InvertedFiles, error) { 1172 var decomp *compress.Decompressor 1173 var index *recsplit.Index 1174 var comp *compress.Compressor 1175 var err error 1176 closeComp := true 1177 defer func() { 1178 if closeComp { 1179 if comp != nil { 1180 comp.Close() 1181 } 1182 if decomp != nil { 1183 decomp.Close() 1184 } 1185 if index != nil { 1186 index.Close() 1187 } 1188 } 1189 }() 1190 txNumFrom := step * ii.aggregationStep 1191 txNumTo := (step + 1) * ii.aggregationStep 1192 datFileName := fmt.Sprintf("%s.%d-%d.ef", ii.filenameBase, txNumFrom/ii.aggregationStep, txNumTo/ii.aggregationStep) 1193 datPath := filepath.Join(ii.dir, datFileName) 1194 keys := make([]string, 0, len(bitmaps)) 1195 for key := range bitmaps { 1196 keys = append(keys, key) 1197 } 1198 slices.Sort(keys) 1199 { 1200 p := ps.AddNew(datFileName, 1) 1201 defer ps.Delete(p) 1202 comp, err = compress.NewCompressor(ctx, "ef", datPath, ii.tmpdir, compress.MinPatternScore, ii.compressWorkers, log.LvlTrace, ii.logger) 1203 if err != nil { 1204 return InvertedFiles{}, fmt.Errorf("create %s compressor: %w", ii.filenameBase, err) 1205 } 1206 var buf []byte 1207 for _, key := range keys { 1208 if err = comp.AddUncompressedWord([]byte(key)); err != nil { 1209 return InvertedFiles{}, fmt.Errorf("add %s key [%x]: %w", ii.filenameBase, key, err) 1210 } 1211 bitmap := bitmaps[key] 1212 ef := eliasfano32.NewEliasFano(bitmap.GetCardinality(), bitmap.Maximum()) 1213 it := bitmap.Iterator() 1214 for it.HasNext() { 1215 ef.AddOffset(it.Next()) 1216 } 1217 ef.Build() 1218 buf = ef.AppendBytes(buf[:0]) 1219 if err = comp.AddUncompressedWord(buf); err != nil { 1220 return InvertedFiles{}, fmt.Errorf("add %s val: %w", ii.filenameBase, err) 1221 } 1222 } 1223 if err = comp.Compress(); err != nil { 1224 return InvertedFiles{}, fmt.Errorf("compress %s: %w", ii.filenameBase, err) 1225 } 1226 comp.Close() 1227 comp = nil 1228 ps.Delete(p) 1229 } 1230 if decomp, err = compress.NewDecompressor(datPath); err != nil { 1231 return InvertedFiles{}, fmt.Errorf("open %s decompressor: %w", ii.filenameBase, err) 1232 } 1233 1234 idxFileName := fmt.Sprintf("%s.%d-%d.efi", ii.filenameBase, txNumFrom/ii.aggregationStep, txNumTo/ii.aggregationStep) 1235 idxPath := filepath.Join(ii.dir, idxFileName) 1236 p := ps.AddNew(idxFileName, uint64(decomp.Count()*2)) 1237 defer ps.Delete(p) 1238 if index, err = buildIndexThenOpen(ctx, decomp, idxPath, ii.tmpdir, len(keys), false /* values */, p, ii.logger, ii.noFsync); err != nil { 1239 return InvertedFiles{}, fmt.Errorf("build %s efi: %w", ii.filenameBase, err) 1240 } 1241 closeComp = false 1242 return InvertedFiles{decomp: decomp, index: index}, nil 1243 } 1244 1245 func (ii *InvertedIndex) integrateFiles(sf InvertedFiles, txNumFrom, txNumTo uint64) { 1246 fi := newFilesItem(txNumFrom, txNumTo, ii.aggregationStep) 1247 fi.decompressor = sf.decomp 1248 fi.index = sf.index 1249 ii.files.Set(fi) 1250 1251 ii.reCalcRoFiles() 1252 } 1253 1254 func (ii *InvertedIndex) warmup(ctx context.Context, txFrom, limit uint64, tx kv.Tx) error { 1255 keysCursor, err := tx.CursorDupSort(ii.indexKeysTable) 1256 if err != nil { 1257 return fmt.Errorf("create %s keys cursor: %w", ii.filenameBase, err) 1258 } 1259 defer keysCursor.Close() 1260 var txKey [8]byte 1261 binary.BigEndian.PutUint64(txKey[:], txFrom) 1262 var k, v []byte 1263 idxC, err := tx.CursorDupSort(ii.indexTable) 1264 if err != nil { 1265 return err 1266 } 1267 defer idxC.Close() 1268 k, v, err = keysCursor.Seek(txKey[:]) 1269 if err != nil { 1270 return err 1271 } 1272 if k == nil { 1273 return nil 1274 } 1275 txFrom = binary.BigEndian.Uint64(k) 1276 txTo := txFrom + ii.aggregationStep 1277 if limit != math.MaxUint64 && limit != 0 { 1278 txTo = txFrom + limit 1279 } 1280 for ; k != nil; k, v, err = keysCursor.Next() { 1281 if err != nil { 1282 return fmt.Errorf("iterate over %s keys: %w", ii.filenameBase, err) 1283 } 1284 txNum := binary.BigEndian.Uint64(k) 1285 if txNum >= txTo { 1286 break 1287 } 1288 _, _ = idxC.SeekBothRange(v, k) 1289 1290 select { 1291 case <-ctx.Done(): 1292 return ctx.Err() 1293 default: 1294 } 1295 } 1296 return nil 1297 } 1298 1299 // [txFrom; txTo) 1300 func (ii *InvertedIndex) prune(ctx context.Context, txFrom, txTo, limit uint64, logEvery *time.Ticker) error { 1301 keysCursor, err := ii.tx.RwCursorDupSort(ii.indexKeysTable) 1302 if err != nil { 1303 return fmt.Errorf("create %s keys cursor: %w", ii.filenameBase, err) 1304 } 1305 defer keysCursor.Close() 1306 var txKey [8]byte 1307 binary.BigEndian.PutUint64(txKey[:], txFrom) 1308 k, v, err := keysCursor.Seek(txKey[:]) 1309 if err != nil { 1310 return err 1311 } 1312 if k == nil { 1313 return nil 1314 } 1315 txFrom = binary.BigEndian.Uint64(k) 1316 if limit != math.MaxUint64 && limit != 0 { 1317 txTo = cmp.Min(txTo, txFrom+limit) 1318 } 1319 if txFrom >= txTo { 1320 return nil 1321 } 1322 1323 collector := etl.NewCollector("snapshots", ii.tmpdir, etl.NewOldestEntryBuffer(etl.BufferOptimalSize), ii.logger) 1324 defer collector.Close() 1325 1326 idxCForDeletes, err := ii.tx.RwCursorDupSort(ii.indexTable) 1327 if err != nil { 1328 return err 1329 } 1330 defer idxCForDeletes.Close() 1331 idxC, err := ii.tx.RwCursorDupSort(ii.indexTable) 1332 if err != nil { 1333 return err 1334 } 1335 defer idxC.Close() 1336 1337 // Invariant: if some `txNum=N` pruned - it's pruned Fully 1338 // Means: can use DeleteCurrentDuplicates all values of given `txNum` 1339 for ; k != nil; k, v, err = keysCursor.NextNoDup() { 1340 if err != nil { 1341 return err 1342 } 1343 txNum := binary.BigEndian.Uint64(k) 1344 if txNum >= txTo { 1345 break 1346 } 1347 for ; v != nil; _, v, err = keysCursor.NextDup() { 1348 if err != nil { 1349 return err 1350 } 1351 if err := collector.Collect(v, nil); err != nil { 1352 return err 1353 } 1354 } 1355 1356 // This DeleteCurrent needs to the last in the loop iteration, because it invalidates k and v 1357 if err = ii.tx.Delete(ii.indexKeysTable, k); err != nil { 1358 return err 1359 } 1360 select { 1361 case <-ctx.Done(): 1362 return ctx.Err() 1363 default: 1364 } 1365 } 1366 if err != nil { 1367 return fmt.Errorf("iterate over %s keys: %w", ii.filenameBase, err) 1368 } 1369 1370 if err := collector.Load(ii.tx, "", func(key, _ []byte, table etl.CurrentTableReader, next etl.LoadNextFunc) error { 1371 for v, err := idxC.SeekBothRange(key, txKey[:]); v != nil; _, v, err = idxC.NextDup() { 1372 if err != nil { 1373 return err 1374 } 1375 txNum := binary.BigEndian.Uint64(v) 1376 if txNum >= txTo { 1377 break 1378 } 1379 1380 if _, _, err = idxCForDeletes.SeekBothExact(key, v); err != nil { 1381 return err 1382 } 1383 if err = idxCForDeletes.DeleteCurrent(); err != nil { 1384 return err 1385 } 1386 1387 select { 1388 case <-logEvery.C: 1389 ii.logger.Info("[snapshots] prune history", "name", ii.filenameBase, "to_step", fmt.Sprintf("%.2f", float64(txTo)/float64(ii.aggregationStep)), "prefix", fmt.Sprintf("%x", key[:8])) 1390 default: 1391 } 1392 } 1393 return nil 1394 }, etl.TransformArgs{}); err != nil { 1395 return err 1396 } 1397 1398 return nil 1399 } 1400 1401 func (ii *InvertedIndex) DisableReadAhead() { 1402 ii.files.Walk(func(items []*filesItem) bool { 1403 for _, item := range items { 1404 item.decompressor.DisableReadAhead() 1405 if item.index != nil { 1406 item.index.DisableReadAhead() 1407 } 1408 } 1409 return true 1410 }) 1411 } 1412 1413 func (ii *InvertedIndex) EnableReadAhead() *InvertedIndex { 1414 ii.files.Walk(func(items []*filesItem) bool { 1415 for _, item := range items { 1416 item.decompressor.EnableReadAhead() 1417 if item.index != nil { 1418 item.index.EnableReadAhead() 1419 } 1420 } 1421 return true 1422 }) 1423 return ii 1424 } 1425 func (ii *InvertedIndex) EnableMadvWillNeed() *InvertedIndex { 1426 ii.files.Walk(func(items []*filesItem) bool { 1427 for _, item := range items { 1428 item.decompressor.EnableWillNeed() 1429 if item.index != nil { 1430 item.index.EnableWillNeed() 1431 } 1432 } 1433 return true 1434 }) 1435 return ii 1436 } 1437 func (ii *InvertedIndex) EnableMadvNormalReadAhead() *InvertedIndex { 1438 ii.files.Walk(func(items []*filesItem) bool { 1439 for _, item := range items { 1440 item.decompressor.EnableMadvNormal() 1441 if item.index != nil { 1442 item.index.EnableMadvNormal() 1443 } 1444 } 1445 return true 1446 }) 1447 return ii 1448 } 1449 1450 func (ii *InvertedIndex) collectFilesStat() (filesCount, filesSize, idxSize uint64) { 1451 if ii.files == nil { 1452 return 0, 0, 0 1453 } 1454 ii.files.Walk(func(items []*filesItem) bool { 1455 for _, item := range items { 1456 if item.index == nil { 1457 return false 1458 } 1459 filesSize += uint64(item.decompressor.Size()) 1460 idxSize += uint64(item.index.Size()) 1461 filesCount += 2 1462 } 1463 return true 1464 }) 1465 return filesCount, filesSize, idxSize 1466 } 1467 1468 func (ii *InvertedIndex) stepsRangeInDBAsStr(tx kv.Tx) string { 1469 a1, a2 := ii.stepsRangeInDB(tx) 1470 return fmt.Sprintf("%s: %.1f-%.1f", ii.filenameBase, a1, a2) 1471 } 1472 func (ii *InvertedIndex) stepsRangeInDB(tx kv.Tx) (from, to float64) { 1473 fst, _ := kv.FirstKey(tx, ii.indexKeysTable) 1474 if len(fst) > 0 { 1475 from = float64(binary.BigEndian.Uint64(fst)) / float64(ii.aggregationStep) 1476 } 1477 lst, _ := kv.LastKey(tx, ii.indexKeysTable) 1478 if len(lst) > 0 { 1479 to = float64(binary.BigEndian.Uint64(lst)) / float64(ii.aggregationStep) 1480 } 1481 return from, to 1482 }