Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create Live Snapshot without shutting down node #380

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions arbitrum/recordingdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ func newRecordingKV(inner *triedb.Database, diskDb ethdb.KeyValueStore) *Recordi
return &RecordingKV{inner, diskDb, make(map[common.Hash][]byte), false}
}

func (db *RecordingKV) CreateDBSnapshot(dir string) error {
return errors.New("createDBSnapshot method is not supported")
}

func (db *RecordingKV) Has(key []byte) (bool, error) {
return false, errors.New("recording KV doesn't support Has")
}
Expand Down
20 changes: 20 additions & 0 deletions core/rawdb/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ func (frdb *freezerdb) Freeze() error {
return nil
}

func (db *freezerdb) CreateDBSnapshot(dir string) error {
if err := db.KeyValueStore.CreateDBSnapshot(dir); err != nil {
return err
}
if err := db.AncientStore.CreateDBSnapshot(dir); err != nil {
return err
}
return nil
}

// nofreezedb is a database wrapper that disables freezer data retrievals.
type nofreezedb struct {
ethdb.KeyValueStore
Expand Down Expand Up @@ -189,6 +199,16 @@ type dbWithWasmEntry struct {
wasmTargets []ethdb.WasmTarget
}

func (db *dbWithWasmEntry) CreateDBSnapshot(dir string) error {
if err := db.Database.CreateDBSnapshot(dir); err != nil {
return err
}
if err := db.wasmDb.CreateDBSnapshot(dir); err != nil {
return err
}
return nil
}

func (db *dbWithWasmEntry) WasmDataBase() (ethdb.KeyValueStore, uint32) {
return db.wasmDb, db.wasmCacheTag
}
Expand Down
64 changes: 64 additions & 0 deletions core/rawdb/freezer.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package rawdb
import (
"errors"
"fmt"
"io"
"math"
"os"
"path/filepath"
Expand Down Expand Up @@ -73,6 +74,9 @@ type Freezer struct {
tables map[string]*freezerTable // Data tables for storing everything
instanceLock FileLock // File-system lock to prevent double opens
closeOnce sync.Once

datadir string
createSnapshotMutex sync.Mutex
}

// NewFreezer creates a freezer instance for maintaining immutable ordered
Expand Down Expand Up @@ -115,6 +119,7 @@ func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize ui
readonly: readonly,
tables: make(map[string]*freezerTable),
instanceLock: lock,
datadir: datadir,
}

// Create the tables.
Expand Down Expand Up @@ -316,6 +321,8 @@ func (f *Freezer) TruncateTail(tail uint64) (uint64, error) {

// Sync flushes all data tables to disk.
func (f *Freezer) Sync() error {
f.createSnapshotMutex.Lock()
defer f.createSnapshotMutex.Unlock()
var errs []error
for _, table := range f.tables {
if err := table.Sync(); err != nil {
Expand All @@ -328,6 +335,63 @@ func (f *Freezer) Sync() error {
return nil
}

func (f *Freezer) CreateDBSnapshot(dir string) error {
f.createSnapshotMutex.Lock()
defer f.createSnapshotMutex.Unlock()
snapshotDir := filepath.Join(dir, "l2chaindata", "ancient", "chain") // Format currently used
if err := os.MkdirAll(snapshotDir, os.ModePerm); err != nil {
return fmt.Errorf("failed to create snapshot of ancient directory: %w", err)
}
// Manually copy contents of ancient to the snapshotDir, createSnapshotMutex makes sure ancient is not updated while copying contents
err := filepath.Walk(f.datadir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("error accessing path %s: %w", path, err)
}
relPath, err := filepath.Rel(f.datadir, path)
if err != nil {
return fmt.Errorf("error calculating relative path: %w", err)
}
destPath := filepath.Join(snapshotDir, relPath)
if info.IsDir() {
if err := os.MkdirAll(destPath, info.Mode()); err != nil {
return fmt.Errorf("failed to create directory %s: %w", destPath, err)
}
} else {
if err := copyFile(path, destPath); err != nil {
return fmt.Errorf("failed to copy file %s: %w", path, err)
}
}
return nil
})
return err
}

func copyFile(src, dest string) error {
srcFile, err := os.Open(src)
if err != nil {
return fmt.Errorf("failed to open source file: %w", err)
}
defer srcFile.Close()
destFile, err := os.Create(dest)
if err != nil {
return fmt.Errorf("failed to create destination file: %w", err)
}
defer destFile.Close()
_, err = io.Copy(destFile, srcFile)
if err != nil {
return fmt.Errorf("failed to copy contents: %w", err)
}
srcInfo, err := os.Stat(src)
if err != nil {
return fmt.Errorf("failed to stat source file: %w", err)
}
err = os.Chmod(dest, srcInfo.Mode())
if err != nil {
return fmt.Errorf("failed to set permissions: %w", err)
}
return nil
}

// validate checks that every table has the same boundary.
// Used instead of `repair` in readonly mode.
func (f *Freezer) validate() error {
Expand Down
4 changes: 4 additions & 0 deletions core/rawdb/freezer_memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ func NewMemoryFreezer(readonly bool, tableName map[string]bool) *MemoryFreezer {
}
}

func (f *MemoryFreezer) CreateDBSnapshot(dir string) error {
return errors.New("createDBSnapshot method is not supported by MemoryFreezer")
}

// HasAncient returns an indicator whether the specified data exists.
func (f *MemoryFreezer) HasAncient(kind string, number uint64) (bool, error) {
f.lock.RLock()
Expand Down
5 changes: 5 additions & 0 deletions core/rawdb/freezer_resettable.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package rawdb

import (
"errors"
"os"
"path/filepath"
"sync"
Expand Down Expand Up @@ -92,6 +93,10 @@ func (f *resettableFreezer) Reset() error {
return nil
}

func (f *resettableFreezer) CreateDBSnapshot(dir string) error {
return errors.New("createDBSnapshot method is not supported by resettableFreezer")
}

// Close terminates the chain freezer, unmapping all the data files.
func (f *resettableFreezer) Close() error {
f.lock.RLock()
Expand Down
6 changes: 6 additions & 0 deletions core/rawdb/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package rawdb

import (
"errors"

"github.com/ethereum/go-ethereum/ethdb"
)

Expand All @@ -35,6 +37,10 @@ func NewTable(db ethdb.Database, prefix string) ethdb.Database {
}
}

func (db *table) CreateDBSnapshot(dir string) error {
return errors.New("createDBSnapshot method is not supported")
}

// Close is a noop to implement the Database interface.
func (t *table) Close() error {
return nil
Expand Down
1 change: 1 addition & 0 deletions ethdb/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ type AncientStore interface {
AncientReader
AncientWriter
io.Closer
CreateDBSnapshot(dir string) error
}

type WasmTarget string
Expand Down
4 changes: 4 additions & 0 deletions ethdb/leveldb/fake_leveldb.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ func configureOptions(customizeFn func(*opt.Options)) *opt.Options {
}
*/

func (db *Database) CreateDBSnapshot(dir string) error {
return errors.New("createDBSnapshot method is not supported by leveldb")
}

// Close stops the metrics collection, flushes any pending data to disk and closes
// all io accesses to the underlying key-value store.
func (db *Database) Close() error {
Expand Down
4 changes: 4 additions & 0 deletions ethdb/leveldb/leveldb.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ func configureOptions(customizeFn func(*opt.Options)) *opt.Options {
return options
}

func (db *Database) CreateDBSnapshot(dir string) error {
return errors.New("createDBSnapshot method is not supported by leveldb")
}

// Close stops the metrics collection, flushes any pending data to disk and closes
// all io accesses to the underlying key-value store.
func (db *Database) Close() error {
Expand Down
4 changes: 4 additions & 0 deletions ethdb/memorydb/memorydb.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ func NewWithCap(size int) *Database {
}
}

func (db *Database) CreateDBSnapshot(dir string) error {
return errors.New("createDBSnapshot method is not supported by memorydb")
}

// Close deallocates the internal map and ensures any consecutive data access op
// fails with an error.
func (db *Database) Close() error {
Expand Down
6 changes: 6 additions & 0 deletions ethdb/pebble/pebble.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package pebble
import (
"bytes"
"fmt"
"path/filepath"
"runtime"
"sync"
"sync/atomic"
Expand Down Expand Up @@ -116,6 +117,11 @@ type Database struct {
writeOptions *pebble.WriteOptions
}

func (d *Database) CreateDBSnapshot(dir string) error {
snapshotDir := filepath.Join(dir, filepath.Base(d.Path()))
return d.db.Checkpoint(snapshotDir, pebble.WithFlushedWAL())
}

func (d *Database) onCompactionBegin(info pebble.CompactionInfo) {
if d.activeComp == 0 {
d.compStartTime = time.Now()
Expand Down
6 changes: 6 additions & 0 deletions ethdb/remotedb/remotedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
package remotedb

import (
"errors"

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/rpc"
Expand All @@ -32,6 +34,10 @@ type Database struct {
remote *rpc.Client
}

func (t *Database) CreateDBSnapshot(dir string) error {
return errors.New("createDBSnapshot method is not supported by remotedb")
}

func (db *Database) Has(key []byte) (bool, error) {
if _, err := db.Get(key); err != nil {
return false, nil
Expand Down
3 changes: 3 additions & 0 deletions ethdb/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,7 @@ type Snapshotter interface {
// Note don't forget to release the snapshot once it's used up, otherwise
// the stale data will never be cleaned up by the underlying compactor.
NewSnapshot() (Snapshot, error)

// CreateDBSnapshot creates a copy of underlying database inside dir
CreateDBSnapshot(dir string) error
}
3 changes: 3 additions & 0 deletions trie/trie_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,9 @@ type spongeDb struct {
values map[string]string
}

func (s *spongeDb) CreateDBSnapshot(dir string) error {
return errors.New("createDBSnapshot method is not supported by spongeDb")
}
func (s *spongeDb) Has(key []byte) (bool, error) { panic("implement me") }
func (s *spongeDb) Get(key []byte) ([]byte, error) { return nil, errors.New("no such elem") }
func (s *spongeDb) Delete(key []byte) error { panic("implement me") }
Expand Down
Loading