mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
core/rawdb: add ability to retrieve freezer data iteratively
This commit is contained in:
parent
9e71f55bfa
commit
539166c7ba
2 changed files with 247 additions and 0 deletions
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
"github.com/golang/snappy"
|
"github.com/golang/snappy"
|
||||||
|
|
@ -579,6 +580,146 @@ func (t *freezerTable) Retrieve(item uint64) ([]byte, error) {
|
||||||
return snappy.Decode(nil, blob)
|
return snappy.Decode(nil, blob)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// freezerTableRS represents a resultset
|
||||||
|
type freezerTableRS struct {
|
||||||
|
data []byte
|
||||||
|
sizes []uint32
|
||||||
|
current int32
|
||||||
|
compressed bool
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next move the resultset forward one step, and returns true if the iterator
|
||||||
|
// is exhausted
|
||||||
|
func (rs *freezerTableRS) Next() bool {
|
||||||
|
// On the first call to Next after initialization, the first
|
||||||
|
// sizes will be zero
|
||||||
|
nextStart := int(rs.sizes[0])
|
||||||
|
rs.data = rs.data[nextStart:]
|
||||||
|
rs.sizes = rs.sizes[1:]
|
||||||
|
rs.current++
|
||||||
|
return len(rs.sizes) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the current value of the iterator, decompressing if necessary
|
||||||
|
func (rs *freezerTableRS) Value() []byte {
|
||||||
|
nextStart := int(rs.sizes[0])
|
||||||
|
|
||||||
|
if !rs.compressed {
|
||||||
|
return rs.data[:nextStart]
|
||||||
|
}
|
||||||
|
if res, err := snappy.Decode(nil, rs.data[:nextStart]); err != nil {
|
||||||
|
rs.err = err
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key returns the little-endian representation of the current item number
|
||||||
|
func (rs *freezerTableRS) Key() []byte {
|
||||||
|
res := make([]byte, 4)
|
||||||
|
binary.LittleEndian.PutUint32(res, uint32(rs.current))
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release releases the memory held
|
||||||
|
func (rs *freezerTableRS) Release() {
|
||||||
|
rs.data = nil
|
||||||
|
rs.sizes = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns any error that occurred during processing
|
||||||
|
func (rs *freezerTableRS) Error() error {
|
||||||
|
return rs.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetrieveN returns an iterator with a number of items, starting from the index 'start'.
|
||||||
|
// The number of items retrieved is determined by
|
||||||
|
// 1. It tries to read 'max' number of items, but
|
||||||
|
// 2. Stops reading if it comes to a file boundary, and
|
||||||
|
// 3. Stops reading if the total bytesize exceeds maxBytes
|
||||||
|
//
|
||||||
|
// OBS! if the underlying table is compressed, the data will be decompressed
|
||||||
|
// when returned by the iterator. Thus, the total size of the decompressed item(s)
|
||||||
|
// may be larger than the `maxBytes` argument
|
||||||
|
func (t *freezerTable) RetrieveN(start, max, maxBytes uint64) (ethdb.Iterator, error) {
|
||||||
|
// Ensure the table and the item is accessible
|
||||||
|
if t.index == nil || t.head == nil {
|
||||||
|
return nil, errClosed
|
||||||
|
}
|
||||||
|
count := atomic.LoadUint64(&t.items)
|
||||||
|
if count <= start {
|
||||||
|
return nil, errOutOfBounds
|
||||||
|
}
|
||||||
|
// Check if requested max count is even possible
|
||||||
|
if count < start+max {
|
||||||
|
max = count - start
|
||||||
|
}
|
||||||
|
if max == 0 {
|
||||||
|
// Why are you requesting zero items? Sheesh
|
||||||
|
return nil, errOutOfBounds
|
||||||
|
}
|
||||||
|
// Ensure the item was not deleted from the tail either
|
||||||
|
offset := atomic.LoadUint32(&t.itemOffset)
|
||||||
|
if uint64(offset) > start {
|
||||||
|
return nil, errOutOfBounds
|
||||||
|
}
|
||||||
|
// Now, keep iterating the index until we hit one of the limits
|
||||||
|
t.lock.RLock()
|
||||||
|
defer t.lock.RUnlock()
|
||||||
|
var (
|
||||||
|
fileId = -1
|
||||||
|
totalSize = uint64(0)
|
||||||
|
sizes = []uint32{0}
|
||||||
|
dataStart = uint32(0)
|
||||||
|
dataEnd = uint32(0)
|
||||||
|
nItems = uint64(0)
|
||||||
|
)
|
||||||
|
current := start
|
||||||
|
for nItems < max {
|
||||||
|
nextStart, nextEnd, nextFile, err := t.getBounds(current)
|
||||||
|
size := (nextEnd - nextStart)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if fileId == -1 {
|
||||||
|
// This is the first item
|
||||||
|
fileId = int(nextFile)
|
||||||
|
dataStart = nextStart
|
||||||
|
} else if int(nextFile) != fileId {
|
||||||
|
// Crossing file boundary
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if nItems > 0 && totalSize+uint64(size) > maxBytes {
|
||||||
|
// Too large (but return at least one item)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// We include this one too
|
||||||
|
nItems++
|
||||||
|
totalSize += uint64(size)
|
||||||
|
sizes = append(sizes, size)
|
||||||
|
dataEnd = nextEnd
|
||||||
|
current++
|
||||||
|
}
|
||||||
|
dataFile, exist := t.files[uint32(fileId)]
|
||||||
|
if !exist {
|
||||||
|
return nil, fmt.Errorf("missing data file %d", fileId)
|
||||||
|
}
|
||||||
|
// Now, read the actual data
|
||||||
|
// Retrieve the data itself, decompress and return
|
||||||
|
blob := make([]byte, dataEnd-dataStart)
|
||||||
|
if _, err := dataFile.ReadAt(blob, int64(dataStart)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &freezerTableRS{
|
||||||
|
data: blob,
|
||||||
|
sizes: sizes,
|
||||||
|
current: int32(start - 1), // Set to one less than first item, a 'Next' is required
|
||||||
|
compressed: !t.noCompression,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// has returns an indicator whether the specified number data
|
// has returns an indicator whether the specified number data
|
||||||
// exists in the freezer table.
|
// exists in the freezer table.
|
||||||
func (t *freezerTable) has(number uint64) bool {
|
func (t *freezerTable) has(number uint64) bool {
|
||||||
|
|
|
||||||
|
|
@ -622,3 +622,109 @@ func TestOffset(t *testing.T) {
|
||||||
// However, all 'normal' failure modes arising due to failing to sync() or save a file should be
|
// However, all 'normal' failure modes arising due to failing to sync() or save a file should be
|
||||||
// handled already, and the case described above can only (?) happen if an external process/user
|
// handled already, and the case described above can only (?) happen if an external process/user
|
||||||
// deletes files from the filesystem.
|
// deletes files from the filesystem.
|
||||||
|
|
||||||
|
// TestIterator does some basic tests on the RetrieveN and iterarating the
|
||||||
|
// resultset
|
||||||
|
func TestIterator(t *testing.T) {
|
||||||
|
rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
|
||||||
|
fname := fmt.Sprintf("iterator-%d", rand.Uint64())
|
||||||
|
{ // Fill table
|
||||||
|
f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 50, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Write 15 bytes 30 times
|
||||||
|
for x := 0; x < 30; x++ {
|
||||||
|
data := getChunk(15, x)
|
||||||
|
f.Append(uint64(x), data)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
{ // Open it, iterate, verify iteration
|
||||||
|
f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 40, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
exp := f.items
|
||||||
|
itemCount := uint64(0)
|
||||||
|
lookupCount := 0
|
||||||
|
for {
|
||||||
|
it, err := f.RetrieveN(itemCount, 10000, 100000)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lookupCount++
|
||||||
|
for !it.Next() {
|
||||||
|
it.Value()
|
||||||
|
itemCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// There should be one lookup per file (zero-indexed files)
|
||||||
|
if exp := int(f.headId) + 1; exp != lookupCount {
|
||||||
|
t.Errorf("did %d lookups, expected %d", lookupCount, exp)
|
||||||
|
}
|
||||||
|
if itemCount != exp {
|
||||||
|
t.Errorf("got %d items, expected %d", itemCount, exp)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
{ // Open it, iterate, verify byte limit. The byte limit is less than item
|
||||||
|
// size, so each lookup should only return one otem
|
||||||
|
f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 40, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
exp := f.items
|
||||||
|
itemCount := uint64(0)
|
||||||
|
lookupCount := 0
|
||||||
|
for {
|
||||||
|
|
||||||
|
it, err := f.RetrieveN(itemCount, 10000, 10)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lookupCount++
|
||||||
|
for !it.Next() {
|
||||||
|
it.Value()
|
||||||
|
itemCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// There should be one lookup per item
|
||||||
|
if exp != uint64(lookupCount) {
|
||||||
|
t.Errorf("did %d lookups, expected %d", lookupCount, exp)
|
||||||
|
}
|
||||||
|
if itemCount != exp {
|
||||||
|
t.Errorf("got %d items, expected %d", itemCount, exp)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
{ // Open it, iterate, verify item limit. Max two items per go
|
||||||
|
f, err := newCustomTable(os.TempDir(), fname, rm, wm, sg, 40, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
exp := f.items
|
||||||
|
itemCount := uint64(0)
|
||||||
|
lookupCount := 0
|
||||||
|
for {
|
||||||
|
|
||||||
|
it, err := f.RetrieveN(itemCount, 1, 1000)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lookupCount++
|
||||||
|
for !it.Next() {
|
||||||
|
it.Value()
|
||||||
|
itemCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// There should be one lookup per item
|
||||||
|
if exp != uint64(lookupCount) {
|
||||||
|
t.Errorf("did %d lookups, expected %d", lookupCount, exp)
|
||||||
|
}
|
||||||
|
if itemCount != exp {
|
||||||
|
t.Errorf("got %d items, expected %d", itemCount, exp)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue