Skip to content

Commit

Permalink
ethdb: pebble backend (64bit platforms only) (ethereum#26517)
Browse files Browse the repository at this point in the history
* ethdb: use pebble

Co-authored-by: Gary Rong <garyrong0905@gmail.com>

foo

update

* apply suggested changes

* flags: go format

node: fix ddir lookup mistake

accounts/abi/bind: fix go.mod replacement for generated binding

deps: update pebble + with fix 32-bit build

* ethdb/pebble: respect max memtable size

* core/rawdb, ethdb: enable pebble on non-32bit platforms only

* core/rawdb: fix build tags, fix some review concerns

* core/rawdb: refactor methods for database opening

* core/rawdb: remove erroneous build tag

* cmd/geth: fix the flag default handling + testcase

* cmd/geth: improve testing regarding custom backends

* ethdb/pebble, deps: update pebble dependency

* core/rawdb: replace method with Open

* ethdb/pebble: several updates for pebble (#49)

* ethdb/pebble: fix size count in batch

* ethdb/pebble: disable seek compaction

* ethdb/pebble: more fixes

* ethdb, core, cmd: polish and fixes (#50)

* cmd/utils, core/rawdb, ethdb/pebble: address some review concerns

* Update flags.go

* ethdb/pebble: minor refactors

* ethdb/pebble: avoid copy on batch replay

* ethdb: fix compilation flaw

* cmd: fix test fail due to mismatching error message

* cmd/geth, node: rename backingdb to db.engine

---------

Co-authored-by: Jared Wasinger <j-wasinger@hotmail.com>
Co-authored-by: rjl493456442 <garyrong0905@gmail.com>
Co-authored-by: Péter Szilágyi <peterke@gmail.com>
  • Loading branch information
4 people committed Feb 9, 2023
1 parent 095e365 commit ed51b8c
Show file tree
Hide file tree
Showing 16 changed files with 1,312 additions and 46 deletions.
101 changes: 101 additions & 0 deletions cmd/geth/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package main

import (
"fmt"
"os"
"path/filepath"
"strconv"
"testing"
)

Expand Down Expand Up @@ -70,6 +72,7 @@ var customGenesisTests = []struct {
// Tests that initializing Geth with a custom genesis block and chain definitions
// work properly.
func TestCustomGenesis(t *testing.T) {
t.Parallel()
for i, tt := range customGenesisTests {
// Create a temporary data directory to use and inspect later
datadir := t.TempDir()
Expand All @@ -90,3 +93,101 @@ func TestCustomGenesis(t *testing.T) {
geth.ExpectExit()
}
}

// TestCustomBackend that the backend selection and detection (leveldb vs pebble) works properly.
func TestCustomBackend(t *testing.T) {
t.Parallel()
// Test pebble, but only on 64-bit platforms
if strconv.IntSize != 64 {
t.Skip("Custom backends are only available on 64-bit platform")
}
genesis := `{
"alloc" : {},
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x20000",
"extraData" : "",
"gasLimit" : "0x2fefd8",
"nonce" : "0x0000000000001338",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00",
"config" : {}
}`
type backendTest struct {
initArgs []string
initExpect string
execArgs []string
execExpect string
}
testfunc := func(t *testing.T, tt backendTest) error {
// Create a temporary data directory to use and inspect later
datadir := t.TempDir()

// Initialize the data directory with the custom genesis block
json := filepath.Join(datadir, "genesis.json")
if err := os.WriteFile(json, []byte(genesis), 0600); err != nil {
return fmt.Errorf("failed to write genesis file: %v", err)
}
{ // Init
args := append(tt.initArgs, "--datadir", datadir, "init", json)
geth := runGeth(t, args...)
geth.ExpectRegexp(tt.initExpect)
geth.ExpectExit()
}
{ // Exec + query
args := append(tt.execArgs, "--networkid", "1337", "--syncmode=full", "--cache", "16",
"--datadir", datadir, "--maxpeers", "0", "--port", "0", "--authrpc.port", "0",
"--nodiscover", "--nat", "none", "--ipcdisable",
"--exec", "eth.getBlock(0).nonce", "console")
geth := runGeth(t, args...)
geth.ExpectRegexp(tt.execExpect)
geth.ExpectExit()
}
return nil
}
for i, tt := range []backendTest{
{ // When not specified, it should default to leveldb
execArgs: []string{"--db.engine", "leveldb"},
execExpect: "0x0000000000001338",
},
{ // Explicit leveldb
initArgs: []string{"--db.engine", "leveldb"},
execArgs: []string{"--db.engine", "leveldb"},
execExpect: "0x0000000000001338",
},
{ // Explicit leveldb first, then autodiscover
initArgs: []string{"--db.engine", "leveldb"},
execExpect: "0x0000000000001338",
},
{ // Explicit pebble
initArgs: []string{"--db.engine", "pebble"},
execArgs: []string{"--db.engine", "pebble"},
execExpect: "0x0000000000001338",
},
{ // Explicit pebble, then auto-discover
initArgs: []string{"--db.engine", "pebble"},
execExpect: "0x0000000000001338",
},
{ // Can't start pebble on top of leveldb
initArgs: []string{"--db.engine", "leveldb"},
execArgs: []string{"--db.engine", "pebble"},
execExpect: `Fatal: Failed to register the Ethereum service: db.engine choice was pebble but found pre-existing leveldb database in specified data directory`,
},
{ // Can't start leveldb on top of pebble
initArgs: []string{"--db.engine", "pebble"},
execArgs: []string{"--db.engine", "leveldb"},
execExpect: `Fatal: Failed to register the Ethereum service: db.engine choice was leveldb but found pre-existing pebble database in specified data directory`,
},
{ // Reject invalid backend choice
initArgs: []string{"--db.engine", "mssql"},
initExpect: `Fatal: Invalid choice for db.engine 'mssql', allowed 'leveldb' or 'pebble'`,
// Since the init fails, this will return the (default) mainnet genesis
// block nonce
execExpect: `0x0000000000000042`,
},
} {
if err := testfunc(t, tt); err != nil {
t.Fatalf("test %d-leveldb: %v", i, err)
}
}
}
20 changes: 20 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ var (
Usage: "URL for remote database",
Category: flags.LoggingCategory,
}
DBEngineFlag = &cli.StringFlag{
Name: "db.engine",
Usage: "Backing database implementation to use ('leveldb' or 'pebble')",
Value: "leveldb",
Category: flags.EthCategory,
}
AncientFlag = &flags.DirectoryFlag{
Name: "datadir.ancient",
Usage: "Root directory for ancient data (default = inside chaindata)",
Expand Down Expand Up @@ -1009,6 +1015,12 @@ var (
}
)

func init() {
if rawdb.PebbleEnabled {
DatabasePathFlags = append(DatabasePathFlags, DBEngineFlag)
}
}

// MakeDataDir retrieves the currently requested data directory, terminating
// if none (or the empty string) is specified. If the node is starting a testnet,
// then a subdirectory of the specified datadir will be used.
Expand Down Expand Up @@ -1484,6 +1496,14 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
if ctx.IsSet(InsecureUnlockAllowedFlag.Name) {
cfg.InsecureUnlockAllowed = ctx.Bool(InsecureUnlockAllowedFlag.Name)
}
if ctx.IsSet(DBEngineFlag.Name) {
dbEngine := ctx.String(DBEngineFlag.Name)
if dbEngine != "leveldb" && dbEngine != "pebble" {
Fatalf("Invalid choice for db.engine '%s', allowed 'leveldb' or 'pebble'", dbEngine)
}
log.Info(fmt.Sprintf("Using %s as db engine", dbEngine))
cfg.DBEngine = dbEngine
}
}

func setSmartCard(ctx *cli.Context, cfg *node.Config) {
Expand Down
22 changes: 18 additions & 4 deletions core/blockchain_repair_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1756,7 +1756,10 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
// Create a temporary persistent database
datadir := t.TempDir()

db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
db, err := rawdb.Open(rawdb.OpenOptions{
Directory: datadir,
AncientsDirectory: datadir,
})
if err != nil {
t.Fatalf("Failed to create persistent database: %v", err)
}
Expand Down Expand Up @@ -1829,7 +1832,11 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
chain.stopWithoutSaving()

// Start a new blockchain back up and see where the repair leads us
db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
db, err = rawdb.Open(rawdb.OpenOptions{
Directory: datadir,
AncientsDirectory: datadir,
})

if err != nil {
t.Fatalf("Failed to reopen persistent database: %v", err)
}
Expand Down Expand Up @@ -1884,7 +1891,11 @@ func TestIssue23496(t *testing.T) {
// Create a temporary persistent database
datadir := t.TempDir()

db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
db, err := rawdb.Open(rawdb.OpenOptions{
Directory: datadir,
AncientsDirectory: datadir,
})

if err != nil {
t.Fatalf("Failed to create persistent database: %v", err)
}
Expand Down Expand Up @@ -1944,7 +1955,10 @@ func TestIssue23496(t *testing.T) {
chain.stopWithoutSaving()

// Start a new blockchain back up and see where the repair leads us
db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
db, err = rawdb.Open(rawdb.OpenOptions{
Directory: datadir,
AncientsDirectory: datadir,
})
if err != nil {
t.Fatalf("Failed to reopen persistent database: %v", err)
}
Expand Down
5 changes: 4 additions & 1 deletion core/blockchain_sethead_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1956,7 +1956,10 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) {
// Create a temporary persistent database
datadir := t.TempDir()

db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
db, err := rawdb.Open(rawdb.OpenOptions{
Directory: datadir,
AncientsDirectory: datadir,
})
if err != nil {
t.Fatalf("Failed to create persistent database: %v", err)
}
Expand Down
11 changes: 9 additions & 2 deletions core/blockchain_snapshot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo
// Create a temporary persistent database
datadir := t.TempDir()

db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
db, err := rawdb.Open(rawdb.OpenOptions{
Directory: datadir,
AncientsDirectory: datadir,
})
if err != nil {
t.Fatalf("Failed to create persistent database: %v", err)
}
Expand Down Expand Up @@ -250,7 +253,11 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) {
chain.stopWithoutSaving()

// Start a new blockchain back up and see where the repair leads us
newdb, err := rawdb.NewLevelDBDatabaseWithFreezer(snaptest.datadir, 0, 0, snaptest.datadir, "", false)
newdb, err := rawdb.Open(rawdb.OpenOptions{
Directory: snaptest.datadir,
AncientsDirectory: snaptest.datadir,
})

if err != nil {
t.Fatalf("Failed to reopen persistent database: %v", err)
}
Expand Down
80 changes: 73 additions & 7 deletions core/rawdb/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
"sync/atomic"
"time"
Expand Down Expand Up @@ -302,19 +303,84 @@ func NewLevelDBDatabase(file string, cache int, handles int, namespace string, r
if err != nil {
return nil, err
}
log.Info("Using LevelDB as the backing database")
return NewDatabase(db), nil
}

// NewLevelDBDatabaseWithFreezer creates a persistent key-value database with a
// freezer moving immutable chain segments into cold storage. The passed ancient
// indicates the path of root ancient directory where the chain freezer can be
// opened.
func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, ancient string, namespace string, readonly bool) (ethdb.Database, error) {
kvdb, err := leveldb.New(file, cache, handles, namespace, readonly)
const (
dbPebble = "pebble"
dbLeveldb = "leveldb"
)

// hasPreexistingDb checks the given data directory whether a database is already
// instantiated at that location, and if so, returns the type of database (or the
// empty string).
func hasPreexistingDb(path string) string {
if _, err := os.Stat(filepath.Join(path, "CURRENT")); err != nil {
return "" // No pre-existing db
}
if matches, err := filepath.Glob(filepath.Join(path, "OPTIONS*")); len(matches) > 0 || err != nil {
if err != nil {
panic(err) // only possible if the pattern is malformed
}
return dbPebble
}
return dbLeveldb
}

// OpenOptions contains the options to apply when opening a database.
// OBS: If AncientsDirectory is empty, it indicates that no freezer is to be used.
type OpenOptions struct {
Type string // "leveldb" | "pebble"
Directory string // the datadir
AncientsDirectory string // the ancients-dir
Namespace string // the namespace for database relevant metrics
Cache int // the capacity(in megabytes) of the data caching
Handles int // number of files to be open simultaneously
ReadOnly bool
}

// openKeyValueDatabase opens a disk-based key-value database, e.g. leveldb or pebble.
//
// type == null type != null
// +----------------------------------------
// db is non-existent | leveldb default | specified type
// db is existent | from db | specified type (if compatible)
func openKeyValueDatabase(o OpenOptions) (ethdb.Database, error) {
existingDb := hasPreexistingDb(o.Directory)
if len(existingDb) != 0 && len(o.Type) != 0 && o.Type != existingDb {
return nil, fmt.Errorf("db.engine choice was %v but found pre-existing %v database in specified data directory", o.Type, existingDb)
}
if o.Type == dbPebble || existingDb == dbPebble {
if PebbleEnabled {
log.Info("Using pebble as the backing database")
return NewPebbleDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly)
} else {
return nil, errors.New("db.engine 'pebble' not supported on this platform")
}
}
if len(o.Type) != 0 && o.Type != dbLeveldb {
return nil, fmt.Errorf("unknown db.engine %v", o.Type)
}
log.Info("Using leveldb as the backing database")
// Use leveldb, either as default (no explicit choice), or pre-existing, or chosen explicitly
return NewLevelDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly)
}

// Open opens both a disk-based key-value database such as leveldb or pebble, but also
// integrates it with a freezer database -- if the AncientDir option has been
// set on the provided OpenOptions.
// The passed o.AncientDir indicates the path of root ancient directory where
// the chain freezer can be opened.
func Open(o OpenOptions) (ethdb.Database, error) {
kvdb, err := openKeyValueDatabase(o)
if err != nil {
return nil, err
}
frdb, err := NewDatabaseWithFreezer(kvdb, ancient, namespace, readonly)
if len(o.AncientsDirectory) == 0 {
return kvdb, nil
}
frdb, err := NewDatabaseWithFreezer(kvdb, o.AncientsDirectory, o.Namespace, o.ReadOnly)
if err != nil {
kvdb.Close()
return nil, err
Expand Down
37 changes: 37 additions & 0 deletions core/rawdb/databases_64bit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2023 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>

//go:build arm64 || amd64

package rawdb

import (
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethdb/pebble"
)

// Pebble is unsuported on 32bit architecture
const PebbleEnabled = true

// NewPebbleDBDatabase creates a persistent key-value database without a freezer
// moving immutable chain segments into cold storage.
func NewPebbleDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) {
db, err := pebble.New(file, cache, handles, namespace, readonly)
if err != nil {
return nil, err
}
return NewDatabase(db), nil
}
Loading

0 comments on commit ed51b8c

Please sign in to comment.