core/rawdb: freezer batch write (#23462)
This change is a rewrite of the freezer code. When writing ancient chain data to the freezer, the previous version first encoded each individual item to a temporary buffer, then wrote the buffer. For small item sizes (for example, in the block hash freezer table), this strategy causes a lot of system calls for writing tiny chunks of data. It also allocated a lot of temporary []byte buffers. In the new version, we instead encode multiple items into a re-useable batch buffer, which is then written to the file all at once. This avoids performing a system call for every inserted item. To make the internal batching work, the ancient database API had to be changed. While integrating this new API in BlockChain.InsertReceiptChain, additional optimizations were also added there. Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
committed by
GitHub
parent
9a0df80bbc
commit
794c6133ef
@@ -19,6 +19,7 @@ package rawdb
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
|
||||
@@ -81,6 +82,37 @@ func ReadAllHashes(db ethdb.Iteratee, number uint64) []common.Hash {
|
||||
return hashes
|
||||
}
|
||||
|
||||
type NumberHash struct {
|
||||
Number uint64
|
||||
Hash common.Hash
|
||||
}
|
||||
|
||||
// ReadAllHashes retrieves all the hashes assigned to blocks at a certain heights,
|
||||
// both canonical and reorged forks included.
|
||||
// This method considers both limits to be _inclusive_.
|
||||
func ReadAllHashesInRange(db ethdb.Iteratee, first, last uint64) []*NumberHash {
|
||||
var (
|
||||
start = encodeBlockNumber(first)
|
||||
keyLength = len(headerPrefix) + 8 + 32
|
||||
hashes = make([]*NumberHash, 0, 1+last-first)
|
||||
it = db.NewIterator(headerPrefix, start)
|
||||
)
|
||||
defer it.Release()
|
||||
for it.Next() {
|
||||
key := it.Key()
|
||||
if len(key) != keyLength {
|
||||
continue
|
||||
}
|
||||
num := binary.BigEndian.Uint64(key[len(headerPrefix) : len(headerPrefix)+8])
|
||||
if num > last {
|
||||
break
|
||||
}
|
||||
hash := common.BytesToHash(key[len(key)-32:])
|
||||
hashes = append(hashes, &NumberHash{num, hash})
|
||||
}
|
||||
return hashes
|
||||
}
|
||||
|
||||
// ReadAllCanonicalHashes retrieves all canonical number and hash mappings at the
|
||||
// certain chain range. If the accumulated entries reaches the given threshold,
|
||||
// abort the iteration and return the semi-finish result.
|
||||
@@ -656,34 +688,48 @@ func WriteBlock(db ethdb.KeyValueWriter, block *types.Block) {
|
||||
}
|
||||
|
||||
// WriteAncientBlock writes entire block data into ancient store and returns the total written size.
|
||||
func WriteAncientBlock(db ethdb.AncientWriter, block *types.Block, receipts types.Receipts, td *big.Int) int {
|
||||
// Encode all block components to RLP format.
|
||||
headerBlob, err := rlp.EncodeToBytes(block.Header())
|
||||
if err != nil {
|
||||
log.Crit("Failed to RLP encode block header", "err", err)
|
||||
func WriteAncientBlocks(db ethdb.AncientWriter, blocks []*types.Block, receipts []types.Receipts, td *big.Int) (int64, error) {
|
||||
var (
|
||||
tdSum = new(big.Int).Set(td)
|
||||
stReceipts []*types.ReceiptForStorage
|
||||
)
|
||||
return db.ModifyAncients(func(op ethdb.AncientWriteOp) error {
|
||||
for i, block := range blocks {
|
||||
// Convert receipts to storage format and sum up total difficulty.
|
||||
stReceipts = stReceipts[:0]
|
||||
for _, receipt := range receipts[i] {
|
||||
stReceipts = append(stReceipts, (*types.ReceiptForStorage)(receipt))
|
||||
}
|
||||
header := block.Header()
|
||||
if i > 0 {
|
||||
tdSum.Add(tdSum, header.Difficulty)
|
||||
}
|
||||
if err := writeAncientBlock(op, block, header, stReceipts, tdSum); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func writeAncientBlock(op ethdb.AncientWriteOp, block *types.Block, header *types.Header, receipts []*types.ReceiptForStorage, td *big.Int) error {
|
||||
num := block.NumberU64()
|
||||
if err := op.AppendRaw(freezerHashTable, num, block.Hash().Bytes()); err != nil {
|
||||
return fmt.Errorf("can't add block %d hash: %v", num, err)
|
||||
}
|
||||
bodyBlob, err := rlp.EncodeToBytes(block.Body())
|
||||
if err != nil {
|
||||
log.Crit("Failed to RLP encode body", "err", err)
|
||||
if err := op.Append(freezerHeaderTable, num, header); err != nil {
|
||||
return fmt.Errorf("can't append block header %d: %v", num, err)
|
||||
}
|
||||
storageReceipts := make([]*types.ReceiptForStorage, len(receipts))
|
||||
for i, receipt := range receipts {
|
||||
storageReceipts[i] = (*types.ReceiptForStorage)(receipt)
|
||||
if err := op.Append(freezerBodiesTable, num, block.Body()); err != nil {
|
||||
return fmt.Errorf("can't append block body %d: %v", num, err)
|
||||
}
|
||||
receiptBlob, err := rlp.EncodeToBytes(storageReceipts)
|
||||
if err != nil {
|
||||
log.Crit("Failed to RLP encode block receipts", "err", err)
|
||||
if err := op.Append(freezerReceiptTable, num, receipts); err != nil {
|
||||
return fmt.Errorf("can't append block %d receipts: %v", num, err)
|
||||
}
|
||||
tdBlob, err := rlp.EncodeToBytes(td)
|
||||
if err != nil {
|
||||
log.Crit("Failed to RLP encode block total difficulty", "err", err)
|
||||
if err := op.Append(freezerDifficultyTable, num, td); err != nil {
|
||||
return fmt.Errorf("can't append block %d total difficulty: %v", num, err)
|
||||
}
|
||||
// Write all blob to flatten files.
|
||||
err = db.AppendAncient(block.NumberU64(), block.Hash().Bytes(), headerBlob, bodyBlob, receiptBlob, tdBlob)
|
||||
if err != nil {
|
||||
log.Crit("Failed to write block data to ancient store", "err", err)
|
||||
}
|
||||
return len(headerBlob) + len(bodyBlob) + len(receiptBlob) + len(tdBlob) + common.HashLength
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteBlock removes all block data associated with a hash.
|
||||
|
Reference in New Issue
Block a user