mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 13:16:42 +00:00
soft truncate head
This commit is contained in:
parent
5c2bf5b33a
commit
1c431f6f95
12 changed files with 250 additions and 27 deletions
|
|
@ -994,15 +994,6 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha
|
||||||
// and the current freezer limit to start nuking it's underflown.
|
// and the current freezer limit to start nuking it's underflown.
|
||||||
pivot = rawdb.ReadLastPivotNumber(bc.db)
|
pivot = rawdb.ReadLastPivotNumber(bc.db)
|
||||||
)
|
)
|
||||||
const truncateInterval = 100
|
|
||||||
truncateFn := func() {
|
|
||||||
// Truncate triedb to align the state histories with the current state.
|
|
||||||
if bc.triedb.Scheme() == rawdb.PathScheme {
|
|
||||||
if err := bc.triedb.TruncateHead(); err != nil {
|
|
||||||
log.Crit("Failed to truncate trie database", "err", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateFn := func(db ethdb.KeyValueWriter, header *types.Header) (*types.Header, bool) {
|
updateFn := func(db ethdb.KeyValueWriter, header *types.Header) (*types.Header, bool) {
|
||||||
// Rewind the blockchain, ensuring we don't end up with a stateless head
|
// Rewind the blockchain, ensuring we don't end up with a stateless head
|
||||||
// block. Note, depth equality is permitted to allow using SetHead as a
|
// block. Note, depth equality is permitted to allow using SetHead as a
|
||||||
|
|
@ -1012,9 +1003,11 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha
|
||||||
newHeadBlock, rootNumber = bc.rewindHead(header, root)
|
newHeadBlock, rootNumber = bc.rewindHead(header, root)
|
||||||
rawdb.WriteHeadBlockHash(db, newHeadBlock.Hash())
|
rawdb.WriteHeadBlockHash(db, newHeadBlock.Hash())
|
||||||
|
|
||||||
// The truncate costs a lot of time, no need to run for each block
|
// Use soft truncate for fast rewind - actual truncation happens at the end
|
||||||
if header.Number.Uint64()%truncateInterval == 0 {
|
if bc.triedb.Scheme() == rawdb.PathScheme {
|
||||||
truncateFn()
|
if err := bc.triedb.SoftTruncateHead(); err != nil {
|
||||||
|
log.Warn("Failed to soft truncate trie database", "err", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Degrade the chain markers if they are explicitly reverted.
|
// Degrade the chain markers if they are explicitly reverted.
|
||||||
|
|
@ -1110,8 +1103,12 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha
|
||||||
bc.blockCache.Purge()
|
bc.blockCache.Purge()
|
||||||
bc.txLookupCache.Purge()
|
bc.txLookupCache.Purge()
|
||||||
|
|
||||||
// Truncate to align the state histories with the current state.
|
// Commit all soft truncations to align the state histories with the current state.
|
||||||
truncateFn()
|
if bc.triedb.Scheme() == rawdb.PathScheme {
|
||||||
|
if err := bc.triedb.CommitTruncation(); err != nil {
|
||||||
|
log.Crit("Failed to commit trie database truncation", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clear safe block, finalized block if needed
|
// Clear safe block, finalized block if needed
|
||||||
if safe := bc.CurrentSafeBlock(); safe != nil && head < safe.Number.Uint64() {
|
if safe := bc.CurrentSafeBlock(); safe != nil && head < safe.Number.Uint64() {
|
||||||
|
|
|
||||||
|
|
@ -415,6 +415,14 @@ func (f *chainFreezer) TruncateTail(items uint64) (uint64, error) {
|
||||||
return f.ancients.TruncateTail(items)
|
return f.ancients.TruncateTail(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *chainFreezer) SoftTruncateHead(items uint64) (uint64, error) {
|
||||||
|
return f.ancients.SoftTruncateHead(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *chainFreezer) CommitTruncation() error {
|
||||||
|
return f.ancients.CommitTruncation()
|
||||||
|
}
|
||||||
|
|
||||||
func (f *chainFreezer) SyncAncient() error {
|
func (f *chainFreezer) SyncAncient() error {
|
||||||
return f.ancients.SyncAncient()
|
return f.ancients.SyncAncient()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,16 @@ func (db *nofreezedb) TruncateTail(items uint64) (uint64, error) {
|
||||||
return 0, errNotSupported
|
return 0, errNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SoftTruncateHead returns an error as we don't have a backing chain freezer.
|
||||||
|
func (db *nofreezedb) SoftTruncateHead(items uint64) (uint64, error) {
|
||||||
|
return 0, errNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitTruncation returns an error as we don't have a backing chain freezer.
|
||||||
|
func (db *nofreezedb) CommitTruncation() error {
|
||||||
|
return errNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
// SyncAncient returns an error as we don't have a backing chain freezer.
|
// SyncAncient returns an error as we don't have a backing chain freezer.
|
||||||
func (db *nofreezedb) SyncAncient() error {
|
func (db *nofreezedb) SyncAncient() error {
|
||||||
return errNotSupported
|
return errNotSupported
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ type Freezer struct {
|
||||||
datadir string
|
datadir string
|
||||||
frozen atomic.Uint64 // Number of items already frozen
|
frozen atomic.Uint64 // Number of items already frozen
|
||||||
tail atomic.Uint64 // Number of the first stored item in the freezer
|
tail atomic.Uint64 // Number of the first stored item in the freezer
|
||||||
|
vHead atomic.Uint64 // Virtual head for soft truncation (0 means not set)
|
||||||
|
|
||||||
// This lock synchronizes writers and the truncate operation, as well as
|
// This lock synchronizes writers and the truncate operation, as well as
|
||||||
// the "atomic" (batched) read operations.
|
// the "atomic" (batched) read operations.
|
||||||
|
|
@ -203,6 +204,10 @@ func (f *Freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]
|
||||||
|
|
||||||
// Ancients returns the length of the frozen items.
|
// Ancients returns the length of the frozen items.
|
||||||
func (f *Freezer) Ancients() (uint64, error) {
|
func (f *Freezer) Ancients() (uint64, error) {
|
||||||
|
vHead := f.vHead.Load()
|
||||||
|
if vHead > 0 {
|
||||||
|
return vHead, nil
|
||||||
|
}
|
||||||
return f.frozen.Load(), nil
|
return f.frozen.Load(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,6 +318,61 @@ func (f *Freezer) TruncateTail(tail uint64) (uint64, error) {
|
||||||
return old, nil
|
return old, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SoftTruncateHead marks items above threshold as logically deleted without physical truncation.
|
||||||
|
// This is much faster than TruncateHead as it only updates a pointer.
|
||||||
|
func (f *Freezer) SoftTruncateHead(items uint64) (uint64, error) {
|
||||||
|
if f.readonly {
|
||||||
|
return 0, errReadOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
frozen := f.frozen.Load()
|
||||||
|
vHead := f.vHead.Load()
|
||||||
|
|
||||||
|
// Determine the current effective head
|
||||||
|
var currentHead uint64
|
||||||
|
if vHead > 0 {
|
||||||
|
currentHead = vHead
|
||||||
|
} else {
|
||||||
|
currentHead = frozen
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentHead <= items {
|
||||||
|
return currentHead, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we don't truncate below the tail
|
||||||
|
tail := f.tail.Load()
|
||||||
|
if items < tail {
|
||||||
|
return 0, fmt.Errorf("soft truncate below tail: tail=%d, target=%d", tail, items)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.vHead.Store(items)
|
||||||
|
return currentHead, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitTruncation performs the actual file truncation to match virtual head.
|
||||||
|
// This should be called after all soft truncations are complete.
|
||||||
|
func (f *Freezer) CommitTruncation() error {
|
||||||
|
if f.readonly {
|
||||||
|
return errReadOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
vHead := f.vHead.Load()
|
||||||
|
if vHead == 0 {
|
||||||
|
return nil // No pending truncation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the actual truncation
|
||||||
|
_, err := f.TruncateHead(vHead)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear virtual head since we've committed
|
||||||
|
f.vHead.Store(0)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SyncAncient flushes all data tables to disk.
|
// SyncAncient flushes all data tables to disk.
|
||||||
func (f *Freezer) SyncAncient() error {
|
func (f *Freezer) SyncAncient() error {
|
||||||
var errs []error
|
var errs []error
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,7 @@ func (b *memoryBatch) commit(freezer *MemoryFreezer) (items uint64, writeSize in
|
||||||
type MemoryFreezer struct {
|
type MemoryFreezer struct {
|
||||||
items uint64 // Number of items stored
|
items uint64 // Number of items stored
|
||||||
tail uint64 // Number of the first stored item in the freezer
|
tail uint64 // Number of the first stored item in the freezer
|
||||||
|
vHead uint64 // Virtual head for soft truncation (0 means not set)
|
||||||
readonly bool // Flag if the freezer is only for reading
|
readonly bool // Flag if the freezer is only for reading
|
||||||
lock sync.RWMutex // Lock to protect fields
|
lock sync.RWMutex // Lock to protect fields
|
||||||
tables map[string]*memoryTable // Tables for storing everything
|
tables map[string]*memoryTable // Tables for storing everything
|
||||||
|
|
@ -262,6 +263,9 @@ func (f *MemoryFreezer) Ancients() (uint64, error) {
|
||||||
f.lock.RLock()
|
f.lock.RLock()
|
||||||
defer f.lock.RUnlock()
|
defer f.lock.RUnlock()
|
||||||
|
|
||||||
|
if f.vHead > 0 {
|
||||||
|
return f.vHead, nil
|
||||||
|
}
|
||||||
return f.items, nil
|
return f.items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -338,17 +342,10 @@ func (f *MemoryFreezer) TruncateHead(items uint64) (uint64, error) {
|
||||||
if f.readonly {
|
if f.readonly {
|
||||||
return 0, errReadOnly
|
return 0, errReadOnly
|
||||||
}
|
}
|
||||||
old := f.items
|
|
||||||
if old <= items {
|
// Clear virtual head if we're doing a real truncation
|
||||||
return old, nil
|
f.vHead = 0
|
||||||
}
|
return f.truncateHead(items)
|
||||||
for _, table := range f.tables {
|
|
||||||
if err := table.truncateHead(items); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.items = items
|
|
||||||
return old, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TruncateTail discards all data below the provided threshold number.
|
// TruncateTail discards all data below the provided threshold number.
|
||||||
|
|
@ -376,6 +373,75 @@ func (f *MemoryFreezer) TruncateTail(tail uint64) (uint64, error) {
|
||||||
return old, nil
|
return old, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SoftTruncateHead marks items above threshold as logically deleted without physical truncation.
|
||||||
|
func (f *MemoryFreezer) SoftTruncateHead(items uint64) (uint64, error) {
|
||||||
|
f.lock.Lock()
|
||||||
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
|
if f.readonly {
|
||||||
|
return 0, errReadOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the current effective head
|
||||||
|
var currentHead uint64
|
||||||
|
if f.vHead > 0 {
|
||||||
|
currentHead = f.vHead
|
||||||
|
} else {
|
||||||
|
currentHead = f.items
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentHead <= items {
|
||||||
|
return currentHead, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we don't truncate below the tail
|
||||||
|
if items < f.tail {
|
||||||
|
return 0, fmt.Errorf("soft truncate below tail: tail=%d, target=%d", f.tail, items)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.vHead = items
|
||||||
|
return currentHead, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitTruncation performs the actual truncation to match virtual head.
|
||||||
|
func (f *MemoryFreezer) CommitTruncation() error {
|
||||||
|
f.lock.Lock()
|
||||||
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
|
if f.readonly {
|
||||||
|
return errReadOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.vHead == 0 {
|
||||||
|
return nil // No pending truncation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the actual truncation
|
||||||
|
_, err := f.truncateHead(f.vHead)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear virtual head since we've committed
|
||||||
|
f.vHead = 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// truncateHead is the internal method that does the actual head truncation.
|
||||||
|
func (f *MemoryFreezer) truncateHead(items uint64) (uint64, error) {
|
||||||
|
old := f.items
|
||||||
|
if old <= items {
|
||||||
|
return old, nil
|
||||||
|
}
|
||||||
|
for _, table := range f.tables {
|
||||||
|
if err := table.truncateHead(items); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.items = items
|
||||||
|
return old, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SyncAncient flushes all data tables to disk.
|
// SyncAncient flushes all data tables to disk.
|
||||||
func (f *MemoryFreezer) SyncAncient() error {
|
func (f *MemoryFreezer) SyncAncient() error {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -185,6 +185,22 @@ func (f *resettableFreezer) TruncateTail(tail uint64) (uint64, error) {
|
||||||
return f.freezer.TruncateTail(tail)
|
return f.freezer.TruncateTail(tail)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SoftTruncateHead marks items above threshold as logically deleted without physical truncation.
|
||||||
|
func (f *resettableFreezer) SoftTruncateHead(items uint64) (uint64, error) {
|
||||||
|
f.lock.RLock()
|
||||||
|
defer f.lock.RUnlock()
|
||||||
|
|
||||||
|
return f.freezer.SoftTruncateHead(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitTruncation performs the actual file truncation to match any soft truncations.
|
||||||
|
func (f *resettableFreezer) CommitTruncation() error {
|
||||||
|
f.lock.RLock()
|
||||||
|
defer f.lock.RUnlock()
|
||||||
|
|
||||||
|
return f.freezer.CommitTruncation()
|
||||||
|
}
|
||||||
|
|
||||||
// SyncAncient flushes all data tables to disk.
|
// SyncAncient flushes all data tables to disk.
|
||||||
func (f *resettableFreezer) SyncAncient() error {
|
func (f *resettableFreezer) SyncAncient() error {
|
||||||
f.lock.RLock()
|
f.lock.RLock()
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,18 @@ func (t *table) TruncateTail(items uint64) (uint64, error) {
|
||||||
return t.db.TruncateTail(items)
|
return t.db.TruncateTail(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SoftTruncateHead is a noop passthrough that just forwards the request to the underlying
|
||||||
|
// database.
|
||||||
|
func (t *table) SoftTruncateHead(items uint64) (uint64, error) {
|
||||||
|
return t.db.SoftTruncateHead(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitTruncation is a noop passthrough that just forwards the request to the underlying
|
||||||
|
// database.
|
||||||
|
func (t *table) CommitTruncation() error {
|
||||||
|
return t.db.CommitTruncation()
|
||||||
|
}
|
||||||
|
|
||||||
// SyncAncient is a noop passthrough that just forwards the request to the underlying
|
// SyncAncient is a noop passthrough that just forwards the request to the underlying
|
||||||
// database.
|
// database.
|
||||||
func (t *table) SyncAncient() error {
|
func (t *table) SyncAncient() error {
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,14 @@ type AncientWriter interface {
|
||||||
//
|
//
|
||||||
// Note that data marked as non-prunable will still be retained and remain accessible.
|
// Note that data marked as non-prunable will still be retained and remain accessible.
|
||||||
TruncateTail(n uint64) (uint64, error)
|
TruncateTail(n uint64) (uint64, error)
|
||||||
|
|
||||||
|
// SoftTruncateHead marks items above threshold as logically deleted without physical truncation.
|
||||||
|
// This is much faster than TruncateHead as it only updates a pointer.
|
||||||
|
SoftTruncateHead(n uint64) (uint64, error)
|
||||||
|
|
||||||
|
// CommitTruncation performs the actual file truncation to match any soft truncations.
|
||||||
|
// This should be called after all soft truncations are complete.
|
||||||
|
CommitTruncation() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// AncientWriteOp is given to the function argument of ModifyAncients.
|
// AncientWriteOp is given to the function argument of ModifyAncients.
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,16 @@ func (db *Database) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SoftTruncateHead returns an error as we don't have a backing chain freezer.
|
||||||
|
func (db *Database) SoftTruncateHead(items uint64) (uint64, error) {
|
||||||
|
panic("not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitTruncation returns an error as we don't have a backing chain freezer.
|
||||||
|
func (db *Database) CommitTruncation() error {
|
||||||
|
panic("not supported")
|
||||||
|
}
|
||||||
|
|
||||||
func New(client *rpc.Client) ethdb.Database {
|
func New(client *rpc.Client) ethdb.Database {
|
||||||
if client == nil {
|
if client == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -291,6 +291,26 @@ func (db *Database) TruncateHead() error {
|
||||||
return pdb.TruncateHead()
|
return pdb.TruncateHead()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SoftTruncateHead marks state histories above the bottom state layer as logically deleted
|
||||||
|
// without physical truncation. This is much faster than TruncateHead.
|
||||||
|
func (db *Database) SoftTruncateHead() error {
|
||||||
|
pdb, ok := db.backend.(*pathdb.Database)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not supported")
|
||||||
|
}
|
||||||
|
return pdb.SoftTruncateHead()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitTruncation performs the actual file truncation to match any soft truncations.
|
||||||
|
// This should be called after all soft truncations are complete.
|
||||||
|
func (db *Database) CommitTruncation() error {
|
||||||
|
pdb, ok := db.backend.(*pathdb.Database)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not supported")
|
||||||
|
}
|
||||||
|
return pdb.CommitTruncation()
|
||||||
|
}
|
||||||
|
|
||||||
// Recoverable returns the indicator if the specified state is enabled to be
|
// Recoverable returns the indicator if the specified state is enabled to be
|
||||||
// recovered. It's only supported by path-based database and will return an
|
// recovered. It's only supported by path-based database and will return an
|
||||||
// error for others.
|
// error for others.
|
||||||
|
|
|
||||||
|
|
@ -512,6 +512,19 @@ func (db *Database) TruncateHead() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SoftTruncateHead marks state histories above the bottom state layer as logically deleted
|
||||||
|
// without physical truncation. This is much faster than TruncateHead.
|
||||||
|
func (db *Database) SoftTruncateHead() error {
|
||||||
|
_, err := db.stateFreezer.SoftTruncateHead(db.tree.bottom().stateID())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommitTruncation performs the actual file truncation to match any soft truncations.
|
||||||
|
// This should be called after all soft truncations are complete.
|
||||||
|
func (db *Database) CommitTruncation() error {
|
||||||
|
return db.stateFreezer.CommitTruncation()
|
||||||
|
}
|
||||||
|
|
||||||
// Recoverable returns the indicator if the specified state is recoverable.
|
// Recoverable returns the indicator if the specified state is recoverable.
|
||||||
//
|
//
|
||||||
// The supplied root must be a valid trie hash value.
|
// The supplied root must be a valid trie hash value.
|
||||||
|
|
|
||||||
|
|
@ -600,8 +600,11 @@ func TestDatabaseRollback(t *testing.T) {
|
||||||
if err := tester.db.Recover(parent); err != nil {
|
if err := tester.db.Recover(parent); err != nil {
|
||||||
t.Fatalf("Failed to revert db, err: %v", err)
|
t.Fatalf("Failed to revert db, err: %v", err)
|
||||||
}
|
}
|
||||||
if err := tester.db.TruncateHead(); err != nil {
|
if err := tester.db.SoftTruncateHead(); err != nil {
|
||||||
t.Fatalf("Failed to truncate head, err: %v", err)
|
t.Fatalf("Failed to soft truncate head, err: %v", err)
|
||||||
|
}
|
||||||
|
if err := tester.db.CommitTruncation(); err != nil {
|
||||||
|
t.Fatalf("Failed to commit truncation, err: %v", err)
|
||||||
}
|
}
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
if err := tester.verifyState(parent); err != nil {
|
if err := tester.verifyState(parent); err != nil {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue