Skip to content

Commit a7359ce

Browse files
jsvisarjl493456442
andauthored
triedb, core/rawdb: implement the partial read in freezer (#32132)
This PR implements the partial read functionalities in the freezer, optimizing the state history reader by resolving less data from freezer. --------- Signed-off-by: jsvisa <[email protected]> Co-authored-by: Gary Rong <[email protected]>
1 parent bc0a21a commit a7359ce

File tree

12 files changed

+214
-42
lines changed

12 files changed

+214
-42
lines changed

core/rawdb/accessors_state.go

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -188,37 +188,25 @@ func ReadStateAccountIndex(db ethdb.AncientReaderOp, id uint64) []byte {
188188
// data in the concatenated storage data table. Compute the position of state
189189
// history in freezer by minus one since the id of first state history starts
190190
// from one (zero for initial state).
191-
func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64) []byte {
192-
blob, err := db.Ancient(stateHistoryStorageIndex, id-1)
193-
if err != nil {
194-
return nil
195-
}
196-
return blob
191+
func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) {
192+
return db.AncientBytes(stateHistoryStorageIndex, id-1, uint64(offset), uint64(length))
197193
}
198194

199195
// ReadStateAccountHistory retrieves the concatenated account data blob for the
200196
// specified state history. Offsets and lengths are resolved via the account
201197
// index. Compute the position of state history in freezer by minus one since
202198
// the id of first state history starts from one (zero for initial state).
203-
func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte {
204-
blob, err := db.Ancient(stateHistoryAccountData, id-1)
205-
if err != nil {
206-
return nil
207-
}
208-
return blob
199+
func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) {
200+
return db.AncientBytes(stateHistoryAccountData, id-1, uint64(offset), uint64(length))
209201
}
210202

211203
// ReadStateStorageHistory retrieves the concatenated storage slot data blob for
212204
// the specified state history. Locations are resolved via the account and
213205
// storage indexes. Compute the position of state history in freezer by minus
214206
// one since the id of first state history starts from one (zero for initial
215207
// state).
216-
func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64) []byte {
217-
blob, err := db.Ancient(stateHistoryStorageData, id-1)
218-
if err != nil {
219-
return nil
220-
}
221-
return blob
208+
func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64, offset, length int) ([]byte, error) {
209+
return db.AncientBytes(stateHistoryStorageData, id-1, uint64(offset), uint64(length))
222210
}
223211

224212
// ReadStateHistory retrieves the state history from database with provided id.

core/rawdb/chain_freezer.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,10 @@ func (f *chainFreezer) AncientRange(kind string, start, count, maxBytes uint64)
403403
return f.ancients.AncientRange(kind, start, count, maxBytes)
404404
}
405405

406+
func (f *chainFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
407+
return f.ancients.AncientBytes(kind, id, offset, length)
408+
}
409+
406410
func (f *chainFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (int64, error) {
407411
return f.ancients.ModifyAncients(fn)
408412
}

core/rawdb/database.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ func (db *nofreezedb) AncientRange(kind string, start, max, maxByteSize uint64)
100100
return nil, errNotSupported
101101
}
102102

103+
// AncientBytes retrieves the value segment of the element specified by the id
104+
// and value offsets.
105+
func (db *nofreezedb) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
106+
return nil, errNotSupported
107+
}
108+
103109
// Ancients returns an error as we don't have a backing chain freezer.
104110
func (db *nofreezedb) Ancients() (uint64, error) {
105111
return 0, errNotSupported

core/rawdb/freezer.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,15 @@ func (f *Freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]
202202
return nil, errUnknownTable
203203
}
204204

