Skip to content

Commit 61b75e0

Browse files
committed
core/rawdb: introduce flush offset in freezer
1 parent 67a3b08 commit 61b75e0

File tree

8 files changed

+644
-250
lines changed

8 files changed

+644
-250
lines changed

core/blockchain.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,21 +113,28 @@ const (
113113
// * the `BlockNumber`, `TxHash`, `TxIndex`, `BlockHash` and `Index` fields of log are deleted
114114
// * the `Bloom` field of receipt is deleted
115115
// * the `BlockIndex` and `TxIndex` fields of txlookup are deleted
116+
//
116117
// - Version 5
117118
// The following incompatible database changes were added:
118119
// * the `TxHash`, `GasCost`, and `ContractAddress` fields are no longer stored for a receipt
119120
// * the `TxHash`, `GasCost`, and `ContractAddress` fields are computed by looking up the
120121
// receipts' corresponding block
122+
//
121123
// - Version 6
122124
// The following incompatible database changes were added:
123125
// * Transaction lookup information stores the corresponding block number instead of block hash
126+
//
124127
// - Version 7
125128
// The following incompatible database changes were added:
126129
// * Use freezer as the ancient database to maintain all ancient data
130+
//
127131
// - Version 8
128132
// The following incompatible database changes were added:
129133
// * New scheme for contract code in order to separate the codes and trie nodes
130-
BlockChainVersion uint64 = 8
134+
//
135+
// - Version 9
136+
// * The metadata structure of freezer is changed by adding 'flushOffset'
137+
BlockChainVersion uint64 = 9
131138
)
132139

133140
// CacheConfig contains the configuration values for the trie database

core/rawdb/accessors_chain_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,7 @@ func TestHeadersRLPStorage(t *testing.T) {
883883
t.Fatalf("failed to create database with ancient backend")
884884
}
885885
defer db.Close()
886+
886887
// Create blocks
887888
var chain []*types.Block
888889
var pHash common.Hash
@@ -898,7 +899,7 @@ func TestHeadersRLPStorage(t *testing.T) {
898899
chain = append(chain, block)
899900
pHash = block.Hash()
900901
}
901-
var receipts []types.Receipts = make([]types.Receipts, 100)
902+
receipts := make([]types.Receipts, 100)
902903
// Write first half to ancients
903904
WriteAncientBlocks(db, chain[:50], receipts[:50], big.NewInt(100))
904905
// Write second half to db

core/rawdb/ancient_scheme.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const (
6262
stateHistoryStorageData = "storage.data"
6363
)
6464

