Skip to content

Commit 0f06e35

Browse files
MariusVanDerWijdenfjlrjl493456442
authored
core/rawdb: allow for truncation in the freezer (#31362)
Here we add the notion of prunable tables for the `TruncateTail` operation in the freezer. TruncateTail for the chain freezer now only truncates the body and receipts tables, leaving headers and hashes as-is. This change also requires changing the validation/repair at startup to allow for tables with different tail. For the header and hash tables, we now require them to start at number zero. --------- Co-authored-by: Felix Lange <[email protected]> Co-authored-by: Gary Rong <[email protected]>
1 parent 64bd213 commit 0f06e35

12 files changed

+208
-156
lines changed

core/rawdb/ancient_scheme.go

+24-16
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,21 @@ const (
3737
ChainFreezerReceiptTable = "receipts"
3838
)
3939

40-
// chainFreezerNoSnappy configures whether compression is disabled for the ancient-tables.
41-
// Hashes and difficulties don't compress well.
42-
var chainFreezerNoSnappy = map[string]bool{
43-
ChainFreezerHeaderTable: false,
44-
ChainFreezerHashTable: true,
45-
ChainFreezerBodiesTable: false,
46-
ChainFreezerReceiptTable: false,
40+
// chainFreezerTableConfigs configures the settings for tables in the chain freezer.
41+
// Compression is disabled for hashes as they don't compress well. Additionally,
42+
// tail truncation is disabled for the header and hash tables, as these are intended
43+
// to be retained long-term.
44+
var chainFreezerTableConfigs = map[string]freezerTableConfig{
45+
ChainFreezerHeaderTable: {noSnappy: false, prunable: false},
46+
ChainFreezerHashTable: {noSnappy: true, prunable: false},
47+
ChainFreezerBodiesTable: {noSnappy: false, prunable: true},
48+
ChainFreezerReceiptTable: {noSnappy: false, prunable: true},
49+
}
50+
51+
// freezerTableConfig contains the settings for a freezer table.
52+
type freezerTableConfig struct {
53+
noSnappy bool // disables item compression
54+
prunable bool // true for tables that can be pruned by TruncateTail
4755
}
4856

4957
const (
@@ -58,13 +66,13 @@ const (
5866
stateHistoryStorageData = "storage.data"
5967
)
6068

61-
// stateFreezerNoSnappy configures whether compression is disabled for the state freezer.
62-
var stateFreezerNoSnappy = map[string]bool{
63-
stateHistoryMeta: true,
64-
stateHistoryAccountIndex: false,
65-
stateHistoryStorageIndex: false,
66-
stateHistoryAccountData: false,
67-
stateHistoryStorageData: false,
69+
// stateFreezerTableConfigs configures the settings for tables in the state freezer.
70+
var stateFreezerTableConfigs = map[string]freezerTableConfig{
71+
stateHistoryMeta: {noSnappy: true, prunable: true},
72+
stateHistoryAccountIndex: {noSnappy: false, prunable: true},
73+
stateHistoryStorageIndex: {noSnappy: false, prunable: true},
74+
stateHistoryAccountData: {noSnappy: false, prunable: true},
75+
stateHistoryStorageData: {noSnappy: false, prunable: true},
6876
}
6977

7078
// The list of identifiers of ancient stores.
@@ -85,13 +93,13 @@ var freezers = []string{ChainFreezerName, MerkleStateFreezerName, VerkleStateFre
8593
// state freezer.
8694
func NewStateFreezer(ancientDir string, verkle bool, readOnly bool) (ethdb.ResettableAncientStore, error) {
8795
if ancientDir == "" {
88-
return NewMemoryFreezer(readOnly, stateFreezerNoSnappy), nil
96+
return NewMemoryFreezer(readOnly, stateFreezerTableConfigs), nil
8997
}
9098
var name string
9199
if verkle {
92100
name = filepath.Join(ancientDir, VerkleStateFreezerName)
93101
} else {
94102
name = filepath.Join(ancientDir, MerkleStateFreezerName)
95103
}
96-
return newResettableFreezer(name, "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerNoSnappy)
104+
return newResettableFreezer(name, "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerTableConfigs)
97105
}

core/rawdb/ancient_utils.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func (info *freezerInfo) size() common.StorageSize {
5151
return total
5252
}
5353

54-
func inspect(name string, order map[string]bool, reader ethdb.AncientReader) (freezerInfo, error) {
54+
func inspect(name string, order map[string]freezerTableConfig, reader ethdb.AncientReader) (freezerInfo, error) {
5555
info := freezerInfo{name: name}
5656
for t := range order {
5757
size, err := reader.AncientSize(t)
@@ -82,7 +82,7 @@ func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) {
8282
for _, freezer := range freezers {
8383
switch freezer {
8484
case ChainFreezerName:
85-
info, err := inspect(ChainFreezerName, chainFreezerNoSnappy, db)
85+
info, err := inspect(ChainFreezerName, chainFreezerTableConfigs, db)
8686
if err != nil {
8787
return nil, err
8888
}
@@ -99,7 +99,7 @@ func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) {
9999
}
100100
defer f.Close()
101101

102-
info, err := inspect(freezer, stateFreezerNoSnappy, f)
102+
info, err := inspect(freezer, stateFreezerTableConfigs, f)
103103
if err != nil {
104104
return nil, err
105105
}
@@ -119,13 +119,13 @@ func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) {
119119
func InspectFreezerTable(ancient string, freezerName string, tableName string, start, end int64) error {
120120
var (
121121
path string
122-
tables map[string]bool
122+
tables map[string]freezerTableConfig
123123
)
124124
switch freezerName {
125125
case ChainFreezerName:
126-
path, tables = resolveChainFreezerDir(ancient), chainFreezerNoSnappy
126+
path, tables = resolveChainFreezerDir(ancient), chainFreezerTableConfigs
127127
case MerkleStateFreezerName, VerkleStateFreezerName:
128-
path, tables = filepath.Join(ancient, freezerName), stateFreezerNoSnappy
128+
path, tables = filepath.Join(ancient, freezerName), stateFreezerTableConfigs
129129
default:
130130
return fmt.Errorf("unknown freezer, supported ones: %v", freezers)
131131
}

core/rawdb/chain_freezer.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ func newChainFreezer(datadir string, namespace string, readonly bool) (*chainFre
6262
freezer ethdb.AncientStore
6363
)
6464
if datadir == "" {
65-
freezer = NewMemoryFreezer(readonly, chainFreezerNoSnappy)
65+
freezer = NewMemoryFreezer(readonly, chainFreezerTableConfigs)
6666
} else {
67-
freezer, err = NewFreezer(datadir, namespace, readonly, freezerTableSize, chainFreezerNoSnappy)
67+
freezer, err = NewFreezer(datadir, namespace, readonly, freezerTableSize, chainFreezerTableConfigs)
6868
}
6969
if err != nil {
7070
return nil, err

core/rawdb/freezer.go

+56-32
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ type Freezer struct {
7878
//
7979
// The 'tables' argument defines the data tables. If the value of a map
8080
// entry is true, snappy compression is disabled for the table.
81-
func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]bool) (*Freezer, error) {
81+
func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]freezerTableConfig) (*Freezer, error) {
8282
// Create the initial freezer object
8383
var (
8484
readMeter = metrics.NewRegisteredMeter(namespace+"ancient/read", nil)
@@ -121,8 +121,8 @@ func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize ui
121121
}
122122

123123
// Create the tables.
124-
for name, disableSnappy := range tables {
125-
table, err := newTable(datadir, name, readMeter, writeMeter, sizeGauge, maxTableSize, disableSnappy, readonly)
124+
for name, config := range tables {
125+
table, err := newTable(datadir, name, readMeter, writeMeter, sizeGauge, maxTableSize, config, readonly)
126126
if err != nil {
127127
for _, table := range freezer.tables {
128128
table.Close()
@@ -301,7 +301,8 @@ func (f *Freezer) TruncateHead(items uint64) (uint64, error) {
301301
return oitems, nil
302302
}
303303

304-
// TruncateTail discards any recent data below the provided threshold number.
304+
// TruncateTail discards all data below the specified threshold. Note that only
305+
// 'prunable' tables will be truncated.
305306
func (f *Freezer) TruncateTail(tail uint64) (uint64, error) {
306307
if f.readonly {
307308
return 0, errReadOnly
@@ -314,8 +315,10 @@ func (f *Freezer) TruncateTail(tail uint64) (uint64, error) {
314315
return old, nil
315316
}
316317
for _, table := range f.tables {
317-
if err := table.truncateTail(tail); err != nil {
318-
return 0, err
318+
if table.config.prunable {
319+
if err := table.truncateTail(tail); err != nil {
320+
return 0, err
321+
}
319322
}
320323
}
321324
f.tail.Store(tail)
@@ -343,56 +346,77 @@ func (f *Freezer) validate() error {
343346
return nil
344347
}
345348
var (
346-
head uint64
347-
tail uint64
348-
name string
349+
head uint64
350+
prunedTail *uint64
349351
)
350-
// Hack to get boundary of any table
351-
for kind, table := range f.tables {
352+
// get any head value
353+
for _, table := range f.tables {
352354
head = table.items.Load()
353-
tail = table.itemHidden.Load()
354-
name = kind
355355
break
356356
}
357-
// Now check every table against those boundaries.
358357
for kind, table := range f.tables {
358+
// all tables have to have the same head
359359
if head != table.items.Load() {
360-
return fmt.Errorf("freezer tables %s and %s have differing head: %d != %d", kind, name, table.items.Load(), head)
360+
return fmt.Errorf("freezer table %s has a differing head: %d != %d", kind, table.items.Load(), head)
361361
}
362-
if tail != table.itemHidden.Load() {
363-
return fmt.Errorf("freezer tables %s and %s have differing tail: %d != %d", kind, name, table.itemHidden.Load(), tail)
362+
if !table.config.prunable {
363+
// non-prunable tables have to start at 0
364+
if table.itemHidden.Load() != 0 {
365+
return fmt.Errorf("non-prunable freezer table '%s' has a non-zero tail: %d", kind, table.itemHidden.Load())
366+
}
367+
} else {
368+
// prunable tables have to have the same length
369+
if prunedTail == nil {
370+
tmp := table.itemHidden.Load()
371+
prunedTail = &tmp
372+
}
373+
if *prunedTail != table.itemHidden.Load() {
374+
return fmt.Errorf("freezer table %s has differing tail: %d != %d", kind, table.itemHidden.Load(), *prunedTail)
375+
}
364376
}
365377
}
378+
379+
if prunedTail == nil {
380+
tmp := uint64(0)
381+
prunedTail = &tmp
382+
}
383+
366384
f.frozen.Store(head)
367-
f.tail.Store(tail)
385+
f.tail.Store(*prunedTail)
368386
return nil
369387
}
370388

371389
// repair truncates all data tables to the same length.
372390
func (f *Freezer) repair() error {
373391
var (
374-
head = uint64(math.MaxUint64)
375-
tail = uint64(0)
392+
head = uint64(math.MaxUint64)
393+
prunedTail = uint64(0)
376394
)
395+
// get the minimal head and the maximum tail
377396
for _, table := range f.tables {
378-
items := table.items.Load()
379-
if head > items {
380-
head = items
381-
}
382-
hidden := table.itemHidden.Load()
383-
if hidden > tail {
384-
tail = hidden
385-
}
397+
head = min(head, table.items.Load())
398+
prunedTail = max(prunedTail, table.itemHidden.Load())
386399
}
387-
for _, table := range f.tables {
400+
// apply the pruning
401+
for kind, table := range f.tables {
402+
// all tables need to have the same head
388403
if err := table.truncateHead(head); err != nil {
389404
return err
390405
}
391-
if err := table.truncateTail(tail); err != nil {
392-
return err
406+
if !table.config.prunable {
407+
// non-prunable tables have to start at 0
408+
if table.itemHidden.Load() != 0 {
409+
panic(fmt.Sprintf("non-prunable freezer table %s has non-zero tail: %v", kind, table.itemHidden.Load()))
410+
}
411+
} else {
412+
// prunable tables have to have the same length
413+
if err := table.truncateTail(prunedTail); err != nil {
414+
return err
415+
}
393416
}
394417
}
418+
395419
f.frozen.Store(head)
396-
f.tail.Store(tail)
420+
f.tail.Store(prunedTail)
397421
return nil
398422
}

core/rawdb/freezer_batch.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ type freezerTableBatch struct {
9696
// newBatch creates a new batch for the freezer table.
9797
func (t *freezerTable) newBatch() *freezerTableBatch {
9898
batch := &freezerTableBatch{t: t}
99-
if !t.noCompression {
99+
if !t.config.noSnappy {
100100
batch.sb = new(snappyBuffer)
101101
}
102102
batch.reset()

core/rawdb/freezer_memory.go

+17-11
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,19 @@ import (
3030

3131
// memoryTable is used to store a list of sequential items in memory.
3232
type memoryTable struct {
33-
name string // Table name
3433
items uint64 // Number of stored items in the table, including the deleted ones
3534
offset uint64 // Number of deleted items from the table
3635
data [][]byte // List of rlp-encoded items, sort in order
3736
size uint64 // Total memory size occupied by the table
3837
lock sync.RWMutex
38+
39+
name string
40+
config freezerTableConfig
3941
}
4042

4143
// newMemoryTable initializes the memory table.
42-
func newMemoryTable(name string) *memoryTable {
43-
return &memoryTable{name: name}
44+
func newMemoryTable(name string, config freezerTableConfig) *memoryTable {
45+
return &memoryTable{name: name, config: config}
4446
}
4547

4648
// has returns an indicator whether the specified data exists.
@@ -218,10 +220,10 @@ type MemoryFreezer struct {
218220
}
219221

220222
// NewMemoryFreezer initializes an in-memory freezer instance.
221-
func NewMemoryFreezer(readonly bool, tableName map[string]bool) *MemoryFreezer {
223+
func NewMemoryFreezer(readonly bool, tableName map[string]freezerTableConfig) *MemoryFreezer {
222224
tables := make(map[string]*memoryTable)
223-
for name := range tableName {
224-
tables[name] = newMemoryTable(name)
225+
for name, cfg := range tableName {
226+
tables[name] = newMemoryTable(name, cfg)
225227
}
226228
return &MemoryFreezer{
227229
writeBatch: newMemoryBatch(),
@@ -368,7 +370,9 @@ func (f *MemoryFreezer) TruncateHead(items uint64) (uint64, error) {
368370
return old, nil
369371
}
370372

371-
// TruncateTail discards any recent data below the provided threshold number.
373+
// TruncateTail discards all data below the provided threshold number.
374+
// Note this will only truncate 'prunable' tables. Block headers and canonical
375+
// hashes cannot be truncated at this time.
372376
func (f *MemoryFreezer) TruncateTail(tail uint64) (uint64, error) {
373377
f.lock.Lock()
374378
defer f.lock.Unlock()
@@ -381,8 +385,10 @@ func (f *MemoryFreezer) TruncateTail(tail uint64) (uint64, error) {
381385
return old, nil
382386
}
383387
for _, table := range f.tables {
384-
if err := table.truncateTail(tail); err != nil {
385-
return 0, err
388+
if table.config.prunable {
389+
if err := table.truncateTail(tail); err != nil {
390+
return 0, err
391+
}
386392
}
387393
}
388394
f.tail = tail
@@ -412,8 +418,8 @@ func (f *MemoryFreezer) Reset() error {
412418
defer f.lock.Unlock()
413419

414420
tables := make(map[string]*memoryTable)
415-
for name := range f.tables {
416-
tables[name] = newMemoryTable(name)
421+
for name, table := range f.tables {
422+
tables[name] = newMemoryTable(name, table.config)
417423
}
418424
f.tables = tables
419425
f.items, f.tail = 0, 0

core/rawdb/freezer_memory_test.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,22 @@ import (
2525

2626
func TestMemoryFreezer(t *testing.T) {
2727
ancienttest.TestAncientSuite(t, func(kinds []string) ethdb.AncientStore {
28-
tables := make(map[string]bool)
28+
tables := make(map[string]freezerTableConfig)
2929
for _, kind := range kinds {
30-
tables[kind] = true
30+
tables[kind] = freezerTableConfig{
31+
noSnappy: true,
32+
prunable: true,
33+
}
3134
}
3235
return NewMemoryFreezer(false, tables)
3336
})
3437
ancienttest.TestResettableAncientSuite(t, func(kinds []string) ethdb.ResettableAncientStore {
35-
tables := make(map[string]bool)
38+
tables := make(map[string]freezerTableConfig)
3639
for _, kind := range kinds {
37-
tables[kind] = true
40+
tables[kind] = freezerTableConfig{
41+
noSnappy: true,
42+
prunable: true,
43+
}
3844
}
3945
return NewMemoryFreezer(false, tables)
4046
})

core/rawdb/freezer_resettable.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ type resettableFreezer struct {
4949
//
5050
// The reset function will delete directory atomically and re-create the
5151
// freezer from scratch.
52-
func newResettableFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]bool) (*resettableFreezer, error) {
52+
func newResettableFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]freezerTableConfig) (*resettableFreezer, error) {
5353
if err := cleanup(datadir); err != nil {
5454
return nil, err
5555
}

0 commit comments

Comments
 (0)