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  }