65+
// stateFreezerNoSnappy configures whether compression is disabled for the state freezer.
6566
var stateFreezerNoSnappy = map[string]bool{
6667
stateHistoryMeta: true,
6768
stateHistoryAccountIndex: false,

core/rawdb/freezer_batch.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package rawdb
1919
import (
2020
"fmt"
2121
"math"
22+
"time"
2223

2324
"github.com/ethereum/go-ethereum/rlp"
2425
"github.com/golang/snappy"
@@ -188,9 +189,6 @@ func (batch *freezerTableBatch) commit() error {
188189
if err != nil {
189190
return err
190191
}
191-
if err := batch.t.head.Sync(); err != nil {
192-
return err
193-
}
194192
dataSize := int64(len(batch.dataBuffer))
195193
batch.dataBuffer = batch.dataBuffer[:0]
196194

@@ -208,6 +206,12 @@ func (batch *freezerTableBatch) commit() error {
208206
// Update metrics.
209207
batch.t.sizeGauge.Inc(dataSize + indexSize)
210208
batch.t.writeMeter.Mark(dataSize + indexSize)
209+
210+
// Periodically sync the table, todo (rjl493456442) make it configurable?
211+
if time.Since(batch.t.lastSync) > 30*time.Second {
212+
batch.t.lastSync = time.Now()
213+
return batch.t.syncWithNoLock()
214+
}
211215
return nil
212216
}
213217

core/rawdb/freezer_meta.go

Lines changed: 127 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -17,93 +17,166 @@
1717
package rawdb
1818

1919
import (
20+
"errors"
2021
"io"
2122
"os"
2223

23-
"github.com/ethereum/go-ethereum/log"
2424
"github.com/ethereum/go-ethereum/rlp"
2525
)
2626

27-
const freezerVersion = 1 // The initial version tag of freezer table metadata
27+
const (
28+
freezerTableV1 = 1 // Initial version of metadata struct
29+
freezerTableV2 = 2 // Add field: 'flushOffset'
30+
)
2831

29-
// freezerTableMeta wraps all the metadata of the freezer table.
32+
// freezerTableMeta is a collection of additional properties that describe the
33+
// freezer table. These properties are designed with error resilience, allowing
34+
// them to be automatically corrected after an error occurs without significantly
35+
// impacting overall correctness.
3036
type freezerTableMeta struct {
31-
// Version is the versioning descriptor of the freezer table.
32-
Version uint16
37+
file *os.File // file handler of metadata
38+
version uint16 // version descriptor of the freezer table
3339

34-
// VirtualTail indicates how many items have been marked as deleted.
35-
// Its value is equal to the number of items removed from the table
36-
// plus the number of items hidden in the table, so it should never
37-
// be lower than the "actual tail".
38-
VirtualTail uint64
39-
}
40+
// virtualTail represents the number of items marked as deleted. It is
41+
// calculated as the sum of items removed from the table and the items
42+
// hidden within the table, and should never be less than the "actual
43+
// tail".
44+
//
45+
// If lost due to a crash or other reasons, it will be reset to the number
46+
// of items deleted from the table, causing the previously hidden items
47+
// to become visible, which is an acceptable consequence.
48+
virtualTail uint64
4049

41-
// newMetadata initializes the metadata object with the given virtual tail.
42-
func newMetadata(tail uint64) *freezerTableMeta {
43-
return &freezerTableMeta{
44-
Version: freezerVersion,
45-
VirtualTail: tail,
46-
}
50+
// flushOffset represents the offset in the index file up to which the index
51+
// items along with the corresponding data items in data files has been flushed
52+
// (fsync’d) to disk. Beyond this offset, data integrity is not guaranteed,
53+
// the extra index items along with the associated data items should be removed
54+
// during the startup.
55+
//
56+
// The principle is that all data items above the flush offset are considered
57+
// volatile and should be recoverable if they are discarded after the unclean
58+
// shutdown. If data integrity is required, manually force a sync of the
59+
// freezer before proceeding with further operations (e.g. do freezer.Sync()
60+
// first and then write data to key value store in some circumstances).
61+
//
62+
// The offset could be moved forward by applying sync operation, or be moved
63+
// backward in cases of head/tail truncation, etc.
64+
flushOffset uint64
4765
}
4866

49-
// readMetadata reads the metadata of the freezer table from the
50-
// given metadata file.
51-
func readMetadata(file *os.File) (*freezerTableMeta, error) {
67+
// decodeV1 attempts to decode the metadata structure in v1 format. If fails or
68+
// the result is incompatible, nil is returned.
69+
func decodeV1(file *os.File) *freezerTableMeta {
5270
_, err := file.Seek(0, io.SeekStart)
5371
if err != nil {
54-
return nil, err
72+
return nil
5573
}
56-
var meta freezerTableMeta
57-
if err := rlp.Decode(file, &meta); err != nil {
58-
return nil, err
74+
type obj struct {
75+
Version uint16
76+
Tail uint64
77+
}
78+
var o obj
79+
if err := rlp.Decode(file, &o); err != nil {
80+
return nil
81+
}
82+
if o.Version != freezerTableV1 {
83+
return nil
84+
}
85+
return &freezerTableMeta{
86+
file: file,
87+
version: o.Version,
88+
virtualTail: o.Tail,
5989
}
60-
return &meta, nil
6190
}
6291

63-
// writeMetadata writes the metadata of the freezer table into the
64-
// given metadata file.
65-
func writeMetadata(file *os.File, meta *freezerTableMeta) error {
92+
// decodeV2 attempts to decode the metadata structure in v2 format. If fails or
93+
// the result is incompatible, nil is returned.
94+
func decodeV2(file *os.File) *freezerTableMeta {
6695
_, err := file.Seek(0, io.SeekStart)
6796
if err != nil {
68-
return err
97+
return nil
98+
}
99+
type obj struct {
100+
Version uint16
101+
Tail uint64
102+
Offset uint64
103+
}
104+
var o obj
105+
if err := rlp.Decode(file, &o); err != nil {
106+
return nil
107+
}
108+
if o.Version != freezerTableV2 {
109+
return nil
110+
}
111+
return &freezerTableMeta{
112+
file: file,
113+
version: freezerTableV2,
114+
virtualTail: o.Tail,
115+
flushOffset: o.Offset,
69116
}
70-
return rlp.Encode(file, meta)
71117
}
72118

73-
// loadMetadata loads the metadata from the given metadata file.
74-
// Initializes the metadata file with the given "actual tail" if
75-
// it's empty.
76-
func loadMetadata(file *os.File, tail uint64) (*freezerTableMeta, error) {
119+
// newMetadata initializes the metadata object, either by loading it from the file
120+
// or by constructing a new one from scratch.
121+
func newMetadata(file *os.File) (*freezerTableMeta, error) {
77122
stat, err := file.Stat()
78123
if err != nil {
79124
return nil, err
80125
}
81-
// Write the metadata with the given actual tail into metadata file
82-
// if it's non-existent. There are two possible scenarios here:
83-
// - the freezer table is empty
84-
// - the freezer table is legacy
85-
// In both cases, write the meta into the file with the actual tail
86-
// as the virtual tail.
87126
if stat.Size() == 0 {
88-
m := newMetadata(tail)
89-
if err := writeMetadata(file, m); err != nil {
127+
m := &freezerTableMeta{
128+
file: file,
129+
version: freezerTableV2,
130+
virtualTail: 0,
131+
flushOffset: 0,
132+
}
133+
if err := m.write(true); err != nil {
90134
return nil, err
91135
}
92136
return m, nil
93137
}
94-
m, err := readMetadata(file)
138+
if m := decodeV2(file); m != nil {
139+
return m, nil
140+
}
141+
if m := decodeV1(file); m != nil {
142+
return m, nil // legacy metadata
143+
}
144+
return nil, errors.New("failed to decode metadata")
145+
}
146+
147+
// setVirtualTail sets the virtual tail and flushes the metadata if sync is true.
148+
func (m *freezerTableMeta) setVirtualTail(tail uint64, sync bool) error {
149+
m.virtualTail = tail
150+
return m.write(sync)
151+
}
152+
153+
// setFlushOffset sets the flush offset and flushes the metadata if sync is true.
154+
func (m *freezerTableMeta) setFlushOffset(offset uint64, sync bool) error {
155+
m.flushOffset = offset
156+
return m.write(sync)
157+
}
158+
159+
// write flushes the content of metadata into file and performs a fsync if required.
160+
func (m *freezerTableMeta) write(sync bool) error {
161+
type obj struct {
162+
Version uint16
163+
Tail uint64
164+
Offset uint64
165+
}
166+
var o obj
167+
o.Version = freezerTableV2 // forcibly set it to v2
168+
o.Tail = m.virtualTail
169+
o.Offset = m.flushOffset
170+
171+
_, err := m.file.Seek(0, io.SeekStart)
95172
if err != nil {
96-
return nil, err
173+
return err
97174
}
98-
// Update the virtual tail with the given actual tail if it's even
99-
// lower than it. Theoretically it shouldn't happen at all, print
100-
// a warning here.
101-
if m.VirtualTail < tail {
102-
log.Warn("Updated virtual tail", "have", m.VirtualTail, "now", tail)
103-
m.VirtualTail = tail
104-
if err := writeMetadata(file, m); err != nil {
105-
return nil, err
106-
}
175+
if err := rlp.Encode(m.file, &o); err != nil {
176+
return err
177+
}
178+
if !sync {
179+
return nil
107180
}
108-
return m, nil
181+
return m.file.Sync()
109182
}

0 commit comments

Comments
 (0)