Skip to content

Commit ed51b8c

Browse files
holimanjwasingerrjl493456442karalabe
authored
ethdb: pebble backend (64bit platforms only) (#26517)
* 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>
1 parent 095e365 commit ed51b8c

16 files changed

+1312
-46
lines changed

cmd/geth/genesis_test.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
package main
1818

1919
import (
20+
"fmt"
2021
"os"
2122
"path/filepath"
23+
"strconv"
2224
"testing"
2325
)
2426

@@ -70,6 +72,7 @@ var customGenesisTests = []struct {
7072
// Tests that initializing Geth with a custom genesis block and chain definitions
7173
// work properly.
7274
func TestCustomGenesis(t *testing.T) {
75+
t.Parallel()
7376
for i, tt := range customGenesisTests {
7477
// Create a temporary data directory to use and inspect later
7578
datadir := t.TempDir()
@@ -90,3 +93,101 @@ func TestCustomGenesis(t *testing.T) {
9093
geth.ExpectExit()
9194
}
9295
}
96+
97+
// TestCustomBackend that the backend selection and detection (leveldb vs pebble) works properly.
98+
func TestCustomBackend(t *testing.T) {
99+
t.Parallel()
100+
// Test pebble, but only on 64-bit platforms
101+
if strconv.IntSize != 64 {
102+
t.Skip("Custom backends are only available on 64-bit platform")
103+
}
104+
genesis := `{
105+
"alloc" : {},
106+
"coinbase" : "0x0000000000000000000000000000000000000000",
107+
"difficulty" : "0x20000",
108+
"extraData" : "",
109+
"gasLimit" : "0x2fefd8",
110+
"nonce" : "0x0000000000001338",
111+
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
112+
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
113+
"timestamp" : "0x00",
114+
"config" : {}
115+
}`
116+
type backendTest struct {
117+
initArgs []string
118+
initExpect string
119+
execArgs []string
120+
execExpect string
121+
}
122+
testfunc := func(t *testing.T, tt backendTest) error {
123+
// Create a temporary data directory to use and inspect later
124+
datadir := t.TempDir()
125+
126+
// Initialize the data directory with the custom genesis block
127+
json := filepath.Join(datadir, "genesis.json")
128+
if err := os.WriteFile(json, []byte(genesis), 0600); err != nil {
129+
return fmt.Errorf("failed to write genesis file: %v", err)
130+
}
131+
{ // Init
132+
args := append(tt.initArgs, "--datadir", datadir, "init", json)
133+
geth := runGeth(t, args...)
134+
geth.ExpectRegexp(tt.initExpect)
135+
geth.ExpectExit()
136+
}
137+
{ // Exec + query
138+
args := append(tt.execArgs, "--networkid", "1337", "--syncmode=full", "--cache", "16",
139+
"--datadir", datadir, "--maxpeers", "0", "--port", "0", "--authrpc.port", "0",
140+
"--nodiscover", "--nat", "none", "--ipcdisable",
141+
"--exec", "eth.getBlock(0).nonce", "console")
142+
geth := runGeth(t, args...)
143+
geth.ExpectRegexp(tt.execExpect)
144+
geth.ExpectExit()
145+
}
146+
return nil
147+
}
148+
for i, tt := range []backendTest{
149+
{ // When not specified, it should default to leveldb
150+
execArgs: []string{"--db.engine", "leveldb"},
151+
execExpect: "0x0000000000001338",
152+
},
153+
{ // Explicit leveldb
154+
initArgs: []string{"--db.engine", "leveldb"},
155+
execArgs: []string{"--db.engine", "leveldb"},
156+
execExpect: "0x0000000000001338",
157+
},
158+
{ // Explicit leveldb first, then autodiscover
159+
initArgs: []string{"--db.engine", "leveldb"},
160+
execExpect: "0x0000000000001338",
161+
},
162+
{ // Explicit pebble
163+
initArgs: []string{"--db.engine", "pebble"},
164+
execArgs: []string{"--db.engine", "pebble"},
165+
execExpect: "0x0000000000001338",
166+
},
167+
{ // Explicit pebble, then auto-discover
168+
initArgs: []string{"--db.engine", "pebble"},
169+
execExpect: "0x0000000000001338",
170+
},
171+
{ // Can't start pebble on top of leveldb
172+
initArgs: []string{"--db.engine", "leveldb"},
173+
execArgs: []string{"--db.engine", "pebble"},
174+
execExpect: `Fatal: Failed to register the Ethereum service: db.engine choice was pebble but found pre-existing leveldb database in specified data directory`,
175+
},
176+
{ // Can't start leveldb on top of pebble
177+
initArgs: []string{"--db.engine", "pebble"},
178+
execArgs: []string{"--db.engine", "leveldb"},
179+
execExpect: `Fatal: Failed to register the Ethereum service: db.engine choice was leveldb but found pre-existing pebble database in specified data directory`,
180+
},
181+
{ // Reject invalid backend choice
182+
initArgs: []string{"--db.engine", "mssql"},
183+
initExpect: `Fatal: Invalid choice for db.engine 'mssql', allowed 'leveldb' or 'pebble'`,
184+
// Since the init fails, this will return the (default) mainnet genesis
185+
// block nonce
186+
execExpect: `0x0000000000000042`,
187+
},
188+
} {
189+
if err := testfunc(t, tt); err != nil {
190+
t.Fatalf("test %d-leveldb: %v", i, err)
191+
}
192+
}
193+
}

cmd/utils/flags.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ var (
9999
Usage: "URL for remote database",
100100
Category: flags.LoggingCategory,
101101
}
102+
DBEngineFlag = &cli.StringFlag{
103+
Name: "db.engine",
104+
Usage: "Backing database implementation to use ('leveldb' or 'pebble')",
105+
Value: "leveldb",
106+
Category: flags.EthCategory,
107+
}
102108
AncientFlag = &flags.DirectoryFlag{
103109
Name: "datadir.ancient",
104110
Usage: "Root directory for ancient data (default = inside chaindata)",
@@ -1009,6 +1015,12 @@ var (
10091015
}
10101016
)
10111017

1018+
func init() {
1019+
if rawdb.PebbleEnabled {
1020+
DatabasePathFlags = append(DatabasePathFlags, DBEngineFlag)
1021+
}
1022+
}
1023+
10121024
// MakeDataDir retrieves the currently requested data directory, terminating
10131025
// if none (or the empty string) is specified. If the node is starting a testnet,
10141026
// then a subdirectory of the specified datadir will be used.
@@ -1484,6 +1496,14 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
14841496
if ctx.IsSet(InsecureUnlockAllowedFlag.Name) {
14851497
cfg.InsecureUnlockAllowed = ctx.Bool(InsecureUnlockAllowedFlag.Name)
14861498
}
1499+
if ctx.IsSet(DBEngineFlag.Name) {
1500+
dbEngine := ctx.String(DBEngineFlag.Name)
1501+
if dbEngine != "leveldb" && dbEngine != "pebble" {
1502+
Fatalf("Invalid choice for db.engine '%s', allowed 'leveldb' or 'pebble'", dbEngine)
1503+
}
1504+
log.Info(fmt.Sprintf("Using %s as db engine", dbEngine))
1505+
cfg.DBEngine = dbEngine
1506+
}
14871507
}
14881508

14891509
func setSmartCard(ctx *cli.Context, cfg *node.Config) {

core/blockchain_repair_test.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,7 +1756,10 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) {
17561756
// Create a temporary persistent database
17571757
datadir := t.TempDir()
17581758

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

18311834
// Start a new blockchain back up and see where the repair leads us
1832-
db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
1835+
db, err = rawdb.Open(rawdb.OpenOptions{
1836+
Directory: datadir,
1837+
AncientsDirectory: datadir,
1838+
})
1839+
18331840
if err != nil {
18341841
t.Fatalf("Failed to reopen persistent database: %v", err)
18351842
}
@@ -1884,7 +1891,11 @@ func TestIssue23496(t *testing.T) {
18841891
// Create a temporary persistent database
18851892
datadir := t.TempDir()
18861893

1887-
db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
1894+
db, err := rawdb.Open(rawdb.OpenOptions{
1895+
Directory: datadir,
1896+
AncientsDirectory: datadir,
1897+
})
1898+
18881899
if err != nil {
18891900
t.Fatalf("Failed to create persistent database: %v", err)
18901901
}
@@ -1944,7 +1955,10 @@ func TestIssue23496(t *testing.T) {
19441955
chain.stopWithoutSaving()
19451956

19461957
// Start a new blockchain back up and see where the repair leads us
1947-
db, err = rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
1958+
db, err = rawdb.Open(rawdb.OpenOptions{
1959+
Directory: datadir,
1960+
AncientsDirectory: datadir,
1961+
})
19481962
if err != nil {
19491963
t.Fatalf("Failed to reopen persistent database: %v", err)
19501964
}

core/blockchain_sethead_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1956,7 +1956,10 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) {
19561956
// Create a temporary persistent database
19571957
datadir := t.TempDir()
19581958

1959-
db, err := rawdb.NewLevelDBDatabaseWithFreezer(datadir, 0, 0, datadir, "", false)
1959+
db, err := rawdb.Open(rawdb.OpenOptions{
1960+
Directory: datadir,
1961+
AncientsDirectory: datadir,
1962+
})
19601963
if err != nil {
19611964
t.Fatalf("Failed to create persistent database: %v", err)
19621965
}

core/blockchain_snapshot_test.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,10 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo
6161
// Create a temporary persistent database
6262
datadir := t.TempDir()
6363

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

252255
// Start a new blockchain back up and see where the repair leads us
253-
newdb, err := rawdb.NewLevelDBDatabaseWithFreezer(snaptest.datadir, 0, 0, snaptest.datadir, "", false)
256+
newdb, err := rawdb.Open(rawdb.OpenOptions{
257+
Directory: snaptest.datadir,
258+
AncientsDirectory: snaptest.datadir,
259+
})
260+
254261
if err != nil {
255262
t.Fatalf("Failed to reopen persistent database: %v", err)
256263
}

core/rawdb/database.go

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"fmt"
2323
"os"
2424
"path"
25+
"path/filepath"
2526
"strings"
2627
"sync/atomic"
2728
"time"
@@ -302,19 +303,84 @@ func NewLevelDBDatabase(file string, cache int, handles int, namespace string, r
302303
if err != nil {
303304
return nil, err
304305
}
306+
log.Info("Using LevelDB as the backing database")
305307
return NewDatabase(db), nil
306308
}
307309

308-
// NewLevelDBDatabaseWithFreezer creates a persistent key-value database with a
309-
// freezer moving immutable chain segments into cold storage. The passed ancient
310-
// indicates the path of root ancient directory where the chain freezer can be
311-
// opened.
312-
func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, ancient string, namespace string, readonly bool) (ethdb.Database, error) {
313-
kvdb, err := leveldb.New(file, cache, handles, namespace, readonly)
310+
const (
311+
dbPebble = "pebble"
312+
dbLeveldb = "leveldb"
313+
)
314+
315+
// hasPreexistingDb checks the given data directory whether a database is already
316+
// instantiated at that location, and if so, returns the type of database (or the
317+
// empty string).
318+
func hasPreexistingDb(path string) string {
319+
if _, err := os.Stat(filepath.Join(path, "CURRENT")); err != nil {
320+
return "" // No pre-existing db
321+
}
322+
if matches, err := filepath.Glob(filepath.Join(path, "OPTIONS*")); len(matches) > 0 || err != nil {
323+
if err != nil {
324+
panic(err) // only possible if the pattern is malformed
325+
}
326+
return dbPebble
327+
}
328+
return dbLeveldb
329+
}
330+
331+
// OpenOptions contains the options to apply when opening a database.
332+
// OBS: If AncientsDirectory is empty, it indicates that no freezer is to be used.
333+
type OpenOptions struct {
334+
Type string // "leveldb" | "pebble"
335+
Directory string // the datadir
336+
AncientsDirectory string // the ancients-dir
337+
Namespace string // the namespace for database relevant metrics
338+
Cache int // the capacity(in megabytes) of the data caching
339+
Handles int // number of files to be open simultaneously
340+
ReadOnly bool
341+
}
342+
343+
// openKeyValueDatabase opens a disk-based key-value database, e.g. leveldb or pebble.
344+
//
345+
// type == null type != null
346+
// +----------------------------------------
347+
// db is non-existent | leveldb default | specified type
348+
// db is existent | from db | specified type (if compatible)
349+
func openKeyValueDatabase(o OpenOptions) (ethdb.Database, error) {
350+
existingDb := hasPreexistingDb(o.Directory)
351+
if len(existingDb) != 0 && len(o.Type) != 0 && o.Type != existingDb {
352+
return nil, fmt.Errorf("db.engine choice was %v but found pre-existing %v database in specified data directory", o.Type, existingDb)
353+
}
354+
if o.Type == dbPebble || existingDb == dbPebble {
355+
if PebbleEnabled {
356+
log.Info("Using pebble as the backing database")
357+
return NewPebbleDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly)
358+
} else {
359+
return nil, errors.New("db.engine 'pebble' not supported on this platform")
360+
}
361+
}
362+
if len(o.Type) != 0 && o.Type != dbLeveldb {
363+
return nil, fmt.Errorf("unknown db.engine %v", o.Type)
364+
}
365+
log.Info("Using leveldb as the backing database")
366+
// Use leveldb, either as default (no explicit choice), or pre-existing, or chosen explicitly
367+
return NewLevelDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly)
368+
}
369+
370+
// Open opens both a disk-based key-value database such as leveldb or pebble, but also
371+
// integrates it with a freezer database -- if the AncientDir option has been
372+
// set on the provided OpenOptions.
373+
// The passed o.AncientDir indicates the path of root ancient directory where
374+
// the chain freezer can be opened.
375+
func Open(o OpenOptions) (ethdb.Database, error) {
376+
kvdb, err := openKeyValueDatabase(o)
314377
if err != nil {
315378
return nil, err
316379
}
317-
frdb, err := NewDatabaseWithFreezer(kvdb, ancient, namespace, readonly)
380+
if len(o.AncientsDirectory) == 0 {
381+
return kvdb, nil
382+
}
383+
frdb, err := NewDatabaseWithFreezer(kvdb, o.AncientsDirectory, o.Namespace, o.ReadOnly)
318384
if err != nil {
319385
kvdb.Close()
320386
return nil, err

core/rawdb/databases_64bit.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2023 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+
//go:build arm64 || amd64
18+
19+
package rawdb
20+
21+
import (
22+
"github.com/ethereum/go-ethereum/ethdb"
23+
"github.com/ethereum/go-ethereum/ethdb/pebble"
24+
)
25+
26+
// Pebble is unsuported on 32bit architecture
27+
const PebbleEnabled = true
28+
29+
// NewPebbleDBDatabase creates a persistent key-value database without a freezer
30+
// moving immutable chain segments into cold storage.
31+
func NewPebbleDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) {
32+
db, err := pebble.New(file, cache, handles, namespace, readonly)
33+
if err != nil {
34+
return nil, err
35+
}
36+
return NewDatabase(db), nil
37+
}

0 commit comments

Comments
 (0)