github.com/codysnider/go-ethereum@v1.10.18-0.20220420071915-14f4ae99222a/core/rawdb/freezer.go (about)

     1  // Copyright 2019 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package rawdb
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"math"
    24  	"os"
    25  	"path/filepath"
    26  	"sync"
    27  	"sync/atomic"
    28  	"time"
    29  
    30  	"github.com/ethereum/go-ethereum/common"
    31  	"github.com/ethereum/go-ethereum/ethdb"
    32  	"github.com/ethereum/go-ethereum/log"
    33  	"github.com/ethereum/go-ethereum/metrics"
    34  	"github.com/ethereum/go-ethereum/params"
    35  	"github.com/prometheus/tsdb/fileutil"
    36  )
    37  
    38  var (
    39  	// errReadOnly is returned if the freezer is opened in read only mode. All the
    40  	// mutations are disallowed.
    41  	errReadOnly = errors.New("read only")
    42  
    43  	// errUnknownTable is returned if the user attempts to read from a table that is
    44  	// not tracked by the freezer.
    45  	errUnknownTable = errors.New("unknown table")
    46  
    47  	// errOutOrderInsertion is returned if the user attempts to inject out-of-order
    48  	// binary blobs into the freezer.
    49  	errOutOrderInsertion = errors.New("the append operation is out-order")
    50  
    51  	// errSymlinkDatadir is returned if the ancient directory specified by user
    52  	// is a symbolic link.
    53  	errSymlinkDatadir = errors.New("symbolic link datadir is not supported")
    54  )
    55  
    56  const (
    57  	// freezerRecheckInterval is the frequency to check the key-value database for
    58  	// chain progression that might permit new blocks to be frozen into immutable
    59  	// storage.
    60  	freezerRecheckInterval = time.Minute
    61  
    62  	// freezerBatchLimit is the maximum number of blocks to freeze in one batch
    63  	// before doing an fsync and deleting it from the key-value store.
    64  	freezerBatchLimit = 30000
    65  
    66  	// freezerTableSize defines the maximum size of freezer data files.
    67  	freezerTableSize = 2 * 1000 * 1000 * 1000
    68  )
    69  
    70  // freezer is a memory mapped append-only database to store immutable chain data
    71  // into flat files:
    72  //
    73  // - The append only nature ensures that disk writes are minimized.
    74  // - The memory mapping ensures we can max out system memory for caching without
    75  //   reserving it for go-ethereum. This would also reduce the memory requirements
    76  //   of Geth, and thus also GC overhead.
    77  type freezer struct {
    78  	// WARNING: The `frozen` field is accessed atomically. On 32 bit platforms, only
    79  	// 64-bit aligned fields can be atomic. The struct is guaranteed to be so aligned,
    80  	// so take advantage of that (https://golang.org/pkg/sync/atomic/#pkg-note-BUG).
    81  	frozen    uint64 // Number of blocks already frozen
    82  	tail      uint64 // Number of the first stored item in the freezer
    83  	threshold uint64 // Number of recent blocks not to freeze (params.FullImmutabilityThreshold apart from tests)
    84  
    85  	// This lock synchronizes writers and the truncate operation, as well as
    86  	// the "atomic" (batched) read operations.
    87  	writeLock  sync.RWMutex
    88  	writeBatch *freezerBatch
    89  
    90  	readonly     bool
    91  	tables       map[string]*freezerTable // Data tables for storing everything
    92  	instanceLock fileutil.Releaser        // File-system lock to prevent double opens
    93  
    94  	trigger chan chan struct{} // Manual blocking freeze trigger, test determinism
    95  
    96  	quit      chan struct{}
    97  	wg        sync.WaitGroup
    98  	closeOnce sync.Once
    99  }
   100  
   101  // newFreezer creates a chain freezer that moves ancient chain data into
   102  // append-only flat file containers.
   103  //
   104  // The 'tables' argument defines the data tables. If the value of a map
   105  // entry is true, snappy compression is disabled for the table.
   106  func newFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]bool) (*freezer, error) {
   107  	// Create the initial freezer object
   108  	var (
   109  		readMeter  = metrics.NewRegisteredMeter(namespace+"ancient/read", nil)
   110  		writeMeter = metrics.NewRegisteredMeter(namespace+"ancient/write", nil)
   111  		sizeGauge  = metrics.NewRegisteredGauge(namespace+"ancient/size", nil)
   112  	)
   113  	// Ensure the datadir is not a symbolic link if it exists.
   114  	if info, err := os.Lstat(datadir); !os.IsNotExist(err) {
   115  		if info.Mode()&os.ModeSymlink != 0 {
   116  			log.Warn("Symbolic link ancient database is not supported", "path", datadir)
   117  			return nil, errSymlinkDatadir
   118  		}
   119  	}
   120  	// Leveldb uses LOCK as the filelock filename. To prevent the
   121  	// name collision, we use FLOCK as the lock name.
   122  	lock, _, err := fileutil.Flock(filepath.Join(datadir, "FLOCK"))
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	// Open all the supported data tables
   127  	freezer := &freezer{
   128  		readonly:     readonly,
   129  		threshold:    params.FullImmutabilityThreshold,
   130  		tables:       make(map[string]*freezerTable),
   131  		instanceLock: lock,
   132  		trigger:      make(chan chan struct{}),
   133  		quit:         make(chan struct{}),
   134  	}
   135  
   136  	// Create the tables.
   137  	for name, disableSnappy := range tables {
   138  		table, err := newTable(datadir, name, readMeter, writeMeter, sizeGauge, maxTableSize, disableSnappy, readonly)
   139  		if err != nil {
   140  			for _, table := range freezer.tables {
   141  				table.Close()
   142  			}
   143  			lock.Release()
   144  			return nil, err
   145  		}
   146  		freezer.tables[name] = table
   147  	}
   148  
   149  	if freezer.readonly {
   150  		// In readonly mode only validate, don't truncate.
   151  		// validate also sets `freezer.frozen`.
   152  		err = freezer.validate()
   153  	} else {
   154  		// Truncate all tables to common length.
   155  		err = freezer.repair()
   156  	}
   157  	if err != nil {
   158  		for _, table := range freezer.tables {
   159  			table.Close()
   160  		}
   161  		lock.Release()
   162  		return nil, err
   163  	}
   164  
   165  	// Create the write batch.
   166  	freezer.writeBatch = newFreezerBatch(freezer)
   167  
   168  	log.Info("Opened ancient database", "database", datadir, "readonly", readonly)
   169  	return freezer, nil
   170  }
   171  
   172  // Close terminates the chain freezer, unmapping all the data files.
   173  func (f *freezer) Close() error {
   174  	f.writeLock.Lock()
   175  	defer f.writeLock.Unlock()
   176  
   177  	var errs []error
   178  	f.closeOnce.Do(func() {
   179  		close(f.quit)
   180  		// Wait for any background freezing to stop
   181  		f.wg.Wait()
   182  		for _, table := range f.tables {
   183  			if err := table.Close(); err != nil {
   184  				errs = append(errs, err)
   185  			}
   186  		}
   187  		if err := f.instanceLock.Release(); err != nil {
   188  			errs = append(errs, err)
   189  		}
   190  	})
   191  	if errs != nil {
   192  		return fmt.Errorf("%v", errs)
   193  	}
   194  	return nil
   195  }
   196  
   197  // HasAncient returns an indicator whether the specified ancient data exists
   198  // in the freezer.
   199  func (f *freezer) HasAncient(kind string, number uint64) (bool, error) {
   200  	if table := f.tables[kind]; table != nil {
   201  		return table.has(number), nil
   202  	}
   203  	return false, nil
   204  }
   205  
   206  // Ancient retrieves an ancient binary blob from the append-only immutable files.
   207  func (f *freezer) Ancient(kind string, number uint64) ([]byte, error) {
   208  	if table := f.tables[kind]; table != nil {
   209  		return table.Retrieve(number)
   210  	}
   211  	return nil, errUnknownTable
   212  }
   213  
   214  // AncientRange retrieves multiple items in sequence, starting from the index 'start'.
   215  // It will return
   216  //  - at most 'max' items,
   217  //  - at least 1 item (even if exceeding the maxByteSize), but will otherwise
   218  //   return as many items as fit into maxByteSize.
   219  func (f *freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) {
   220  	if table := f.tables[kind]; table != nil {
   221  		return table.RetrieveItems(start, count, maxBytes)
   222  	}
   223  	return nil, errUnknownTable
   224  }
   225  
   226  // Ancients returns the length of the frozen items.
   227  func (f *freezer) Ancients() (uint64, error) {
   228  	return atomic.LoadUint64(&f.frozen), nil
   229  }
   230  
   231  // Tail returns the number of first stored item in the freezer.
   232  func (f *freezer) Tail() (uint64, error) {
   233  	return atomic.LoadUint64(&f.tail), nil
   234  }
   235  
   236  // AncientSize returns the ancient size of the specified category.
   237  func (f *freezer) AncientSize(kind string) (uint64, error) {
   238  	// This needs the write lock to avoid data races on table fields.
   239  	// Speed doesn't matter here, AncientSize is for debugging.
   240  	f.writeLock.RLock()
   241  	defer f.writeLock.RUnlock()
   242  
   243  	if table := f.tables[kind]; table != nil {
   244  		return table.size()
   245  	}
   246  	return 0, errUnknownTable
   247  }
   248  
   249  // ReadAncients runs the given read operation while ensuring that no writes take place
   250  // on the underlying freezer.
   251  func (f *freezer) ReadAncients(fn func(ethdb.AncientReader) error) (err error) {
   252  	f.writeLock.RLock()
   253  	defer f.writeLock.RUnlock()
   254  	return fn(f)
   255  }
   256  
   257  // ModifyAncients runs the given write operation.
   258  func (f *freezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) {
   259  	if f.readonly {
   260  		return 0, errReadOnly
   261  	}
   262  	f.writeLock.Lock()
   263  	defer f.writeLock.Unlock()
   264  
   265  	// Roll back all tables to the starting position in case of error.
   266  	prevItem := f.frozen
   267  	defer func() {
   268  		if err != nil {
   269  			// The write operation has failed. Go back to the previous item position.
   270  			for name, table := range f.tables {
   271  				err := table.truncateHead(prevItem)
   272  				if err != nil {
   273  					log.Error("Freezer table roll-back failed", "table", name, "index", prevItem, "err", err)
   274  				}
   275  			}
   276  		}
   277  	}()
   278  
   279  	f.writeBatch.reset()
   280  	if err := fn(f.writeBatch); err != nil {
   281  		return 0, err
   282  	}
   283  	item, writeSize, err := f.writeBatch.commit()
   284  	if err != nil {
   285  		return 0, err
   286  	}
   287  	atomic.StoreUint64(&f.frozen, item)
   288  	return writeSize, nil
   289  }
   290  
   291  // TruncateHead discards any recent data above the provided threshold number.
   292  func (f *freezer) TruncateHead(items uint64) error {
   293  	if f.readonly {
   294  		return errReadOnly
   295  	}
   296  	f.writeLock.Lock()
   297  	defer f.writeLock.Unlock()
   298  
   299  	if atomic.LoadUint64(&f.frozen) <= items {
   300  		return nil
   301  	}
   302  	for _, table := range f.tables {
   303  		if err := table.truncateHead(items); err != nil {
   304  			return err
   305  		}
   306  	}
   307  	atomic.StoreUint64(&f.frozen, items)
   308  	return nil
   309  }
   310  
   311  // TruncateTail discards any recent data below the provided threshold number.
   312  func (f *freezer) TruncateTail(tail uint64) error {
   313  	if f.readonly {
   314  		return errReadOnly
   315  	}
   316  	f.writeLock.Lock()
   317  	defer f.writeLock.Unlock()
   318  
   319  	if atomic.LoadUint64(&f.tail) >= tail {
   320  		return nil
   321  	}
   322  	for _, table := range f.tables {
   323  		if err := table.truncateTail(tail); err != nil {
   324  			return err
   325  		}
   326  	}
   327  	atomic.StoreUint64(&f.tail, tail)
   328  	return nil
   329  }
   330  
   331  // Sync flushes all data tables to disk.
   332  func (f *freezer) Sync() error {
   333  	var errs []error
   334  	for _, table := range f.tables {
   335  		if err := table.Sync(); err != nil {
   336  			errs = append(errs, err)
   337  		}
   338  	}
   339  	if errs != nil {
   340  		return fmt.Errorf("%v", errs)
   341  	}
   342  	return nil
   343  }
   344  
   345  // validate checks that every table has the same length.
   346  // Used instead of `repair` in readonly mode.
   347  func (f *freezer) validate() error {
   348  	if len(f.tables) == 0 {
   349  		return nil
   350  	}
   351  	var (
   352  		length uint64
   353  		name   string
   354  	)
   355  	// Hack to get length of any table
   356  	for kind, table := range f.tables {
   357  		length = atomic.LoadUint64(&table.items)
   358  		name = kind
   359  		break
   360  	}
   361  	// Now check every table against that length
   362  	for kind, table := range f.tables {
   363  		items := atomic.LoadUint64(&table.items)
   364  		if length != items {
   365  			return fmt.Errorf("freezer tables %s and %s have differing lengths: %d != %d", kind, name, items, length)
   366  		}
   367  	}
   368  	atomic.StoreUint64(&f.frozen, length)
   369  	return nil
   370  }
   371  
   372  // repair truncates all data tables to the same length.
   373  func (f *freezer) repair() error {
   374  	var (
   375  		head = uint64(math.MaxUint64)
   376  		tail = uint64(0)
   377  	)
   378  	for _, table := range f.tables {
   379  		items := atomic.LoadUint64(&table.items)
   380  		if head > items {
   381  			head = items
   382  		}
   383  		hidden := atomic.LoadUint64(&table.itemHidden)
   384  		if hidden > tail {
   385  			tail = hidden
   386  		}
   387  	}
   388  	for _, table := range f.tables {
   389  		if err := table.truncateHead(head); err != nil {
   390  			return err
   391  		}
   392  		if err := table.truncateTail(tail); err != nil {
   393  			return err
   394  		}
   395  	}
   396  	atomic.StoreUint64(&f.frozen, head)
   397  	atomic.StoreUint64(&f.tail, tail)
   398  	return nil
   399  }
   400  
   401  // freeze is a background thread that periodically checks the blockchain for any
   402  // import progress and moves ancient data from the fast database into the freezer.
   403  //
   404  // This functionality is deliberately broken off from block importing to avoid
   405  // incurring additional data shuffling delays on block propagation.
   406  func (f *freezer) freeze(db ethdb.KeyValueStore) {
   407  	nfdb := &nofreezedb{KeyValueStore: db}
   408  
   409  	var (
   410  		backoff   bool
   411  		triggered chan struct{} // Used in tests
   412  	)
   413  	for {
   414  		select {
   415  		case <-f.quit:
   416  			log.Info("Freezer shutting down")
   417  			return
   418  		default:
   419  		}
   420  		if backoff {
   421  			// If we were doing a manual trigger, notify it
   422  			if triggered != nil {
   423  				triggered <- struct{}{}
   424  				triggered = nil
   425  			}
   426  			select {
   427  			case <-time.NewTimer(freezerRecheckInterval).C:
   428  				backoff = false
   429  			case triggered = <-f.trigger:
   430  				backoff = false
   431  			case <-f.quit:
   432  				return
   433  			}
   434  		}
   435  		// Retrieve the freezing threshold.
   436  		hash := ReadHeadBlockHash(nfdb)
   437  		if hash == (common.Hash{}) {
   438  			log.Debug("Current full block hash unavailable") // new chain, empty database
   439  			backoff = true
   440  			continue
   441  		}
   442  		number := ReadHeaderNumber(nfdb, hash)
   443  		threshold := atomic.LoadUint64(&f.threshold)
   444  
   445  		switch {
   446  		case number == nil:
   447  			log.Error("Current full block number unavailable", "hash", hash)
   448  			backoff = true
   449  			continue
   450  
   451  		case *number < threshold:
   452  			log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", threshold)
   453  			backoff = true
   454  			continue
   455  
   456  		case *number-threshold <= f.frozen:
   457  			log.Debug("Ancient blocks frozen already", "number", *number, "hash", hash, "frozen", f.frozen)
   458  			backoff = true
   459  			continue
   460  		}
   461  		head := ReadHeader(nfdb, hash, *number)
   462  		if head == nil {
   463  			log.Error("Current full block unavailable", "number", *number, "hash", hash)
   464  			backoff = true
   465  			continue
   466  		}
   467  
   468  		// Seems we have data ready to be frozen, process in usable batches
   469  		var (
   470  			start    = time.Now()
   471  			first, _ = f.Ancients()
   472  			limit    = *number - threshold
   473  		)
   474  		if limit-first > freezerBatchLimit {
   475  			limit = first + freezerBatchLimit
   476  		}
   477  		ancients, err := f.freezeRange(nfdb, first, limit)
   478  		if err != nil {
   479  			log.Error("Error in block freeze operation", "err", err)
   480  			backoff = true
   481  			continue
   482  		}
   483  
   484  		// Batch of blocks have been frozen, flush them before wiping from leveldb
   485  		if err := f.Sync(); err != nil {
   486  			log.Crit("Failed to flush frozen tables", "err", err)
   487  		}
   488  
   489  		// Wipe out all data from the active database
   490  		batch := db.NewBatch()
   491  		for i := 0; i < len(ancients); i++ {
   492  			// Always keep the genesis block in active database
   493  			if first+uint64(i) != 0 {
   494  				DeleteBlockWithoutNumber(batch, ancients[i], first+uint64(i))
   495  				DeleteCanonicalHash(batch, first+uint64(i))
   496  			}
   497  		}
   498  		if err := batch.Write(); err != nil {
   499  			log.Crit("Failed to delete frozen canonical blocks", "err", err)
   500  		}
   501  		batch.Reset()
   502  
   503  		// Wipe out side chains also and track dangling side chains
   504  		var dangling []common.Hash
   505  		for number := first; number < f.frozen; number++ {
   506  			// Always keep the genesis block in active database
   507  			if number != 0 {
   508  				dangling = ReadAllHashes(db, number)
   509  				for _, hash := range dangling {
   510  					log.Trace("Deleting side chain", "number", number, "hash", hash)
   511  					DeleteBlock(batch, hash, number)
   512  				}
   513  			}
   514  		}
   515  		if err := batch.Write(); err != nil {
   516  			log.Crit("Failed to delete frozen side blocks", "err", err)
   517  		}
   518  		batch.Reset()
   519  
   520  		// Step into the future and delete and dangling side chains
   521  		if f.frozen > 0 {
   522  			tip := f.frozen
   523  			for len(dangling) > 0 {
   524  				drop := make(map[common.Hash]struct{})
   525  				for _, hash := range dangling {
   526  					log.Debug("Dangling parent from freezer", "number", tip-1, "hash", hash)
   527  					drop[hash] = struct{}{}
   528  				}
   529  				children := ReadAllHashes(db, tip)
   530  				for i := 0; i < len(children); i++ {
   531  					// Dig up the child and ensure it's dangling
   532  					child := ReadHeader(nfdb, children[i], tip)
   533  					if child == nil {
   534  						log.Error("Missing dangling header", "number", tip, "hash", children[i])
   535  						continue
   536  					}
   537  					if _, ok := drop[child.ParentHash]; !ok {
   538  						children = append(children[:i], children[i+1:]...)
   539  						i--
   540  						continue
   541  					}
   542  					// Delete all block data associated with the child
   543  					log.Debug("Deleting dangling block", "number", tip, "hash", children[i], "parent", child.ParentHash)
   544  					DeleteBlock(batch, children[i], tip)
   545  				}
   546  				dangling = children
   547  				tip++
   548  			}
   549  			if err := batch.Write(); err != nil {
   550  				log.Crit("Failed to delete dangling side blocks", "err", err)
   551  			}
   552  		}
   553  
   554  		// Log something friendly for the user
   555  		context := []interface{}{
   556  			"blocks", f.frozen - first, "elapsed", common.PrettyDuration(time.Since(start)), "number", f.frozen - 1,
   557  		}
   558  		if n := len(ancients); n > 0 {
   559  			context = append(context, []interface{}{"hash", ancients[n-1]}...)
   560  		}
   561  		log.Info("Deep froze chain segment", context...)
   562  
   563  		// Avoid database thrashing with tiny writes
   564  		if f.frozen-first < freezerBatchLimit {
   565  			backoff = true
   566  		}
   567  	}
   568  }
   569  
   570  func (f *freezer) freezeRange(nfdb *nofreezedb, number, limit uint64) (hashes []common.Hash, err error) {
   571  	hashes = make([]common.Hash, 0, limit-number)
   572  
   573  	_, err = f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
   574  		for ; number <= limit; number++ {
   575  			// Retrieve all the components of the canonical block.
   576  			hash := ReadCanonicalHash(nfdb, number)
   577  			if hash == (common.Hash{}) {
   578  				return fmt.Errorf("canonical hash missing, can't freeze block %d", number)
   579  			}
   580  			header := ReadHeaderRLP(nfdb, hash, number)
   581  			if len(header) == 0 {
   582  				return fmt.Errorf("block header missing, can't freeze block %d", number)
   583  			}
   584  			body := ReadBodyRLP(nfdb, hash, number)
   585  			if len(body) == 0 {
   586  				return fmt.Errorf("block body missing, can't freeze block %d", number)
   587  			}
   588  			receipts := ReadReceiptsRLP(nfdb, hash, number)
   589  			if len(receipts) == 0 {
   590  				return fmt.Errorf("block receipts missing, can't freeze block %d", number)
   591  			}
   592  			td := ReadTdRLP(nfdb, hash, number)
   593  			if len(td) == 0 {
   594  				return fmt.Errorf("total difficulty missing, can't freeze block %d", number)
   595  			}
   596  
   597  			// Write to the batch.
   598  			if err := op.AppendRaw(freezerHashTable, number, hash[:]); err != nil {
   599  				return fmt.Errorf("can't write hash to freezer: %v", err)
   600  			}
   601  			if err := op.AppendRaw(freezerHeaderTable, number, header); err != nil {
   602  				return fmt.Errorf("can't write header to freezer: %v", err)
   603  			}
   604  			if err := op.AppendRaw(freezerBodiesTable, number, body); err != nil {
   605  				return fmt.Errorf("can't write body to freezer: %v", err)
   606  			}
   607  			if err := op.AppendRaw(freezerReceiptTable, number, receipts); err != nil {
   608  				return fmt.Errorf("can't write receipts to freezer: %v", err)
   609  			}
   610  			if err := op.AppendRaw(freezerDifficultyTable, number, td); err != nil {
   611  				return fmt.Errorf("can't write td to freezer: %v", err)
   612  			}
   613  
   614  			hashes = append(hashes, hash)
   615  		}
   616  		return nil
   617  	})
   618  
   619  	return hashes, err
   620  }
   621  
   622  // convertLegacyFn takes a raw freezer entry in an older format and
   623  // returns it in the new format.
   624  type convertLegacyFn = func([]byte) ([]byte, error)
   625  
   626  // MigrateTable processes the entries in a given table in sequence
   627  // converting them to a new format if they're of an old format.
   628  func (f *freezer) MigrateTable(kind string, convert convertLegacyFn) error {
   629  	if f.readonly {
   630  		return errReadOnly
   631  	}
   632  	f.writeLock.Lock()
   633  	defer f.writeLock.Unlock()
   634  
   635  	table, ok := f.tables[kind]
   636  	if !ok {
   637  		return errUnknownTable
   638  	}
   639  	// forEach iterates every entry in the table serially and in order, calling `fn`
   640  	// with the item as argument. If `fn` returns an error the iteration stops
   641  	// and that error will be returned.
   642  	forEach := func(t *freezerTable, offset uint64, fn func(uint64, []byte) error) error {
   643  		var (
   644  			items     = atomic.LoadUint64(&t.items)
   645  			batchSize = uint64(1024)
   646  			maxBytes  = uint64(1024 * 1024)
   647  		)
   648  		for i := offset; i < items; {
   649  			if i+batchSize > items {
   650  				batchSize = items - i
   651  			}
   652  			data, err := t.RetrieveItems(i, batchSize, maxBytes)
   653  			if err != nil {
   654  				return err
   655  			}
   656  			for j, item := range data {
   657  				if err := fn(i+uint64(j), item); err != nil {
   658  					return err
   659  				}
   660  			}
   661  			i += uint64(len(data))
   662  		}
   663  		return nil
   664  	}
   665  	// TODO(s1na): This is a sanity-check since as of now no process does tail-deletion. But the migration
   666  	// process assumes no deletion at tail and needs to be modified to account for that.
   667  	if table.itemOffset > 0 || table.itemHidden > 0 {
   668  		return fmt.Errorf("migration not supported for tail-deleted freezers")
   669  	}
   670  	ancientsPath := filepath.Dir(table.index.Name())
   671  	// Set up new dir for the migrated table, the content of which
   672  	// we'll at the end move over to the ancients dir.
   673  	migrationPath := filepath.Join(ancientsPath, "migration")
   674  	newTable, err := NewFreezerTable(migrationPath, kind, FreezerNoSnappy[kind], false)
   675  	if err != nil {
   676  		return err
   677  	}
   678  	var (
   679  		batch  = newTable.newBatch()
   680  		out    []byte
   681  		start  = time.Now()
   682  		logged = time.Now()
   683  		offset = newTable.items
   684  	)
   685  	if offset > 0 {
   686  		log.Info("found previous migration attempt", "migrated", offset)
   687  	}
   688  	// Iterate through entries and transform them
   689  	if err := forEach(table, offset, func(i uint64, blob []byte) error {
   690  		if i%10000 == 0 && time.Since(logged) > 16*time.Second {
   691  			log.Info("Processing legacy elements", "count", i, "elapsed", common.PrettyDuration(time.Since(start)))
   692  			logged = time.Now()
   693  		}
   694  		out, err = convert(blob)
   695  		if err != nil {
   696  			return err
   697  		}
   698  		if err := batch.AppendRaw(i, out); err != nil {
   699  			return err
   700  		}
   701  		return nil
   702  	}); err != nil {
   703  		return err
   704  	}
   705  	if err := batch.commit(); err != nil {
   706  		return err
   707  	}
   708  	log.Info("Replacing old table files with migrated ones", "elapsed", common.PrettyDuration(time.Since(start)))
   709  	// Release and delete old table files. Note this won't
   710  	// delete the index file.
   711  	table.releaseFilesAfter(0, true)
   712  
   713  	if err := newTable.Close(); err != nil {
   714  		return err
   715  	}
   716  	files, err := ioutil.ReadDir(migrationPath)
   717  	if err != nil {
   718  		return err
   719  	}
   720  	// Move migrated files to ancients dir.
   721  	for _, f := range files {
   722  		// This will replace the old index file as a side-effect.
   723  		if err := os.Rename(filepath.Join(migrationPath, f.Name()), filepath.Join(ancientsPath, f.Name())); err != nil {
   724  			return err
   725  		}
   726  	}
   727  	// Delete by now empty dir.
   728  	if err := os.Remove(migrationPath); err != nil {
   729  		return err
   730  	}
   731  
   732  	return nil
   733  }