205+
// AncientBytes retrieves the value segment of the element specified by the id
206+
// and value offsets.
207+
func (f *Freezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
208+
if table := f.tables[kind]; table != nil {
209+
return table.RetrieveBytes(id, offset, length)
210+
}
211+
return nil, errUnknownTable
212+
}
213+
205214
// Ancients returns the length of the frozen items.
206215
func (f *Freezer) Ancients() (uint64, error) {
207216
return f.frozen.Load(), nil

core/rawdb/freezer_memory.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,3 +412,28 @@ func (f *MemoryFreezer) Reset() error {
412412
func (f *MemoryFreezer) AncientDatadir() (string, error) {
413413
return "", nil
414414
}
415+
416+
// AncientBytes retrieves the value segment of the element specified by the id
417+
// and value offsets.
418+
func (f *MemoryFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
419+
f.lock.RLock()
420+
defer f.lock.RUnlock()
421+
422+
table := f.tables[kind]
423+
if table == nil {
424+
return nil, errUnknownTable
425+
}
426+
entries, err := table.retrieve(id, 1, 0)
427+
if err != nil {
428+
return nil, err
429+
}
430+
if len(entries) == 0 {
431+
return nil, errOutOfBounds
432+
}
433+
data := entries[0]
434+
435+
if offset > uint64(len(data)) || offset+length > uint64(len(data)) {
436+
return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", len(data), offset, length)
437+
}
438+
return data[offset : offset+length], nil
439+
}

core/rawdb/freezer_resettable.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,15 @@ func (f *resettableFreezer) AncientRange(kind string, start, count, maxBytes uin
126126
return f.freezer.AncientRange(kind, start, count, maxBytes)
127127
}
128128

129+
// AncientBytes retrieves the value segment of the element specified by the id
130+
// and value offsets.
131+
func (f *resettableFreezer) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
132+
f.lock.RLock()
133+
defer f.lock.RUnlock()
134+
135+
return f.freezer.AncientBytes(kind, id, offset, length)
136+
}
137+
129138
// Ancients returns the length of the frozen items.
130139
func (f *resettableFreezer) Ancients() (uint64, error) {
131140
f.lock.RLock()

core/rawdb/freezer_table.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,71 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i
11071107
return output, sizes, nil
11081108
}
11091109

1110+
// RetrieveBytes retrieves the value segment of the element specified by the id
1111+
// and value offsets.
1112+
func (t *freezerTable) RetrieveBytes(item, offset, length uint64) ([]byte, error) {
1113+
t.lock.RLock()
1114+
defer t.lock.RUnlock()
1115+
1116+
if t.index == nil || t.head == nil || t.metadata.file == nil {
1117+
return nil, errClosed
1118+
}
1119+
items, hidden := t.items.Load(), t.itemHidden.Load()
1120+
if items <= item || hidden > item {
1121+
return nil, errOutOfBounds
1122+
}
1123+
1124+
// Retrieves the index entries for the specified ID and its immediate successor
1125+
indices, err := t.getIndices(item, 1)
1126+
if err != nil {
1127+
return nil, err
1128+
}
1129+
index0, index1 := indices[0], indices[1]
1130+
1131+
itemStart, itemLimit, fileId := index0.bounds(index1)
1132+
itemSize := itemLimit - itemStart
1133+
1134+
dataFile, exist := t.files[fileId]
1135+
if !exist {
1136+
return nil, fmt.Errorf("missing data file %d", fileId)
1137+
}
1138+
1139+
// Perform the partial read if no-compression was enabled upon
1140+
if t.config.noSnappy {
1141+
if offset > uint64(itemSize) || offset+length > uint64(itemSize) {
1142+
return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", itemSize, offset, length)
1143+
}
1144+
itemStart += uint32(offset)
1145+
1146+
buf := make([]byte, length)
1147+
_, err = dataFile.ReadAt(buf, int64(itemStart))
1148+
if err != nil {
1149+
return nil, err
1150+
}
1151+
t.readMeter.Mark(int64(length))
1152+
return buf, nil
1153+
} else {
1154+
// If compressed, read the full item, decompress, then slice.
1155+
// Unfortunately, in this case, there is no performance gain
1156+
// by performing the partial read at all.
1157+
buf := make([]byte, itemSize)
1158+
_, err = dataFile.ReadAt(buf, int64(itemStart))
1159+
if err != nil {
1160+
return nil, err
1161+
}
1162+
t.readMeter.Mark(int64(itemSize))
1163+
1164+
data, err := snappy.Decode(nil, buf)
1165+
if err != nil {
1166+
return nil, err
1167+
}
1168+
if offset > uint64(len(data)) || offset+length > uint64(len(data)) {
1169+
return nil, fmt.Errorf("requested range out of bounds: item size %d, offset %d, length %d", len(data), offset, length)
1170+
}
1171+
return data[offset : offset+length], nil
1172+
}
1173+
}
1174+
11101175
// size returns the total data size in the freezer table.
11111176
func (t *freezerTable) size() (uint64, error) {
11121177
t.lock.RLock()

core/rawdb/freezer_table_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,3 +1571,65 @@ func TestTailTruncationCrash(t *testing.T) {
15711571
t.Fatalf("Unexpected index flush offset, want: %d, got: %d", 26*indexEntrySize, f.metadata.flushOffset)
15721572
}
15731573
}
1574+
1575+
func TestFreezerAncientBytes(t *testing.T) {
1576+
t.Parallel()
1577+
types := []struct {
1578+
name string
1579+
config freezerTableConfig
1580+
}{
1581+
{"uncompressed", freezerTableConfig{noSnappy: true}},
1582+
{"compressed", freezerTableConfig{noSnappy: false}},
1583+
}
1584+
for _, typ := range types {
1585+
t.Run(typ.name, func(t *testing.T) {
1586+
f, err := newTable(os.TempDir(), fmt.Sprintf("ancientbytes-%s-%d", typ.name, rand.Uint64()), metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge(), 1000, typ.config, false)
1587+
if err != nil {
1588+
t.Fatal(err)
1589+
}
1590+
defer f.Close()
1591+
1592+
for i := 0; i < 10; i++ {
1593+
data := getChunk(100, i)
1594+
batch := f.newBatch()
1595+
require.NoError(t, batch.AppendRaw(uint64(i), data))
1596+
require.NoError(t, batch.commit())
1597+
}
1598+
1599+
for i := 0; i < 10; i++ {
1600+
full, err := f.Retrieve(uint64(i))
1601+
require.NoError(t, err)
1602+
1603+
// Full read
1604+
got, err := f.RetrieveBytes(uint64(i), 0, uint64(len(full)))
1605+
require.NoError(t, err)
1606+
if !bytes.Equal(got, full) {
1607+
t.Fatalf("full read mismatch for entry %d", i)
1608+
}
1609+
// Empty read
1610+
got, err = f.RetrieveBytes(uint64(i), 0, 0)
1611+
require.NoError(t, err)
1612+
if !bytes.Equal(got, full[:0]) {
1613+
t.Fatalf("empty read mismatch for entry %d", i)
1614+
}
1615+
// Middle slice
1616+
got, err = f.RetrieveBytes(uint64(i), 10, 50)
1617+
require.NoError(t, err)
1618+
if !bytes.Equal(got, full[10:60]) {
1619+
t.Fatalf("middle slice mismatch for entry %d", i)
1620+
}
1621+
// Single byte
1622+
got, err = f.RetrieveBytes(uint64(i), 99, 1)
1623+
require.NoError(t, err)
1624+
if !bytes.Equal(got, full[99:100]) {
1625+
t.Fatalf("single byte mismatch for entry %d", i)
1626+
}
1627+
// Out of bounds
1628+
_, err = f.RetrieveBytes(uint64(i), 100, 1)
1629+
if err == nil {
1630+
t.Fatalf("expected error for out-of-bounds read for entry %d", i)
1631+
}
1632+
}
1633+
})
1634+
}
1635+
}

core/rawdb/table.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ func (t *table) AncientRange(kind string, start, count, maxBytes uint64) ([][]by
6262
return t.db.AncientRange(kind, start, count, maxBytes)
6363
}
6464

65+
// AncientBytes is a noop passthrough that just forwards the request to the underlying
66+
// database.
67+
func (t *table) AncientBytes(kind string, id, offset, length uint64) ([]byte, error) {
68+
return t.db.AncientBytes(kind, id, offset, length)
69+
}
70+
6571
// Ancients is a noop passthrough that just forwards the request to the underlying
6672
// database.
6773
func (t *table) Ancients() (uint64, error) {

ethdb/database.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ type AncientReaderOp interface {
121121
// - if maxBytes is not specified, 'count' items will be returned if they are present
122122
AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error)
123123

124+
// AncientBytes retrieves the value segment of the element specified by the id
125+
// and value offsets.
126+
AncientBytes(kind string, id, offset, length uint64) ([]byte, error)
127+
124128
// Ancients returns the ancient item numbers in the ancient store.
125129
Ancients() (uint64, error)
126130

0 commit comments

Comments
 (0)