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