github.com/ledgerwatch/erigon-lib@v1.0.0/state/merge.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 "os" 26 "path/filepath" 27 "strings" 28 29 "github.com/ledgerwatch/erigon-lib/common/background" 30 "github.com/ledgerwatch/log/v3" 31 32 "github.com/ledgerwatch/erigon-lib/common" 33 "github.com/ledgerwatch/erigon-lib/common/cmp" 34 "github.com/ledgerwatch/erigon-lib/compress" 35 "github.com/ledgerwatch/erigon-lib/recsplit" 36 "github.com/ledgerwatch/erigon-lib/recsplit/eliasfano32" 37 ) 38 39 func (d *Domain) endTxNumMinimax() uint64 { 40 minimax := d.History.endTxNumMinimax() 41 if max, ok := d.files.Max(); ok { 42 endTxNum := max.endTxNum 43 if minimax == 0 || endTxNum < minimax { 44 minimax = endTxNum 45 } 46 } 47 return minimax 48 } 49 50 func (ii *InvertedIndex) endTxNumMinimax() uint64 { 51 var minimax uint64 52 if max, ok := ii.files.Max(); ok { 53 endTxNum := max.endTxNum 54 if minimax == 0 || endTxNum < minimax { 55 minimax = endTxNum 56 } 57 } 58 return minimax 59 } 60 func (ii *InvertedIndex) endIndexedTxNumMinimax(needFrozen bool) uint64 { 61 var max uint64 62 ii.files.Walk(func(items []*filesItem) bool { 63 for _, item := range items { 64 if item.index == nil || (needFrozen && !item.frozen) { 65 continue 66 } 67 max = cmp.Max(max, item.endTxNum) 68 } 69 return true 70 }) 71 return max 72 } 73 74 func (h *History) endTxNumMinimax() uint64 { 75 minimax := h.InvertedIndex.endTxNumMinimax() 76 if max, ok := h.files.Max(); ok { 77 endTxNum := max.endTxNum 78 if minimax == 0 || endTxNum < minimax { 79 minimax = endTxNum 80 } 81 } 82 return minimax 83 } 84 func (h *History) endIndexedTxNumMinimax(needFrozen bool) uint64 { 85 var max uint64 86 h.files.Walk(func(items []*filesItem) bool { 87 for _, item := range items { 88 if item.index == nil || (needFrozen && !item.frozen) { 89 continue 90 } 91 max = cmp.Max(max, item.endTxNum) 92 } 93 return true 94 }) 95 return cmp.Min(max, h.InvertedIndex.endIndexedTxNumMinimax(needFrozen)) 96 } 97 98 type DomainRanges struct { 99 valuesStartTxNum uint64 100 valuesEndTxNum uint64 101 historyStartTxNum uint64 102 historyEndTxNum uint64 103 indexStartTxNum uint64 104 indexEndTxNum uint64 105 values bool 106 history bool 107 index bool 108 } 109 110 func (r DomainRanges) String() string { 111 var b strings.Builder 112 if r.values { 113 b.WriteString(fmt.Sprintf("Values: [%d, %d)", r.valuesStartTxNum, r.valuesEndTxNum)) 114 } 115 if r.history { 116 if b.Len() > 0 { 117 b.WriteString(", ") 118 } 119 b.WriteString(fmt.Sprintf("History: [%d, %d)", r.historyStartTxNum, r.historyEndTxNum)) 120 } 121 if r.index { 122 if b.Len() > 0 { 123 b.WriteString(", ") 124 } 125 b.WriteString(fmt.Sprintf("Index: [%d, %d)", r.indexStartTxNum, r.indexEndTxNum)) 126 } 127 return b.String() 128 } 129 130 func (r DomainRanges) any() bool { 131 return r.values || r.history || r.index 132 } 133 134 // findMergeRange assumes that all fTypes in d.files have items at least as far as maxEndTxNum 135 // That is why only Values type is inspected 136 func (d *Domain) findMergeRange(maxEndTxNum, maxSpan uint64) DomainRanges { 137 hr := d.History.findMergeRange(maxEndTxNum, maxSpan) 138 r := DomainRanges{ 139 historyStartTxNum: hr.historyStartTxNum, 140 historyEndTxNum: hr.historyEndTxNum, 141 history: hr.history, 142 indexStartTxNum: hr.indexStartTxNum, 143 indexEndTxNum: hr.indexEndTxNum, 144 index: hr.index, 145 } 146 d.files.Walk(func(items []*filesItem) bool { 147 for _, item := range items { 148 if item.endTxNum > maxEndTxNum { 149 return false 150 } 151 endStep := item.endTxNum / d.aggregationStep 152 spanStep := endStep & -endStep // Extract rightmost bit in the binary representation of endStep, this corresponds to size of maximally possible merge ending at endStep 153 span := cmp.Min(spanStep*d.aggregationStep, maxSpan) 154 start := item.endTxNum - span 155 if start < item.startTxNum { 156 if !r.values || start < r.valuesStartTxNum { 157 r.values = true 158 r.valuesStartTxNum = start 159 r.valuesEndTxNum = item.endTxNum 160 } 161 } 162 } 163 return true 164 }) 165 return r 166 } 167 168 // 0-1,1-2,2-3,3-4: allow merge 0-1 169 // 0-2,2-3,3-4: allow merge 0-4 170 // 0-2,2-4: allow merge 0-4 171 // 172 // 0-1,1-2,2-3: allow merge 0-2 173 // 174 // 0-2,2-3: nothing to merge 175 func (ii *InvertedIndex) findMergeRange(maxEndTxNum, maxSpan uint64) (bool, uint64, uint64) { 176 var minFound bool 177 var startTxNum, endTxNum uint64 178 ii.files.Walk(func(items []*filesItem) bool { 179 for _, item := range items { 180 if item.endTxNum > maxEndTxNum { 181 continue 182 } 183 endStep := item.endTxNum / ii.aggregationStep 184 spanStep := endStep & -endStep // Extract rightmost bit in the binary representation of endStep, this corresponds to size of maximally possible merge ending at endStep 185 span := cmp.Min(spanStep*ii.aggregationStep, maxSpan) 186 start := item.endTxNum - span 187 foundSuperSet := startTxNum == item.startTxNum && item.endTxNum >= endTxNum 188 if foundSuperSet { 189 minFound = false 190 startTxNum = start 191 endTxNum = item.endTxNum 192 } else if start < item.startTxNum { 193 if !minFound || start < startTxNum { 194 minFound = true 195 startTxNum = start 196 endTxNum = item.endTxNum 197 } 198 } 199 } 200 return true 201 }) 202 return minFound, startTxNum, endTxNum 203 } 204 205 func (ii *InvertedIndex) mergeRangesUpTo(ctx context.Context, maxTxNum, maxSpan uint64, workers int, ictx *InvertedIndexContext, ps *background.ProgressSet) (err error) { 206 closeAll := true 207 for updated, startTx, endTx := ii.findMergeRange(maxSpan, maxTxNum); updated; updated, startTx, endTx = ii.findMergeRange(maxTxNum, maxSpan) { 208 staticFiles, _ := ictx.staticFilesInRange(startTx, endTx) 209 defer func() { 210 if closeAll { 211 for _, i := range staticFiles { 212 i.decompressor.Close() 213 i.index.Close() 214 } 215 } 216 }() 217 218 mergedIndex, err := ii.mergeFiles(ctx, staticFiles, startTx, endTx, workers, ps) 219 if err != nil { 220 return err 221 } 222 defer func() { 223 if closeAll { 224 mergedIndex.decompressor.Close() 225 mergedIndex.index.Close() 226 } 227 }() 228 229 ii.integrateMergedFiles(staticFiles, mergedIndex) 230 if mergedIndex.frozen { 231 ii.cleanAfterFreeze(mergedIndex.endTxNum) 232 } 233 } 234 closeAll = false 235 return nil 236 } 237 238 type HistoryRanges struct { 239 historyStartTxNum uint64 240 historyEndTxNum uint64 241 indexStartTxNum uint64 242 indexEndTxNum uint64 243 history bool 244 index bool 245 } 246 247 func (r HistoryRanges) String(aggStep uint64) string { 248 var str string 249 if r.history { 250 str += fmt.Sprintf("hist: %d-%d, ", r.historyStartTxNum/aggStep, r.historyEndTxNum/aggStep) 251 } 252 if r.index { 253 str += fmt.Sprintf("idx: %d-%d", r.indexStartTxNum/aggStep, r.indexEndTxNum/aggStep) 254 } 255 return str 256 } 257 func (r HistoryRanges) any() bool { 258 return r.history || r.index 259 } 260 261 func (h *History) findMergeRange(maxEndTxNum, maxSpan uint64) HistoryRanges { 262 var r HistoryRanges 263 r.index, r.indexStartTxNum, r.indexEndTxNum = h.InvertedIndex.findMergeRange(maxEndTxNum, maxSpan) 264 h.files.Walk(func(items []*filesItem) bool { 265 for _, item := range items { 266 if item.endTxNum > maxEndTxNum { 267 continue 268 } 269 endStep := item.endTxNum / h.aggregationStep 270 spanStep := endStep & -endStep // Extract rightmost bit in the binary representation of endStep, this corresponds to size of maximally possible merge ending at endStep 271 span := cmp.Min(spanStep*h.aggregationStep, maxSpan) 272 start := item.endTxNum - span 273 foundSuperSet := r.indexStartTxNum == item.startTxNum && item.endTxNum >= r.historyEndTxNum 274 if foundSuperSet { 275 r.history = false 276 r.historyStartTxNum = start 277 r.historyEndTxNum = item.endTxNum 278 } else if start < item.startTxNum { 279 if !r.history || start < r.historyStartTxNum { 280 r.history = true 281 r.historyStartTxNum = start 282 r.historyEndTxNum = item.endTxNum 283 } 284 } 285 } 286 return true 287 }) 288 289 if r.history && r.index { 290 // history is behind idx: then merge only history 291 historyIsAgead := r.historyEndTxNum > r.indexEndTxNum 292 if historyIsAgead { 293 r.history, r.historyStartTxNum, r.historyEndTxNum = false, 0, 0 294 return r 295 } 296 297 historyIsBehind := r.historyEndTxNum < r.indexEndTxNum 298 if historyIsBehind { 299 r.index, r.indexStartTxNum, r.indexEndTxNum = false, 0, 0 300 return r 301 } 302 } 303 return r 304 } 305 306 // staticFilesInRange returns list of static files with txNum in specified range [startTxNum; endTxNum) 307 // files are in the descending order of endTxNum 308 func (dc *DomainContext) staticFilesInRange(r DomainRanges) (valuesFiles, indexFiles, historyFiles []*filesItem, startJ int) { 309 if r.index || r.history { 310 var err error 311 indexFiles, historyFiles, startJ, err = dc.hc.staticFilesInRange(HistoryRanges{ 312 historyStartTxNum: r.historyStartTxNum, 313 historyEndTxNum: r.historyEndTxNum, 314 history: r.history, 315 indexStartTxNum: r.indexStartTxNum, 316 indexEndTxNum: r.indexEndTxNum, 317 index: r.index, 318 }) 319 if err != nil { 320 panic(err) 321 } 322 } 323 if r.values { 324 for _, item := range dc.files { 325 if item.startTxNum < r.valuesStartTxNum { 326 startJ++ 327 continue 328 } 329 if item.endTxNum > r.valuesEndTxNum { 330 break 331 } 332 valuesFiles = append(valuesFiles, item.src) 333 } 334 for _, f := range valuesFiles { 335 if f == nil { 336 panic("must not happen") 337 } 338 } 339 } 340 return 341 } 342 343 // nolint 344 func (d *Domain) staticFilesInRange(r DomainRanges, dc *DomainContext) (valuesFiles, indexFiles, historyFiles []*filesItem, startJ int) { 345 panic("deprecated: use DomainContext.staticFilesInRange") 346 } 347 func (ic *InvertedIndexContext) staticFilesInRange(startTxNum, endTxNum uint64) ([]*filesItem, int) { 348 files := make([]*filesItem, 0, len(ic.files)) 349 var startJ int 350 351 for _, item := range ic.files { 352 if item.startTxNum < startTxNum { 353 startJ++ 354 continue 355 } 356 if item.endTxNum > endTxNum { 357 break 358 } 359 files = append(files, item.src) 360 } 361 for _, f := range files { 362 if f == nil { 363 panic("must not happen") 364 } 365 } 366 367 return files, startJ 368 } 369 370 // nolint 371 func (ii *InvertedIndex) staticFilesInRange(startTxNum, endTxNum uint64, ic *InvertedIndexContext) ([]*filesItem, int) { 372 panic("deprecated: use InvertedIndexContext.staticFilesInRange") 373 } 374 375 func (hc *HistoryContext) staticFilesInRange(r HistoryRanges) (indexFiles, historyFiles []*filesItem, startJ int, err error) { 376 if !r.history && r.index { 377 indexFiles, startJ = hc.ic.staticFilesInRange(r.indexStartTxNum, r.indexEndTxNum) 378 return indexFiles, historyFiles, startJ, nil 379 } 380 381 if r.history { 382 // Get history files from HistoryContext (no "garbage/overalps"), but index files not from InvertedIndexContext 383 // because index files may already be merged (before `kill -9`) and it means not visible in InvertedIndexContext 384 startJ = 0 385 for _, item := range hc.files { 386 if item.startTxNum < r.historyStartTxNum { 387 startJ++ 388 continue 389 } 390 if item.endTxNum > r.historyEndTxNum { 391 break 392 } 393 394 historyFiles = append(historyFiles, item.src) 395 idxFile, ok := hc.h.InvertedIndex.files.Get(item.src) 396 if ok { 397 indexFiles = append(indexFiles, idxFile) 398 } else { 399 walkErr := fmt.Errorf("History.staticFilesInRange: required file not found: %s.%d-%d.efi", hc.h.filenameBase, item.startTxNum/hc.h.aggregationStep, item.endTxNum/hc.h.aggregationStep) 400 return nil, nil, 0, walkErr 401 } 402 } 403 404 for _, f := range historyFiles { 405 if f == nil { 406 panic("must not happen") 407 } 408 } 409 if r.index && len(indexFiles) != len(historyFiles) { 410 var sIdx, sHist []string 411 for _, f := range indexFiles { 412 if f.index != nil { 413 _, fName := filepath.Split(f.index.FilePath()) 414 sIdx = append(sIdx, fmt.Sprintf("%+v", fName)) 415 } 416 } 417 for _, f := range historyFiles { 418 if f.decompressor != nil { 419 _, fName := filepath.Split(f.decompressor.FilePath()) 420 sHist = append(sHist, fmt.Sprintf("%+v", fName)) 421 } 422 } 423 log.Warn("[snapshots] something wrong with files for merge", "idx", strings.Join(sIdx, ","), "hist", strings.Join(sHist, ",")) 424 } 425 } 426 return 427 } 428 429 // nolint 430 func (h *History) staticFilesInRange(r HistoryRanges, hc *HistoryContext) (indexFiles, historyFiles []*filesItem, startJ int, err error) { 431 panic("deprecated: use HistoryContext.staticFilesInRange") 432 } 433 434 func mergeEfs(preval, val, buf []byte) ([]byte, error) { 435 preef, _ := eliasfano32.ReadEliasFano(preval) 436 ef, _ := eliasfano32.ReadEliasFano(val) 437 preIt := preef.Iterator() 438 efIt := ef.Iterator() 439 newEf := eliasfano32.NewEliasFano(preef.Count()+ef.Count(), ef.Max()) 440 for preIt.HasNext() { 441 v, err := preIt.Next() 442 if err != nil { 443 return nil, err 444 } 445 newEf.AddOffset(v) 446 } 447 for efIt.HasNext() { 448 v, err := efIt.Next() 449 if err != nil { 450 return nil, err 451 } 452 newEf.AddOffset(v) 453 } 454 newEf.Build() 455 return newEf.AppendBytes(buf), nil 456 } 457 458 func (d *Domain) mergeFiles(ctx context.Context, valuesFiles, indexFiles, historyFiles []*filesItem, r DomainRanges, workers int, ps *background.ProgressSet) (valuesIn, indexIn, historyIn *filesItem, err error) { 459 if !r.any() { 460 return 461 } 462 var comp *compress.Compressor 463 closeItem := true 464 465 defer func() { 466 if closeItem { 467 if comp != nil { 468 comp.Close() 469 } 470 if indexIn != nil { 471 if indexIn.decompressor != nil { 472 indexIn.decompressor.Close() 473 } 474 if indexIn.index != nil { 475 indexIn.index.Close() 476 } 477 if indexIn.bindex != nil { 478 indexIn.bindex.Close() 479 } 480 } 481 if historyIn != nil { 482 if historyIn.decompressor != nil { 483 historyIn.decompressor.Close() 484 } 485 if historyIn.index != nil { 486 historyIn.index.Close() 487 } 488 if historyIn.bindex != nil { 489 historyIn.bindex.Close() 490 } 491 } 492 if valuesIn != nil { 493 if valuesIn.decompressor != nil { 494 valuesIn.decompressor.Close() 495 } 496 if valuesIn.index != nil { 497 valuesIn.index.Close() 498 } 499 if valuesIn.bindex != nil { 500 valuesIn.bindex.Close() 501 } 502 } 503 } 504 }() 505 if indexIn, historyIn, err = d.History.mergeFiles(ctx, indexFiles, historyFiles, 506 HistoryRanges{ 507 historyStartTxNum: r.historyStartTxNum, 508 historyEndTxNum: r.historyEndTxNum, 509 history: r.history, 510 indexStartTxNum: r.indexStartTxNum, 511 indexEndTxNum: r.indexEndTxNum, 512 index: r.index}, workers, ps); err != nil { 513 return nil, nil, nil, err 514 } 515 if r.values { 516 for _, f := range valuesFiles { 517 defer f.decompressor.EnableMadvNormal().DisableReadAhead() 518 } 519 datFileName := fmt.Sprintf("%s.%d-%d.kv", d.filenameBase, r.valuesStartTxNum/d.aggregationStep, r.valuesEndTxNum/d.aggregationStep) 520 datPath := filepath.Join(d.dir, datFileName) 521 if comp, err = compress.NewCompressor(ctx, "merge", datPath, d.tmpdir, compress.MinPatternScore, workers, log.LvlTrace, d.logger); err != nil { 522 return nil, nil, nil, fmt.Errorf("merge %s history compressor: %w", d.filenameBase, err) 523 } 524 if d.noFsync { 525 comp.DisableFsync() 526 } 527 p := ps.AddNew("merege "+datFileName, 1) 528 defer ps.Delete(p) 529 530 var cp CursorHeap 531 heap.Init(&cp) 532 for _, item := range valuesFiles { 533 g := item.decompressor.MakeGetter() 534 g.Reset(0) 535 if g.HasNext() { 536 key, _ := g.NextUncompressed() 537 var val []byte 538 if d.compressVals { 539 val, _ = g.Next(nil) 540 } else { 541 val, _ = g.NextUncompressed() 542 } 543 heap.Push(&cp, &CursorItem{ 544 t: FILE_CURSOR, 545 dg: g, 546 key: key, 547 val: val, 548 endTxNum: item.endTxNum, 549 reverse: true, 550 }) 551 } 552 } 553 keyCount := 0 554 // In the loop below, the pair `keyBuf=>valBuf` is always 1 item behind `lastKey=>lastVal`. 555 // `lastKey` and `lastVal` are taken from the top of the multi-way merge (assisted by the CursorHeap cp), but not processed right away 556 // instead, the pair from the previous iteration is processed first - `keyBuf=>valBuf`. After that, `keyBuf` and `valBuf` are assigned 557 // to `lastKey` and `lastVal` correspondingly, and the next step of multi-way merge happens. Therefore, after the multi-way merge loop 558 // (when CursorHeap cp is empty), there is a need to process the last pair `keyBuf=>valBuf`, because it was one step behind 559 var keyBuf, valBuf []byte 560 for cp.Len() > 0 { 561 lastKey := common.Copy(cp[0].key) 562 lastVal := common.Copy(cp[0].val) 563 // Advance all the items that have this key (including the top) 564 for cp.Len() > 0 && bytes.Equal(cp[0].key, lastKey) { 565 ci1 := cp[0] 566 if ci1.dg.HasNext() { 567 ci1.key, _ = ci1.dg.NextUncompressed() 568 if d.compressVals { 569 ci1.val, _ = ci1.dg.Next(ci1.val[:0]) 570 } else { 571 ci1.val, _ = ci1.dg.NextUncompressed() 572 } 573 heap.Fix(&cp, 0) 574 } else { 575 heap.Pop(&cp) 576 } 577 } 578 579 // empty value means deletion 580 deleted := r.valuesStartTxNum == 0 && len(lastVal) == 0 581 if !deleted { 582 if keyBuf != nil { 583 if err = comp.AddUncompressedWord(keyBuf); err != nil { 584 return nil, nil, nil, err 585 } 586 keyCount++ // Only counting keys, not values 587 switch d.compressVals { 588 case true: 589 if err = comp.AddWord(valBuf); err != nil { 590 return nil, nil, nil, err 591 } 592 default: 593 if err = comp.AddUncompressedWord(valBuf); err != nil { 594 return nil, nil, nil, err 595 } 596 } 597 } 598 keyBuf = append(keyBuf[:0], lastKey...) 599 valBuf = append(valBuf[:0], lastVal...) 600 } 601 } 602 if keyBuf != nil { 603 if err = comp.AddUncompressedWord(keyBuf); err != nil { 604 return nil, nil, nil, err 605 } 606 keyCount++ // Only counting keys, not values 607 if d.compressVals { 608 if err = comp.AddWord(valBuf); err != nil { 609 return nil, nil, nil, err 610 } 611 } else { 612 if err = comp.AddUncompressedWord(valBuf); err != nil { 613 return nil, nil, nil, err 614 } 615 } 616 } 617 if err = comp.Compress(); err != nil { 618 return nil, nil, nil, err 619 } 620 comp.Close() 621 comp = nil 622 ps.Delete(p) 623 valuesIn = newFilesItem(r.valuesStartTxNum, r.valuesEndTxNum, d.aggregationStep) 624 if valuesIn.decompressor, err = compress.NewDecompressor(datPath); err != nil { 625 return nil, nil, nil, fmt.Errorf("merge %s decompressor [%d-%d]: %w", d.filenameBase, r.valuesStartTxNum, r.valuesEndTxNum, err) 626 } 627 628 idxFileName := fmt.Sprintf("%s.%d-%d.kvi", d.filenameBase, r.valuesStartTxNum/d.aggregationStep, r.valuesEndTxNum/d.aggregationStep) 629 idxPath := filepath.Join(d.dir, idxFileName) 630 p = ps.AddNew("merge "+idxFileName, uint64(keyCount*2)) 631 defer ps.Delete(p) 632 ps.Delete(p) 633 634 // if valuesIn.index, err = buildIndex(valuesIn.decompressor, idxPath, d.dir, keyCount, false /* values */); err != nil { 635 if valuesIn.index, err = buildIndexThenOpen(ctx, valuesIn.decompressor, idxPath, d.tmpdir, keyCount, false /* values */, p, d.logger, d.noFsync); err != nil { 636 return nil, nil, nil, fmt.Errorf("merge %s buildIndex [%d-%d]: %w", d.filenameBase, r.valuesStartTxNum, r.valuesEndTxNum, err) 637 } 638 639 btFileName := strings.TrimSuffix(idxFileName, "kvi") + "bt" 640 p = ps.AddNew(btFileName, uint64(keyCount*2)) 641 defer ps.Delete(p) 642 btPath := filepath.Join(d.dir, btFileName) 643 err = BuildBtreeIndexWithDecompressor(btPath, valuesIn.decompressor, p, d.tmpdir, d.logger) 644 if err != nil { 645 return nil, nil, nil, fmt.Errorf("merge %s btindex [%d-%d]: %w", d.filenameBase, r.valuesStartTxNum, r.valuesEndTxNum, err) 646 } 647 648 bt, err := OpenBtreeIndexWithDecompressor(btPath, DefaultBtreeM, valuesIn.decompressor) 649 if err != nil { 650 return nil, nil, nil, fmt.Errorf("merge %s btindex2 [%d-%d]: %w", d.filenameBase, r.valuesStartTxNum, r.valuesEndTxNum, err) 651 } 652 valuesIn.bindex = bt 653 } 654 closeItem = false 655 d.stats.MergesCount++ 656 return 657 } 658 659 func (ii *InvertedIndex) mergeFiles(ctx context.Context, files []*filesItem, startTxNum, endTxNum uint64, workers int, ps *background.ProgressSet) (*filesItem, error) { 660 for _, h := range files { 661 defer h.decompressor.EnableMadvNormal().DisableReadAhead() 662 } 663 664 var outItem *filesItem 665 var comp *compress.Compressor 666 var decomp *compress.Decompressor 667 var err error 668 var closeItem = true 669 defer func() { 670 if closeItem { 671 if comp != nil { 672 comp.Close() 673 } 674 if decomp != nil { 675 decomp.Close() 676 } 677 if outItem != nil { 678 if outItem.decompressor != nil { 679 outItem.decompressor.Close() 680 } 681 if outItem.index != nil { 682 outItem.index.Close() 683 } 684 outItem = nil 685 } 686 } 687 }() 688 if ctx.Err() != nil { 689 return nil, ctx.Err() 690 } 691 692 datFileName := fmt.Sprintf("%s.%d-%d.ef", ii.filenameBase, startTxNum/ii.aggregationStep, endTxNum/ii.aggregationStep) 693 datPath := filepath.Join(ii.dir, datFileName) 694 if comp, err = compress.NewCompressor(ctx, "Snapshots merge", datPath, ii.tmpdir, compress.MinPatternScore, workers, log.LvlTrace, ii.logger); err != nil { 695 return nil, fmt.Errorf("merge %s inverted index compressor: %w", ii.filenameBase, err) 696 } 697 if ii.noFsync { 698 comp.DisableFsync() 699 } 700 p := ps.AddNew("merge "+datFileName, 1) 701 defer ps.Delete(p) 702 703 var cp CursorHeap 704 heap.Init(&cp) 705 706 for _, item := range files { 707 g := item.decompressor.MakeGetter() 708 g.Reset(0) 709 if g.HasNext() { 710 key, _ := g.Next(nil) 711 val, _ := g.Next(nil) 712 //fmt.Printf("heap push %s [%d] %x\n", item.decompressor.FilePath(), item.endTxNum, key) 713 heap.Push(&cp, &CursorItem{ 714 t: FILE_CURSOR, 715 dg: g, 716 key: key, 717 val: val, 718 endTxNum: item.endTxNum, 719 reverse: true, 720 }) 721 } 722 } 723 keyCount := 0 724 725 // In the loop below, the pair `keyBuf=>valBuf` is always 1 item behind `lastKey=>lastVal`. 726 // `lastKey` and `lastVal` are taken from the top of the multi-way merge (assisted by the CursorHeap cp), but not processed right away 727 // instead, the pair from the previous iteration is processed first - `keyBuf=>valBuf`. After that, `keyBuf` and `valBuf` are assigned 728 // to `lastKey` and `lastVal` correspondingly, and the next step of multi-way merge happens. Therefore, after the multi-way merge loop 729 // (when CursorHeap cp is empty), there is a need to process the last pair `keyBuf=>valBuf`, because it was one step behind 730 var keyBuf, valBuf []byte 731 for cp.Len() > 0 { 732 lastKey := common.Copy(cp[0].key) 733 lastVal := common.Copy(cp[0].val) 734 var mergedOnce bool 735 736 // Advance all the items that have this key (including the top) 737 for cp.Len() > 0 && bytes.Equal(cp[0].key, lastKey) { 738 ci1 := cp[0] 739 if mergedOnce { 740 if lastVal, err = mergeEfs(ci1.val, lastVal, nil); err != nil { 741 return nil, fmt.Errorf("merge %s inverted index: %w", ii.filenameBase, err) 742 } 743 } else { 744 mergedOnce = true 745 } 746 //fmt.Printf("multi-way %s [%d] %x\n", ii.indexKeysTable, ci1.endTxNum, ci1.key) 747 if ci1.dg.HasNext() { 748 ci1.key, _ = ci1.dg.NextUncompressed() 749 ci1.val, _ = ci1.dg.NextUncompressed() 750 //fmt.Printf("heap next push %s [%d] %x\n", ii.indexKeysTable, ci1.endTxNum, ci1.key) 751 heap.Fix(&cp, 0) 752 } else { 753 heap.Pop(&cp) 754 } 755 } 756 if keyBuf != nil { 757 if err = comp.AddUncompressedWord(keyBuf); err != nil { 758 return nil, err 759 } 760 keyCount++ // Only counting keys, not values 761 if err = comp.AddUncompressedWord(valBuf); err != nil { 762 return nil, err 763 } 764 } 765 keyBuf = append(keyBuf[:0], lastKey...) 766 valBuf = append(valBuf[:0], lastVal...) 767 } 768 if keyBuf != nil { 769 if err = comp.AddUncompressedWord(keyBuf); err != nil { 770 return nil, err 771 } 772 keyCount++ // Only counting keys, not values 773 if err = comp.AddUncompressedWord(valBuf); err != nil { 774 return nil, err 775 } 776 } 777 if err = comp.Compress(); err != nil { 778 return nil, err 779 } 780 comp.Close() 781 comp = nil 782 outItem = newFilesItem(startTxNum, endTxNum, ii.aggregationStep) 783 if outItem.decompressor, err = compress.NewDecompressor(datPath); err != nil { 784 return nil, fmt.Errorf("merge %s decompressor [%d-%d]: %w", ii.filenameBase, startTxNum, endTxNum, err) 785 } 786 ps.Delete(p) 787 788 idxFileName := fmt.Sprintf("%s.%d-%d.efi", ii.filenameBase, startTxNum/ii.aggregationStep, endTxNum/ii.aggregationStep) 789 idxPath := filepath.Join(ii.dir, idxFileName) 790 p = ps.AddNew("merge "+idxFileName, uint64(outItem.decompressor.Count()*2)) 791 defer ps.Delete(p) 792 if outItem.index, err = buildIndexThenOpen(ctx, outItem.decompressor, idxPath, ii.tmpdir, keyCount, false /* values */, p, ii.logger, ii.noFsync); err != nil { 793 return nil, fmt.Errorf("merge %s buildIndex [%d-%d]: %w", ii.filenameBase, startTxNum, endTxNum, err) 794 } 795 closeItem = false 796 return outItem, nil 797 } 798 799 func (h *History) mergeFiles(ctx context.Context, indexFiles, historyFiles []*filesItem, r HistoryRanges, workers int, ps *background.ProgressSet) (indexIn, historyIn *filesItem, err error) { 800 if !r.any() { 801 return nil, nil, nil 802 } 803 var closeIndex = true 804 defer func() { 805 if closeIndex { 806 if indexIn != nil { 807 indexIn.decompressor.Close() 808 indexIn.index.Close() 809 } 810 } 811 }() 812 if indexIn, err = h.InvertedIndex.mergeFiles(ctx, indexFiles, r.indexStartTxNum, r.indexEndTxNum, workers, ps); err != nil { 813 return nil, nil, err 814 } 815 if r.history { 816 for _, f := range indexFiles { 817 defer f.decompressor.EnableMadvNormal().DisableReadAhead() 818 } 819 for _, f := range historyFiles { 820 defer f.decompressor.EnableMadvNormal().DisableReadAhead() 821 } 822 823 var comp *compress.Compressor 824 var decomp *compress.Decompressor 825 var rs *recsplit.RecSplit 826 var index *recsplit.Index 827 var closeItem = true 828 defer func() { 829 if closeItem { 830 if comp != nil { 831 comp.Close() 832 } 833 if decomp != nil { 834 decomp.Close() 835 } 836 if rs != nil { 837 rs.Close() 838 } 839 if index != nil { 840 index.Close() 841 } 842 if historyIn != nil { 843 if historyIn.decompressor != nil { 844 historyIn.decompressor.Close() 845 } 846 if historyIn.index != nil { 847 historyIn.index.Close() 848 } 849 } 850 } 851 }() 852 datFileName := fmt.Sprintf("%s.%d-%d.v", h.filenameBase, r.historyStartTxNum/h.aggregationStep, r.historyEndTxNum/h.aggregationStep) 853 idxFileName := fmt.Sprintf("%s.%d-%d.vi", h.filenameBase, r.historyStartTxNum/h.aggregationStep, r.historyEndTxNum/h.aggregationStep) 854 datPath := filepath.Join(h.dir, datFileName) 855 idxPath := filepath.Join(h.dir, idxFileName) 856 if comp, err = compress.NewCompressor(ctx, "merge", datPath, h.tmpdir, compress.MinPatternScore, workers, log.LvlTrace, h.logger); err != nil { 857 return nil, nil, fmt.Errorf("merge %s history compressor: %w", h.filenameBase, err) 858 } 859 if h.noFsync { 860 comp.DisableFsync() 861 } 862 p := ps.AddNew("merge "+datFileName, 1) 863 defer ps.Delete(p) 864 var cp CursorHeap 865 heap.Init(&cp) 866 for _, item := range indexFiles { 867 g := item.decompressor.MakeGetter() 868 g.Reset(0) 869 if g.HasNext() { 870 var g2 *compress.Getter 871 for _, hi := range historyFiles { // full-scan, because it's ok to have different amount files. by unclean-shutdown. 872 if hi.startTxNum == item.startTxNum && hi.endTxNum == item.endTxNum { 873 g2 = hi.decompressor.MakeGetter() 874 break 875 } 876 } 877 if g2 == nil { 878 panic(fmt.Sprintf("for file: %s, not found corresponding file to merge", g.FileName())) 879 } 880 key, _ := g.NextUncompressed() 881 val, _ := g.NextUncompressed() 882 heap.Push(&cp, &CursorItem{ 883 t: FILE_CURSOR, 884 dg: g, 885 dg2: g2, 886 key: key, 887 val: val, 888 endTxNum: item.endTxNum, 889 reverse: false, 890 }) 891 } 892 } 893 // In the loop below, the pair `keyBuf=>valBuf` is always 1 item behind `lastKey=>lastVal`. 894 // `lastKey` and `lastVal` are taken from the top of the multi-way merge (assisted by the CursorHeap cp), but not processed right away 895 // instead, the pair from the previous iteration is processed first - `keyBuf=>valBuf`. After that, `keyBuf` and `valBuf` are assigned 896 // to `lastKey` and `lastVal` correspondingly, and the next step of multi-way merge happens. Therefore, after the multi-way merge loop 897 // (when CursorHeap cp is empty), there is a need to process the last pair `keyBuf=>valBuf`, because it was one step behind 898 var valBuf []byte 899 var keyCount int 900 for cp.Len() > 0 { 901 lastKey := common.Copy(cp[0].key) 902 // Advance all the items that have this key (including the top) 903 for cp.Len() > 0 && bytes.Equal(cp[0].key, lastKey) { 904 ci1 := cp[0] 905 count := eliasfano32.Count(ci1.val) 906 for i := uint64(0); i < count; i++ { 907 if !ci1.dg2.HasNext() { 908 panic(fmt.Errorf("assert: no value??? %s, i=%d, count=%d, lastKey=%x, ci1.key=%x", ci1.dg2.FileName(), i, count, lastKey, ci1.key)) 909 } 910 911 if h.compressVals { 912 valBuf, _ = ci1.dg2.Next(valBuf[:0]) 913 if err = comp.AddWord(valBuf); err != nil { 914 return nil, nil, err 915 } 916 } else { 917 valBuf, _ = ci1.dg2.NextUncompressed() 918 if err = comp.AddUncompressedWord(valBuf); err != nil { 919 return nil, nil, err 920 } 921 } 922 } 923 keyCount += int(count) 924 if ci1.dg.HasNext() { 925 ci1.key, _ = ci1.dg.NextUncompressed() 926 ci1.val, _ = ci1.dg.NextUncompressed() 927 heap.Fix(&cp, 0) 928 } else { 929 heap.Remove(&cp, 0) 930 } 931 } 932 } 933 if err = comp.Compress(); err != nil { 934 return nil, nil, err 935 } 936 comp.Close() 937 comp = nil 938 if decomp, err = compress.NewDecompressor(datPath); err != nil { 939 return nil, nil, err 940 } 941 ps.Delete(p) 942 943 p = ps.AddNew("merge "+idxFileName, uint64(2*keyCount)) 944 defer ps.Delete(p) 945 if rs, err = recsplit.NewRecSplit(recsplit.RecSplitArgs{ 946 KeyCount: keyCount, 947 Enums: false, 948 BucketSize: 2000, 949 LeafSize: 8, 950 TmpDir: h.tmpdir, 951 IndexFile: idxPath, 952 }, h.logger); err != nil { 953 return nil, nil, fmt.Errorf("create recsplit: %w", err) 954 } 955 rs.LogLvl(log.LvlTrace) 956 if h.noFsync { 957 rs.DisableFsync() 958 } 959 var historyKey []byte 960 var txKey [8]byte 961 var valOffset uint64 962 g := indexIn.decompressor.MakeGetter() 963 g2 := decomp.MakeGetter() 964 var keyBuf []byte 965 for { 966 g.Reset(0) 967 g2.Reset(0) 968 valOffset = 0 969 for g.HasNext() { 970 keyBuf, _ = g.NextUncompressed() 971 valBuf, _ = g.NextUncompressed() 972 ef, _ := eliasfano32.ReadEliasFano(valBuf) 973 efIt := ef.Iterator() 974 for efIt.HasNext() { 975 txNum, _ := efIt.Next() 976 binary.BigEndian.PutUint64(txKey[:], txNum) 977 historyKey = append(append(historyKey[:0], txKey[:]...), keyBuf...) 978 if err = rs.AddKey(historyKey, valOffset); err != nil { 979 return nil, nil, err 980 } 981 if h.compressVals { 982 valOffset, _ = g2.Skip() 983 } else { 984 valOffset, _ = g2.SkipUncompressed() 985 } 986 } 987 p.Processed.Add(1) 988 } 989 if err = rs.Build(ctx); err != nil { 990 if rs.Collision() { 991 log.Info("Building recsplit. Collision happened. It's ok. Restarting...") 992 rs.ResetNextSalt() 993 } else { 994 return nil, nil, fmt.Errorf("build %s idx: %w", h.filenameBase, err) 995 } 996 } else { 997 break 998 } 999 } 1000 rs.Close() 1001 rs = nil 1002 if index, err = recsplit.OpenIndex(idxPath); err != nil { 1003 return nil, nil, fmt.Errorf("open %s idx: %w", h.filenameBase, err) 1004 } 1005 historyIn = newFilesItem(r.historyStartTxNum, r.historyEndTxNum, h.aggregationStep) 1006 historyIn.decompressor = decomp 1007 historyIn.index = index 1008 1009 closeItem = false 1010 } 1011 1012 closeIndex = false 1013 return 1014 } 1015 1016 func (d *Domain) integrateMergedFiles(valuesOuts, indexOuts, historyOuts []*filesItem, valuesIn, indexIn, historyIn *filesItem) { 1017 d.History.integrateMergedFiles(indexOuts, historyOuts, indexIn, historyIn) 1018 if valuesIn != nil { 1019 d.files.Set(valuesIn) 1020 1021 // `kill -9` may leave some garbage 1022 // but it still may be useful for merges, until we finish merge frozen file 1023 if historyIn != nil && historyIn.frozen { 1024 d.files.Walk(func(items []*filesItem) bool { 1025 for _, item := range items { 1026 if item.frozen || item.endTxNum > valuesIn.endTxNum { 1027 continue 1028 } 1029 valuesOuts = append(valuesOuts, item) 1030 } 1031 return true 1032 }) 1033 } 1034 } 1035 for _, out := range valuesOuts { 1036 if out == nil { 1037 panic("must not happen") 1038 } 1039 d.files.Delete(out) 1040 out.canDelete.Store(true) 1041 } 1042 d.reCalcRoFiles() 1043 } 1044 1045 func (ii *InvertedIndex) integrateMergedFiles(outs []*filesItem, in *filesItem) { 1046 if in != nil { 1047 ii.files.Set(in) 1048 1049 // `kill -9` may leave some garbage 1050 // but it still may be useful for merges, until we finish merge frozen file 1051 if in.frozen { 1052 ii.files.Walk(func(items []*filesItem) bool { 1053 for _, item := range items { 1054 if item.frozen || item.endTxNum > in.endTxNum { 1055 continue 1056 } 1057 outs = append(outs, item) 1058 } 1059 return true 1060 }) 1061 } 1062 } 1063 for _, out := range outs { 1064 if out == nil { 1065 panic("must not happen: " + ii.filenameBase) 1066 } 1067 ii.files.Delete(out) 1068 out.canDelete.Store(true) 1069 } 1070 ii.reCalcRoFiles() 1071 } 1072 1073 func (h *History) integrateMergedFiles(indexOuts, historyOuts []*filesItem, indexIn, historyIn *filesItem) { 1074 h.InvertedIndex.integrateMergedFiles(indexOuts, indexIn) 1075 //TODO: handle collision 1076 if historyIn != nil { 1077 h.files.Set(historyIn) 1078 1079 // `kill -9` may leave some garbage 1080 // but it still may be useful for merges, until we finish merge frozen file 1081 if historyIn.frozen { 1082 h.files.Walk(func(items []*filesItem) bool { 1083 for _, item := range items { 1084 if item.frozen || item.endTxNum > historyIn.endTxNum { 1085 continue 1086 } 1087 historyOuts = append(historyOuts, item) 1088 } 1089 return true 1090 }) 1091 } 1092 } 1093 for _, out := range historyOuts { 1094 if out == nil { 1095 panic("must not happen: " + h.filenameBase) 1096 } 1097 h.files.Delete(out) 1098 out.canDelete.Store(true) 1099 } 1100 h.reCalcRoFiles() 1101 } 1102 1103 // nolint 1104 func (dc *DomainContext) frozenTo() uint64 { 1105 if len(dc.files) == 0 { 1106 return 0 1107 } 1108 for i := len(dc.files) - 1; i >= 0; i-- { 1109 if dc.files[i].src.frozen { 1110 return cmp.Min(dc.files[i].endTxNum, dc.hc.frozenTo()) 1111 } 1112 } 1113 return 0 1114 } 1115 1116 func (hc *HistoryContext) frozenTo() uint64 { 1117 if len(hc.files) == 0 { 1118 return 0 1119 } 1120 for i := len(hc.files) - 1; i >= 0; i-- { 1121 if hc.files[i].src.frozen { 1122 return cmp.Min(hc.files[i].endTxNum, hc.ic.frozenTo()) 1123 } 1124 } 1125 return 0 1126 } 1127 func (ic *InvertedIndexContext) frozenTo() uint64 { 1128 if len(ic.files) == 0 { 1129 return 0 1130 } 1131 for i := len(ic.files) - 1; i >= 0; i-- { 1132 if ic.files[i].src.frozen { 1133 return ic.files[i].endTxNum 1134 } 1135 } 1136 return 0 1137 } 1138 1139 func (d *Domain) cleanAfterFreeze(frozenTo uint64) { 1140 if frozenTo == 0 { 1141 return 1142 } 1143 1144 var outs []*filesItem 1145 // `kill -9` may leave some garbage 1146 // but it may be useful for merges, until merge `frozen` file 1147 d.files.Walk(func(items []*filesItem) bool { 1148 for _, item := range items { 1149 if item.frozen || item.endTxNum > frozenTo { 1150 continue 1151 } 1152 outs = append(outs, item) 1153 } 1154 return true 1155 }) 1156 1157 for _, out := range outs { 1158 if out == nil { 1159 panic("must not happen: " + d.filenameBase) 1160 } 1161 d.files.Delete(out) 1162 if out.refcount.Load() == 0 { 1163 // if it has no readers (invisible even for us) - it's safe to remove file right here 1164 out.closeFilesAndRemove() 1165 } 1166 out.canDelete.Store(true) 1167 } 1168 d.History.cleanAfterFreeze(frozenTo) 1169 } 1170 1171 // cleanAfterFreeze - mark all small files before `f` as `canDelete=true` 1172 func (h *History) cleanAfterFreeze(frozenTo uint64) { 1173 if frozenTo == 0 { 1174 return 1175 } 1176 //if h.filenameBase == "accounts" { 1177 // log.Warn("[history] History.cleanAfterFreeze", "frozenTo", frozenTo/h.aggregationStep, "stack", dbg.Stack()) 1178 //} 1179 var outs []*filesItem 1180 // `kill -9` may leave some garbage 1181 // but it may be useful for merges, until merge `frozen` file 1182 h.files.Walk(func(items []*filesItem) bool { 1183 for _, item := range items { 1184 if item.frozen || item.endTxNum > frozenTo { 1185 continue 1186 } 1187 outs = append(outs, item) 1188 } 1189 return true 1190 }) 1191 1192 for _, out := range outs { 1193 if out == nil { 1194 panic("must not happen: " + h.filenameBase) 1195 } 1196 out.canDelete.Store(true) 1197 1198 //if out.refcount.Load() == 0 { 1199 // if h.filenameBase == "accounts" { 1200 // log.Warn("[history] History.cleanAfterFreeze: immediately delete", "name", out.decompressor.FileName()) 1201 // } 1202 //} else { 1203 // if h.filenameBase == "accounts" { 1204 // log.Warn("[history] History.cleanAfterFreeze: mark as 'canDelete=true'", "name", out.decompressor.FileName()) 1205 // } 1206 //} 1207 1208 // if it has no readers (invisible even for us) - it's safe to remove file right here 1209 if out.refcount.Load() == 0 { 1210 out.closeFilesAndRemove() 1211 } 1212 h.files.Delete(out) 1213 } 1214 h.InvertedIndex.cleanAfterFreeze(frozenTo) 1215 } 1216 1217 // cleanAfterFreeze - mark all small files before `f` as `canDelete=true` 1218 func (ii *InvertedIndex) cleanAfterFreeze(frozenTo uint64) { 1219 if frozenTo == 0 { 1220 return 1221 } 1222 var outs []*filesItem 1223 // `kill -9` may leave some garbage 1224 // but it may be useful for merges, until merge `frozen` file 1225 ii.files.Walk(func(items []*filesItem) bool { 1226 for _, item := range items { 1227 if item.frozen || item.endTxNum > frozenTo { 1228 continue 1229 } 1230 outs = append(outs, item) 1231 } 1232 return true 1233 }) 1234 1235 for _, out := range outs { 1236 if out == nil { 1237 panic("must not happen: " + ii.filenameBase) 1238 } 1239 out.canDelete.Store(true) 1240 if out.refcount.Load() == 0 { 1241 // if it has no readers (invisible even for us) - it's safe to remove file right here 1242 out.closeFilesAndRemove() 1243 } 1244 ii.files.Delete(out) 1245 } 1246 } 1247 1248 // nolint 1249 func (d *Domain) deleteGarbageFiles() { 1250 for _, item := range d.garbageFiles { 1251 // paranoic-mode: don't delete frozen files 1252 steps := item.endTxNum/d.aggregationStep - item.startTxNum/d.aggregationStep 1253 if steps%StepsInBiggestFile == 0 { 1254 continue 1255 } 1256 f1 := fmt.Sprintf("%s.%d-%d.kv", d.filenameBase, item.startTxNum/d.aggregationStep, item.endTxNum/d.aggregationStep) 1257 os.Remove(filepath.Join(d.dir, f1)) 1258 log.Debug("[snapshots] delete garbage", f1) 1259 f2 := fmt.Sprintf("%s.%d-%d.kvi", d.filenameBase, item.startTxNum/d.aggregationStep, item.endTxNum/d.aggregationStep) 1260 os.Remove(filepath.Join(d.dir, f2)) 1261 log.Debug("[snapshots] delete garbage", f2) 1262 } 1263 d.garbageFiles = nil 1264 d.History.deleteGarbageFiles() 1265 } 1266 func (h *History) deleteGarbageFiles() { 1267 for _, item := range h.garbageFiles { 1268 // paranoic-mode: don't delete frozen files 1269 if item.endTxNum/h.aggregationStep-item.startTxNum/h.aggregationStep == StepsInBiggestFile { 1270 continue 1271 } 1272 f1 := fmt.Sprintf("%s.%d-%d.v", h.filenameBase, item.startTxNum/h.aggregationStep, item.endTxNum/h.aggregationStep) 1273 os.Remove(filepath.Join(h.dir, f1)) 1274 log.Debug("[snapshots] delete garbage", f1) 1275 f2 := fmt.Sprintf("%s.%d-%d.vi", h.filenameBase, item.startTxNum/h.aggregationStep, item.endTxNum/h.aggregationStep) 1276 os.Remove(filepath.Join(h.dir, f2)) 1277 log.Debug("[snapshots] delete garbage", f2) 1278 } 1279 h.garbageFiles = nil 1280 h.InvertedIndex.deleteGarbageFiles() 1281 } 1282 func (ii *InvertedIndex) deleteGarbageFiles() { 1283 for _, item := range ii.garbageFiles { 1284 // paranoic-mode: don't delete frozen files 1285 if item.endTxNum/ii.aggregationStep-item.startTxNum/ii.aggregationStep == StepsInBiggestFile { 1286 continue 1287 } 1288 f1 := fmt.Sprintf("%s.%d-%d.ef", ii.filenameBase, item.startTxNum/ii.aggregationStep, item.endTxNum/ii.aggregationStep) 1289 os.Remove(filepath.Join(ii.dir, f1)) 1290 log.Debug("[snapshots] delete garbage", f1) 1291 f2 := fmt.Sprintf("%s.%d-%d.efi", ii.filenameBase, item.startTxNum/ii.aggregationStep, item.endTxNum/ii.aggregationStep) 1292 os.Remove(filepath.Join(ii.dir, f2)) 1293 log.Debug("[snapshots] delete garbage", f2) 1294 } 1295 ii.garbageFiles = nil 1296 }