github.com/jimmyx0x/go-ethereum@v1.10.28/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  	"math"
    23  	"os"
    24  	"path/filepath"
    25  	"sync"
    26  	"sync/atomic"
    27  	"time"
    28  
    29  	"github.com/ethereum/go-ethereum/common"
    30  	"github.com/ethereum/go-ethereum/ethdb"
    31  	"github.com/ethereum/go-ethereum/log"
    32  	"github.com/ethereum/go-ethereum/metrics"
    33  	"github.com/prometheus/tsdb/fileutil"
    34  )
    35  
    36  var (
    37  	// errReadOnly is returned if the freezer is opened in read only mode. All the
    38  	// mutations are disallowed.
    39  	errReadOnly = errors.New("read only")
    40  
    41  	// errUnknownTable is returned if the user attempts to read from a table that is
    42  	// not tracked by the freezer.
    43  	errUnknownTable = errors.New("unknown table")
    44  
    45  	// errOutOrderInsertion is returned if the user attempts to inject out-of-order
    46  	// binary blobs into the freezer.
    47  	errOutOrderInsertion = errors.New("the append operation is out-order")
    48  
    49  	// errSymlinkDatadir is returned if the ancient directory specified by user
    50  	// is a symbolic link.
    51  	errSymlinkDatadir = errors.New("symbolic link datadir is not supported")
    52  )
    53  
    54  // freezerTableSize defines the maximum size of freezer data files.
    55  const freezerTableSize = 2 * 1000 * 1000 * 1000
    56  
    57  // Freezer is a memory mapped append-only database to store immutable ordered
    58  // data into flat files:
    59  //
    60  //   - The append-only nature ensures that disk writes are minimized.
    61  //   - The memory mapping ensures we can max out system memory for caching without
    62  //     reserving it for go-ethereum. This would also reduce the memory requirements
    63  //     of Geth, and thus also GC overhead.
    64  type Freezer struct {
    65  	// WARNING: The `frozen` and `tail` fields are accessed atomically. On 32 bit platforms, only
    66  	// 64-bit aligned fields can be atomic. The struct is guaranteed to be so aligned,
    67  	// so take advantage of that (https://golang.org/pkg/sync/atomic/#pkg-note-BUG).
    68  	frozen uint64 // Number of blocks already frozen
    69  	tail   uint64 // Number of the first stored item in the freezer
    70  
    71  	// This lock synchronizes writers and the truncate operation, as well as
    72  	// the "atomic" (batched) read operations.
    73  	writeLock  sync.RWMutex
    74  	writeBatch *freezerBatch
    75  
    76  	readonly     bool
    77  	tables       map[string]*freezerTable // Data tables for storing everything
    78  	instanceLock fileutil.Releaser        // File-system lock to prevent double opens
    79  	closeOnce    sync.Once
    80  }
    81  
    82  // NewFreezer creates a freezer instance for maintaining immutable ordered
    83  // data according to the given parameters.
    84  //
    85  // The 'tables' argument defines the data tables. If the value of a map
    86  // entry is true, snappy compression is disabled for the table.
    87  func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]bool) (*Freezer, error) {
    88  	// Create the initial freezer object
    89  	var (
    90  		readMeter  = metrics.NewRegisteredMeter(namespace+"ancient/read", nil)
    91  		writeMeter = metrics.NewRegisteredMeter(namespace+"ancient/write", nil)
    92  		sizeGauge  = metrics.NewRegisteredGauge(namespace+"ancient/size", nil)
    93  	)
    94  	// Ensure the datadir is not a symbolic link if it exists.
    95  	if info, err := os.Lstat(datadir); !os.IsNotExist(err) {
    96  		if info.Mode()&os.ModeSymlink != 0 {
    97  			log.Warn("Symbolic link ancient database is not supported", "path", datadir)
    98  			return nil, errSymlinkDatadir
    99  		}
   100  	}
   101  	// Leveldb uses LOCK as the filelock filename. To prevent the
   102  	// name collision, we use FLOCK as the lock name.
   103  	lock, _, err := fileutil.Flock(filepath.Join(datadir, "FLOCK"))
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	// Open all the supported data tables
   108  	freezer := &Freezer{
   109  		readonly:     readonly,
   110  		tables:       make(map[string]*freezerTable),
   111  		instanceLock: lock,
   112  	}
   113  
   114  	// Create the tables.
   115  	for name, disableSnappy := range tables {
   116  		table, err := newTable(datadir, name, readMeter, writeMeter, sizeGauge, maxTableSize, disableSnappy, readonly)
   117  		if err != nil {
   118  			for _, table := range freezer.tables {
   119  				table.Close()
   120  			}
   121  			lock.Release()
   122  			return nil, err
   123  		}
   124  		freezer.tables[name] = table
   125  	}
   126  
   127  	if freezer.readonly {
   128  		// In readonly mode only validate, don't truncate.
   129  		// validate also sets `freezer.frozen`.
   130  		err = freezer.validate()
   131  	} else {
   132  		// Truncate all tables to common length.
   133  		err = freezer.repair()
   134  	}
   135  	if err != nil {
   136  		for _, table := range freezer.tables {
   137  			table.Close()
   138  		}
   139  		lock.Release()
   140  		return nil, err
   141  	}
   142  
   143  	// Create the write batch.
   144  	freezer.writeBatch = newFreezerBatch(freezer)
   145  
   146  	log.Info("Opened ancient database", "database", datadir, "readonly", readonly)
   147  	return freezer, nil
   148  }
   149  
   150  // Close terminates the chain freezer, unmapping all the data files.
   151  func (f *Freezer) Close() error {
   152  	f.writeLock.Lock()
   153  	defer f.writeLock.Unlock()
   154  
   155  	var errs []error
   156  	f.closeOnce.Do(func() {
   157  		for _, table := range f.tables {
   158  			if err := table.Close(); err != nil {
   159  				errs = append(errs, err)
   160  			}
   161  		}
   162  		if err := f.instanceLock.Release(); err != nil {
   163  			errs = append(errs, err)
   164  		}
   165  	})
   166  	if errs != nil {
   167  		return fmt.Errorf("%v", errs)
   168  	}
   169  	return nil
   170  }
   171  
   172  // HasAncient returns an indicator whether the specified ancient data exists
   173  // in the freezer.
   174  func (f *Freezer) HasAncient(kind string, number uint64) (bool, error) {
   175  	if table := f.tables[kind]; table != nil {
   176  		return table.has(number), nil
   177  	}
   178  	return false, nil
   179  }
   180  
   181  // Ancient retrieves an ancient binary blob from the append-only immutable files.
   182  func (f *Freezer) Ancient(kind string, number uint64) ([]byte, error) {
   183  	if table := f.tables[kind]; table != nil {
   184  		return table.Retrieve(number)
   185  	}
   186  	return nil, errUnknownTable
   187  }
   188  
   189  // AncientRange retrieves multiple items in sequence, starting from the index 'start'.
   190  // It will return
   191  //   - at most 'max' items,
   192  //   - at least 1 item (even if exceeding the maxByteSize), but will otherwise
   193  //     return as many items as fit into maxByteSize.
   194  func (f *Freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) {
   195  	if table := f.tables[kind]; table != nil {
   196  		return table.RetrieveItems(start, count, maxBytes)
   197  	}
   198  	return nil, errUnknownTable
   199  }
   200  
   201  // Ancients returns the length of the frozen items.
   202  func (f *Freezer) Ancients() (uint64, error) {
   203  	return atomic.LoadUint64(&f.frozen), nil
   204  }
   205  
   206  // Tail returns the number of first stored item in the freezer.
   207  func (f *Freezer) Tail() (uint64, error) {
   208  	return atomic.LoadUint64(&f.tail), nil
   209  }
   210  
   211  // AncientSize returns the ancient size of the specified category.
   212  func (f *Freezer) AncientSize(kind string) (uint64, error) {
   213  	// This needs the write lock to avoid data races on table fields.
   214  	// Speed doesn't matter here, AncientSize is for debugging.
   215  	f.writeLock.RLock()
   216  	defer f.writeLock.RUnlock()
   217  
   218  	if table := f.tables[kind]; table != nil {
   219  		return table.size()
   220  	}
   221  	return 0, errUnknownTable
   222  }
   223  
   224  // ReadAncients runs the given read operation while ensuring that no writes take place
   225  // on the underlying freezer.
   226  func (f *Freezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) {
   227  	f.writeLock.RLock()
   228  	defer f.writeLock.RUnlock()
   229  
   230  	return fn(f)
   231  }
   232  
   233  // ModifyAncients runs the given write operation.
   234  func (f *Freezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize int64, err error) {
   235  	if f.readonly {
   236  		return 0, errReadOnly
   237  	}
   238  	f.writeLock.Lock()
   239  	defer f.writeLock.Unlock()
   240  
   241  	// Roll back all tables to the starting position in case of error.
   242  	prevItem := atomic.LoadUint64(&f.frozen)
   243  	defer func() {
   244  		if err != nil {
   245  			// The write operation has failed. Go back to the previous item position.
   246  			for name, table := range f.tables {
   247  				err := table.truncateHead(prevItem)
   248  				if err != nil {
   249  					log.Error("Freezer table roll-back failed", "table", name, "index", prevItem, "err", err)
   250  				}
   251  			}
   252  		}
   253  	}()
   254  
   255  	f.writeBatch.reset()
   256  	if err := fn(f.writeBatch); err != nil {
   257  		return 0, err
   258  	}
   259  	item, writeSize, err := f.writeBatch.commit()
   260  	if err != nil {
   261  		return 0, err
   262  	}
   263  	atomic.StoreUint64(&f.frozen, item)
   264  	return writeSize, nil
   265  }
   266  
   267  // TruncateHead discards any recent data above the provided threshold number.
   268  func (f *Freezer) TruncateHead(items uint64) error {
   269  	if f.readonly {
   270  		return errReadOnly
   271  	}
   272  	f.writeLock.Lock()
   273  	defer f.writeLock.Unlock()
   274  
   275  	if atomic.LoadUint64(&f.frozen) <= items {
   276  		return nil
   277  	}
   278  	for _, table := range f.tables {
   279  		if err := table.truncateHead(items); err != nil {
   280  			return err
   281  		}
   282  	}
   283  	atomic.StoreUint64(&f.frozen, items)
   284  	return nil
   285  }
   286  
   287  // TruncateTail discards any recent data below the provided threshold number.
   288  func (f *Freezer) TruncateTail(tail uint64) error {
   289  	if f.readonly {
   290  		return errReadOnly
   291  	}
   292  	f.writeLock.Lock()
   293  	defer f.writeLock.Unlock()
   294  
   295  	if atomic.LoadUint64(&f.tail) >= tail {
   296  		return nil
   297  	}
   298  	for _, table := range f.tables {
   299  		if err := table.truncateTail(tail); err != nil {
   300  			return err
   301  		}
   302  	}
   303  	atomic.StoreUint64(&f.tail, tail)
   304  	return nil
   305  }
   306  
   307  // Sync flushes all data tables to disk.
   308  func (f *Freezer) Sync() error {
   309  	var errs []error
   310  	for _, table := range f.tables {
   311  		if err := table.Sync(); err != nil {
   312  			errs = append(errs, err)
   313  		}
   314  	}
   315  	if errs != nil {
   316  		return fmt.Errorf("%v", errs)
   317  	}
   318  	return nil
   319  }
   320  
   321  // validate checks that every table has the same boundary.
   322  // Used instead of `repair` in readonly mode.
   323  func (f *Freezer) validate() error {
   324  	if len(f.tables) == 0 {
   325  		return nil
   326  	}
   327  	var (
   328  		head uint64
   329  		tail uint64
   330  		name string
   331  	)
   332  	// Hack to get boundary of any table
   333  	for kind, table := range f.tables {
   334  		head = atomic.LoadUint64(&table.items)
   335  		tail = atomic.LoadUint64(&table.itemHidden)
   336  		name = kind
   337  		break
   338  	}
   339  	// Now check every table against those boundaries.
   340  	for kind, table := range f.tables {
   341  		if head != atomic.LoadUint64(&table.items) {
   342  			return fmt.Errorf("freezer tables %s and %s have differing head: %d != %d", kind, name, atomic.LoadUint64(&table.items), head)
   343  		}
   344  		if tail != atomic.LoadUint64(&table.itemHidden) {
   345  			return fmt.Errorf("freezer tables %s and %s have differing tail: %d != %d", kind, name, atomic.LoadUint64(&table.itemHidden), tail)
   346  		}
   347  	}
   348  	atomic.StoreUint64(&f.frozen, head)
   349  	atomic.StoreUint64(&f.tail, tail)
   350  	return nil
   351  }
   352  
   353  // repair truncates all data tables to the same length.
   354  func (f *Freezer) repair() error {
   355  	var (
   356  		head = uint64(math.MaxUint64)
   357  		tail = uint64(0)
   358  	)
   359  	for _, table := range f.tables {
   360  		items := atomic.LoadUint64(&table.items)
   361  		if head > items {
   362  			head = items
   363  		}
   364  		hidden := atomic.LoadUint64(&table.itemHidden)
   365  		if hidden > tail {
   366  			tail = hidden
   367  		}
   368  	}
   369  	for _, table := range f.tables {
   370  		if err := table.truncateHead(head); err != nil {
   371  			return err
   372  		}
   373  		if err := table.truncateTail(tail); err != nil {
   374  			return err
   375  		}
   376  	}
   377  	atomic.StoreUint64(&f.frozen, head)
   378  	atomic.StoreUint64(&f.tail, tail)
   379  	return nil
   380  }
   381  
   382  // convertLegacyFn takes a raw freezer entry in an older format and
   383  // returns it in the new format.
   384  type convertLegacyFn = func([]byte) ([]byte, error)
   385  
   386  // MigrateTable processes the entries in a given table in sequence
   387  // converting them to a new format if they're of an old format.
   388  func (f *Freezer) MigrateTable(kind string, convert convertLegacyFn) error {
   389  	if f.readonly {
   390  		return errReadOnly
   391  	}
   392  	f.writeLock.Lock()
   393  	defer f.writeLock.Unlock()
   394  
   395  	table, ok := f.tables[kind]
   396  	if !ok {
   397  		return errUnknownTable
   398  	}
   399  	// forEach iterates every entry in the table serially and in order, calling `fn`
   400  	// with the item as argument. If `fn` returns an error the iteration stops
   401  	// and that error will be returned.
   402  	forEach := func(t *freezerTable, offset uint64, fn func(uint64, []byte) error) error {
   403  		var (
   404  			items     = atomic.LoadUint64(&t.items)
   405  			batchSize = uint64(1024)
   406  			maxBytes  = uint64(1024 * 1024)
   407  		)
   408  		for i := offset; i < items; {
   409  			if i+batchSize > items {
   410  				batchSize = items - i
   411  			}
   412  			data, err := t.RetrieveItems(i, batchSize, maxBytes)
   413  			if err != nil {
   414  				return err
   415  			}
   416  			for j, item := range data {
   417  				if err := fn(i+uint64(j), item); err != nil {
   418  					return err
   419  				}
   420  			}
   421  			i += uint64(len(data))
   422  		}
   423  		return nil
   424  	}
   425  	// TODO(s1na): This is a sanity-check since as of now no process does tail-deletion. But the migration
   426  	// process assumes no deletion at tail and needs to be modified to account for that.
   427  	if table.itemOffset > 0 || table.itemHidden > 0 {
   428  		return fmt.Errorf("migration not supported for tail-deleted freezers")
   429  	}
   430  	ancientsPath := filepath.Dir(table.index.Name())
   431  	// Set up new dir for the migrated table, the content of which
   432  	// we'll at the end move over to the ancients dir.
   433  	migrationPath := filepath.Join(ancientsPath, "migration")
   434  	newTable, err := newFreezerTable(migrationPath, kind, table.noCompression, false)
   435  	if err != nil {
   436  		return err
   437  	}
   438  	var (
   439  		batch  = newTable.newBatch()
   440  		out    []byte
   441  		start  = time.Now()
   442  		logged = time.Now()
   443  		offset = newTable.items
   444  	)
   445  	if offset > 0 {
   446  		log.Info("found previous migration attempt", "migrated", offset)
   447  	}
   448  	// Iterate through entries and transform them
   449  	if err := forEach(table, offset, func(i uint64, blob []byte) error {
   450  		if i%10000 == 0 && time.Since(logged) > 16*time.Second {
   451  			log.Info("Processing legacy elements", "count", i, "elapsed", common.PrettyDuration(time.Since(start)))
   452  			logged = time.Now()
   453  		}
   454  		out, err = convert(blob)
   455  		if err != nil {
   456  			return err
   457  		}
   458  		if err := batch.AppendRaw(i, out); err != nil {
   459  			return err
   460  		}
   461  		return nil
   462  	}); err != nil {
   463  		return err
   464  	}
   465  	if err := batch.commit(); err != nil {
   466  		return err
   467  	}
   468  	log.Info("Replacing old table files with migrated ones", "elapsed", common.PrettyDuration(time.Since(start)))
   469  	// Release and delete old table files. Note this won't
   470  	// delete the index file.
   471  	table.releaseFilesAfter(0, true)
   472  
   473  	if err := newTable.Close(); err != nil {
   474  		return err
   475  	}
   476  	files, err := os.ReadDir(migrationPath)
   477  	if err != nil {
   478  		return err
   479  	}
   480  	// Move migrated files to ancients dir.
   481  	for _, f := range files {
   482  		// This will replace the old index file as a side-effect.
   483  		if err := os.Rename(filepath.Join(migrationPath, f.Name()), filepath.Join(ancientsPath, f.Name())); err != nil {
   484  			return err
   485  		}
   486  	}
   487  	// Delete by now empty dir.
   488  	if err := os.Remove(migrationPath); err != nil {
   489  		return err
   490  	}
   491  	return nil
   492  }