mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-24 08:49:29 +00:00
core, ethdb, cmd, triedb: intrudoce table group in freezer
This commit is contained in:
parent
31bb680997
commit
ff9c04e4c8
22 changed files with 322 additions and 164 deletions
|
|
@ -744,7 +744,7 @@ func pruneHistory(ctx *cli.Context) error {
|
|||
)
|
||||
|
||||
// Check the current freezer tail to see if pruning is needed/possible.
|
||||
freezerTail, _ := chaindb.Tail()
|
||||
freezerTail, _ := chaindb.Tail(rawdb.ChainFreezerBlockDataGroup)
|
||||
if freezerTail > 0 {
|
||||
if freezerTail == targetBlock {
|
||||
log.Info("Database already pruned to target block", "tail", freezerTail)
|
||||
|
|
@ -776,7 +776,7 @@ func pruneHistory(ctx *cli.Context) error {
|
|||
log.Info("Starting history pruning", "head", currentHeader.Number, "target", targetBlock, "targetHash", targetBlockHash.Hex())
|
||||
start := time.Now()
|
||||
rawdb.PruneTransactionIndex(chaindb, targetBlock)
|
||||
if _, err := chaindb.TruncateTail(targetBlock); err != nil {
|
||||
if _, err := chaindb.TruncateTail(rawdb.ChainFreezerBlockDataGroup, targetBlock); err != nil {
|
||||
return fmt.Errorf("failed to truncate ancient data: %v", err)
|
||||
}
|
||||
log.Info("History pruning completed", "tail", targetBlock, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
|
|
|
|||
|
|
@ -716,7 +716,7 @@ func (bc *BlockChain) loadLastState() error {
|
|||
|
||||
// initializeHistoryPruning sets bc.historyPrunePoint.
|
||||
func (bc *BlockChain) initializeHistoryPruning(latest uint64) error {
|
||||
freezerTail, _ := bc.db.Tail()
|
||||
freezerTail, _ := bc.db.Tail(rawdb.ChainFreezerBlockDataGroup)
|
||||
policy := bc.cfg.HistoryPolicy
|
||||
|
||||
switch policy.Mode {
|
||||
|
|
@ -2961,7 +2961,7 @@ func (bc *BlockChain) InsertHeadersBeforeCutoff(headers []*types.Header) (int, e
|
|||
}
|
||||
// Truncate the useless chain segment (zero bodies and receipts) in the
|
||||
// ancient store.
|
||||
if _, err := bc.db.TruncateTail(last.Number.Uint64() + 1); err != nil {
|
||||
if _, err := bc.db.TruncateTail(rawdb.ChainFreezerBlockDataGroup, last.Number.Uint64()+1); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Last step update all in-memory markers
|
||||
|
|
|
|||
|
|
@ -4386,7 +4386,7 @@ func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64,
|
|||
if header.Hash() != hash {
|
||||
t.Errorf("block #%d: header mismatch: want: %v, got: %v", num, hash, header.Hash())
|
||||
}
|
||||
tail, err := db.Tail()
|
||||
tail, err := db.Tail(rawdb.ChainFreezerBlockDataGroup)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get chain tail, %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,21 +37,35 @@ const (
|
|||
ChainFreezerReceiptTable = "receipts"
|
||||
)
|
||||
|
||||
// Identifiers of tail groups used by the chain freezer.
|
||||
const (
|
||||
// ChainFreezerBlockDataGroup is the tail group shared by the body and
|
||||
// receipt tables. The two tables are pruned together and therefore have
|
||||
// the same tail position.
|
||||
ChainFreezerBlockDataGroup = "blockdata"
|
||||
)
|
||||
|
||||
// chainFreezerTableConfigs configures the settings for tables in the chain freezer.
|
||||
// Compression is disabled for hashes as they don't compress well. Additionally,
|
||||
// tail truncation is disabled for the header and hash tables, as these are intended
|
||||
// to be retained long-term.
|
||||
var chainFreezerTableConfigs = map[string]freezerTableConfig{
|
||||
ChainFreezerHeaderTable: {noSnappy: false, prunable: false},
|
||||
ChainFreezerHashTable: {noSnappy: true, prunable: false},
|
||||
ChainFreezerBodiesTable: {noSnappy: false, prunable: true},
|
||||
ChainFreezerReceiptTable: {noSnappy: false, prunable: true},
|
||||
ChainFreezerHeaderTable: {noSnappy: false},
|
||||
ChainFreezerHashTable: {noSnappy: true},
|
||||
ChainFreezerBodiesTable: {noSnappy: false, tailGroup: ChainFreezerBlockDataGroup},
|
||||
ChainFreezerReceiptTable: {noSnappy: false, tailGroup: ChainFreezerBlockDataGroup},
|
||||
}
|
||||
|
||||
// freezerTableConfig contains the settings for a freezer table.
|
||||
type freezerTableConfig struct {
|
||||
noSnappy bool // disables item compression
|
||||
prunable bool // true for tables that can be pruned by TruncateTail
|
||||
// noSnappy disables item compression when true.
|
||||
noSnappy bool
|
||||
|
||||
// tailGroup names a logical group of tables that share the same tail
|
||||
// position. Tables in the same group are pruned together and must agree
|
||||
// on their tail. An empty value means the table is not prunable; its
|
||||
// tail is always 0.
|
||||
tailGroup string
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
@ -66,13 +80,16 @@ const (
|
|||
stateHistoryStorageData = "storage.data"
|
||||
)
|
||||
|
||||
// StateHistoryTailGroup is the tail group shared by all state history tables.
|
||||
const StateHistoryTailGroup = "history"
|
||||
|
||||
// stateFreezerTableConfigs configures the settings for tables in the state freezer.
|
||||
var stateFreezerTableConfigs = map[string]freezerTableConfig{
|
||||
stateHistoryMeta: {noSnappy: true, prunable: true},
|
||||
stateHistoryAccountIndex: {noSnappy: false, prunable: true},
|
||||
stateHistoryStorageIndex: {noSnappy: false, prunable: true},
|
||||
stateHistoryAccountData: {noSnappy: false, prunable: true},
|
||||
stateHistoryStorageData: {noSnappy: false, prunable: true},
|
||||
stateHistoryMeta: {noSnappy: true, tailGroup: StateHistoryTailGroup},
|
||||
stateHistoryAccountIndex: {noSnappy: false, tailGroup: StateHistoryTailGroup},
|
||||
stateHistoryStorageIndex: {noSnappy: false, tailGroup: StateHistoryTailGroup},
|
||||
stateHistoryAccountData: {noSnappy: false, tailGroup: StateHistoryTailGroup},
|
||||
stateHistoryStorageData: {noSnappy: false, tailGroup: StateHistoryTailGroup},
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
@ -81,15 +98,18 @@ const (
|
|||
trienodeHistoryValueSectionTable = "trienode.value"
|
||||
)
|
||||
|
||||
// TrienodeHistoryTailGroup is the tail group shared by all trienode history tables.
|
||||
const TrienodeHistoryTailGroup = "history"
|
||||
|
||||
// trienodeFreezerTableConfigs configures the settings for tables in the trienode freezer.
|
||||
var trienodeFreezerTableConfigs = map[string]freezerTableConfig{
|
||||
trienodeHistoryHeaderTable: {noSnappy: false, prunable: true},
|
||||
trienodeHistoryHeaderTable: {noSnappy: false, tailGroup: TrienodeHistoryTailGroup},
|
||||
|
||||
// Disable snappy compression to allow efficient partial read.
|
||||
trienodeHistoryKeySectionTable: {noSnappy: true, prunable: true},
|
||||
trienodeHistoryKeySectionTable: {noSnappy: true, tailGroup: TrienodeHistoryTailGroup},
|
||||
|
||||
// Disable snappy compression to allow efficient partial read.
|
||||
trienodeHistoryValueSectionTable: {noSnappy: true, prunable: true},
|
||||
trienodeHistoryValueSectionTable: {noSnappy: true, tailGroup: TrienodeHistoryTailGroup},
|
||||
}
|
||||
|
||||
// The list of identifiers of ancient stores.
|
||||
|
|
|
|||
|
|
@ -67,13 +67,24 @@ func inspect(name string, order map[string]freezerTableConfig, reader ethdb.Anci
|
|||
info.head = 0
|
||||
}
|
||||
|
||||
// Retrieve the number of first stored item
|
||||
tail, err := reader.Tail()
|
||||
if err != nil {
|
||||
return freezerInfo{}, err
|
||||
// Retrieve the highest tail across all known tail groups. The inspected
|
||||
// freezer info uses a single tail value for display, which corresponds to
|
||||
// the most-pruned group.
|
||||
groups := make(map[string]struct{})
|
||||
for _, cfg := range order {
|
||||
if cfg.tailGroup != "" {
|
||||
groups[cfg.tailGroup] = struct{}{}
|
||||
}
|
||||
}
|
||||
for g := range groups {
|
||||
t, err := reader.Tail(g)
|
||||
if err != nil {
|
||||
return freezerInfo{}, err
|
||||
}
|
||||
if t > info.tail {
|
||||
info.tail = t
|
||||
}
|
||||
}
|
||||
info.tail = tail
|
||||
|
||||
if ancients == 0 {
|
||||
info.count = 0
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,11 @@ import (
|
|||
"github.com/ethereum/go-ethereum/internal/testrand"
|
||||
)
|
||||
|
||||
// TailGroup is the tail group used by tables created in this test suite. The
|
||||
// store factory passed to TestAncientSuite must wire its tables to this group
|
||||
// so that the suite can query the freezer's tail consistently.
|
||||
const TailGroup = "test"
|
||||
|
||||
// TestAncientSuite runs a suite of tests against an ancient database
|
||||
// implementation.
|
||||
func TestAncientSuite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
|
||||
|
|
@ -58,11 +63,11 @@ func basicRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
|
|||
}); err != nil {
|
||||
t.Fatalf("Failed to write ancient data %v", err)
|
||||
}
|
||||
db.TruncateTail(10)
|
||||
db.TruncateTail(TailGroup, 10)
|
||||
db.TruncateHead(90)
|
||||
|
||||
// Test basic tail and head retrievals
|
||||
tail, err := db.Tail()
|
||||
tail, err := db.Tail(TailGroup)
|
||||
if err != nil || tail != 10 {
|
||||
t.Fatal("Failed to retrieve tail")
|
||||
}
|
||||
|
|
@ -123,7 +128,7 @@ func batchRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
|
|||
}); err != nil {
|
||||
t.Fatalf("Failed to write ancient data %v", err)
|
||||
}
|
||||
db.TruncateTail(10)
|
||||
db.TruncateTail(TailGroup, 10)
|
||||
db.TruncateHead(90)
|
||||
|
||||
// Test the items in range should be reachable
|
||||
|
|
@ -262,12 +267,12 @@ func basicWrite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
|
|||
}
|
||||
|
||||
// Write should work after truncating from tail but over the head
|
||||
db.TruncateTail(200)
|
||||
db.TruncateTail(TailGroup, 200)
|
||||
head, err := db.Ancients()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to retrieve head ancients %v", err)
|
||||
}
|
||||
tail, err := db.Tail()
|
||||
tail, err := db.Tail(TailGroup)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to retrieve tail ancients %v", err)
|
||||
}
|
||||
|
|
@ -293,7 +298,7 @@ func basicWrite(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
|
|||
if err != nil {
|
||||
t.Fatalf("Failed to retrieve head ancients %v", err)
|
||||
}
|
||||
tail, err = db.Tail()
|
||||
tail, err = db.Tail(TailGroup)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to retrieve tail ancients %v", err)
|
||||
}
|
||||
|
|
@ -351,7 +356,7 @@ func TestResettableAncientSuite(t *testing.T, newFn func(kinds []string) ethdb.R
|
|||
}); err != nil {
|
||||
t.Fatalf("Failed to write ancient data %v", err)
|
||||
}
|
||||
db.TruncateTail(10)
|
||||
db.TruncateTail(TailGroup, 10)
|
||||
db.TruncateHead(90)
|
||||
|
||||
// Ancient write should work after resetting
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@ func (f *chainFreezer) Ancient(kind string, number uint64) ([]byte, error) {
|
|||
if kind == ChainFreezerHeaderTable || kind == ChainFreezerHashTable {
|
||||
return f.ancients.Ancient(kind, number)
|
||||
}
|
||||
tail, err := f.ancients.Tail()
|
||||
tail, err := f.ancients.Tail(tableTailGroup(kind))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -375,6 +375,16 @@ func (f *chainFreezer) Ancient(kind string, number uint64) ([]byte, error) {
|
|||
return nil, errUnknownTable
|
||||
}
|
||||
|
||||
// tableTailGroup returns the tail group identifier for a chain freezer table.
|
||||
// Unknown tables resolve to the default block-data group, since the chain
|
||||
// freezer's only prunable group today is bodies+receipts.
|
||||
func tableTailGroup(kind string) string {
|
||||
if cfg, ok := chainFreezerTableConfigs[kind]; ok {
|
||||
return cfg.tailGroup
|
||||
}
|
||||
return ChainFreezerBlockDataGroup
|
||||
}
|
||||
|
||||
// ReadAncients executes an operation while preventing mutations to the freezer,
|
||||
// i.e. if fn performs multiple reads, they will be consistent with each other.
|
||||
func (f *chainFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) {
|
||||
|
|
@ -391,8 +401,8 @@ func (f *chainFreezer) Ancients() (uint64, error) {
|
|||
return f.ancients.Ancients()
|
||||
}
|
||||
|
||||
func (f *chainFreezer) Tail() (uint64, error) {
|
||||
return f.ancients.Tail()
|
||||
func (f *chainFreezer) Tail(group string) (uint64, error) {
|
||||
return f.ancients.Tail(group)
|
||||
}
|
||||
|
||||
func (f *chainFreezer) AncientSize(kind string) (uint64, error) {
|
||||
|
|
@ -415,8 +425,8 @@ func (f *chainFreezer) TruncateHead(items uint64) (uint64, error) {
|
|||
return f.ancients.TruncateHead(items)
|
||||
}
|
||||
|
||||
func (f *chainFreezer) TruncateTail(items uint64) (uint64, error) {
|
||||
return f.ancients.TruncateTail(items)
|
||||
func (f *chainFreezer) TruncateTail(group string, items uint64) (uint64, error) {
|
||||
return f.ancients.TruncateTail(group, items)
|
||||
}
|
||||
|
||||
func (f *chainFreezer) SyncAncient() error {
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ func (db *nofreezedb) Ancients() (uint64, error) {
|
|||
}
|
||||
|
||||
// Tail returns an error as we don't have a backing chain freezer.
|
||||
func (db *nofreezedb) Tail() (uint64, error) {
|
||||
func (db *nofreezedb) Tail(group string) (uint64, error) {
|
||||
return 0, errNotSupported
|
||||
}
|
||||
|
||||
|
|
@ -133,7 +133,7 @@ func (db *nofreezedb) TruncateHead(items uint64) (uint64, error) {
|
|||
}
|
||||
|
||||
// TruncateTail returns an error as we don't have a backing chain freezer.
|
||||
func (db *nofreezedb) TruncateTail(items uint64) (uint64, error) {
|
||||
func (db *nofreezedb) TruncateTail(group string, items uint64) (uint64, error) {
|
||||
return 0, errNotSupported
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ const freezerTableSize = 2 * 1000 * 1000 * 1000
|
|||
type Freezer struct {
|
||||
datadir string
|
||||
head atomic.Uint64 // Number of items stored (including items removed from tail)
|
||||
tail atomic.Uint64 // Number of the first stored item in the freezer
|
||||
|
||||
// This lock synchronizes writers and the truncate operation, as well as
|
||||
// the "atomic" (batched) read operations.
|
||||
|
|
@ -216,9 +215,33 @@ func (f *Freezer) Ancients() (uint64, error) {
|
|||
return f.head.Load(), nil
|
||||
}
|
||||
|
||||
// Tail returns the number of first stored item in the freezer.
|
||||
func (f *Freezer) Tail() (uint64, error) {
|
||||
return f.tail.Load(), nil
|
||||
// Tail returns the lowest accessible item index for the given tail group.
|
||||
// All tables sharing this group must agree on the tail; an empty group name
|
||||
// refers to non-prunable tables and always returns 0.
|
||||
func (f *Freezer) Tail(group string) (uint64, error) {
|
||||
if group == "" {
|
||||
return 0, nil
|
||||
}
|
||||
var (
|
||||
tail uint64
|
||||
found bool
|
||||
)
|
||||
for _, table := range f.tables {
|
||||
if table.config.tailGroup != group {
|
||||
continue
|
||||
}
|
||||
h := table.itemHidden.Load()
|
||||
if !found {
|
||||
tail = h
|
||||
found = true
|
||||
} else if h != tail {
|
||||
return 0, fmt.Errorf("inconsistent tail in group %q: %d vs %d", group, h, tail)
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return 0, fmt.Errorf("unknown tail group: %q", group)
|
||||
}
|
||||
return tail, nil
|
||||
}
|
||||
|
||||
// AncientSize returns the ancient size of the specified category.
|
||||
|
|
@ -299,33 +322,49 @@ func (f *Freezer) TruncateHead(items uint64) (uint64, error) {
|
|||
return oitems, nil
|
||||
}
|
||||
|
||||
// TruncateTail discards all data below the specified threshold. Note that only
|
||||
// 'prunable' tables will be truncated.
|
||||
func (f *Freezer) TruncateTail(tail uint64) (uint64, error) {
|
||||
// TruncateTail discards all data below the specified threshold across every
|
||||
// table that belongs to the named tail group. Tables that are already past
|
||||
// the threshold are left untouched. The previous tail of the group is
|
||||
// returned. An empty group name or an unknown group name returns an error.
|
||||
func (f *Freezer) TruncateTail(group string, tail uint64) (uint64, error) {
|
||||
if f.readonly {
|
||||
return 0, errReadOnly
|
||||
}
|
||||
if group == "" {
|
||||
return 0, errors.New("empty tail group")
|
||||
}
|
||||
f.writeLock.Lock()
|
||||
defer f.writeLock.Unlock()
|
||||
|
||||
old := f.tail.Load()
|
||||
if old >= tail {
|
||||
return old, nil
|
||||
}
|
||||
var (
|
||||
prev uint64
|
||||
found bool
|
||||
)
|
||||
for _, table := range f.tables {
|
||||
if table.config.prunable {
|
||||
if err := table.truncateTail(tail); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if table.config.tailGroup != group {
|
||||
continue
|
||||
}
|
||||
if !found {
|
||||
prev = table.itemHidden.Load()
|
||||
found = true
|
||||
}
|
||||
}
|
||||
f.tail.Store(tail)
|
||||
|
||||
// Update the head if the requested tail exceeds the current head
|
||||
if !found {
|
||||
return 0, fmt.Errorf("unknown tail group: %q", group)
|
||||
}
|
||||
for _, table := range f.tables {
|
||||
if table.config.tailGroup != group {
|
||||
continue
|
||||
}
|
||||
if err := table.truncateTail(tail); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
// Update the head if the requested tail exceeds the current head.
|
||||
if f.head.Load() < tail {
|
||||
f.head.Store(tail)
|
||||
}
|
||||
return old, nil
|
||||
return prev, nil
|
||||
}
|
||||
|
||||
// SyncAncient flushes all data tables to disk.
|
||||
|
|
@ -342,84 +381,109 @@ func (f *Freezer) SyncAncient() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// validate checks that every table has the same boundary.
|
||||
// Used instead of `repair` in readonly mode.
|
||||
// validate checks that every table has the same head and that tables sharing
|
||||
// a tail group also share a tail. Used instead of `repair` in readonly mode.
|
||||
func (f *Freezer) validate() error {
|
||||
if len(f.tables) == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
head uint64
|
||||
prunedTail *uint64
|
||||
headSet bool
|
||||
groupTails = make(map[string]uint64)
|
||||
)
|
||||
// get any head value
|
||||
for _, table := range f.tables {
|
||||
head = table.items.Load()
|
||||
break
|
||||
}
|
||||
for kind, table := range f.tables {
|
||||
// all tables have to have the same head
|
||||
if head != table.items.Load() {
|
||||
return fmt.Errorf("freezer table %s has a differing head: %d != %d", kind, table.items.Load(), head)
|
||||
items := table.items.Load()
|
||||
if !headSet {
|
||||
head = items
|
||||
headSet = true
|
||||
} else if items != head {
|
||||
return fmt.Errorf("freezer table %s has a differing head: %d != %d", kind, items, head)
|
||||
}
|
||||
if !table.config.prunable {
|
||||
// non-prunable tables have to start at 0
|
||||
if table.config.tailGroup == "" {
|
||||
if table.itemHidden.Load() != 0 {
|
||||
return fmt.Errorf("non-prunable freezer table '%s' has a non-zero tail: %d", kind, table.itemHidden.Load())
|
||||
}
|
||||
continue
|
||||
}
|
||||
hidden := table.itemHidden.Load()
|
||||
if t, ok := groupTails[table.config.tailGroup]; ok {
|
||||
if t != hidden {
|
||||
return fmt.Errorf("freezer table %s has differing tail in group %q: %d != %d", kind, table.config.tailGroup, hidden, t)
|
||||
}
|
||||
} else {
|
||||
// prunable tables have to have the same length
|
||||
if prunedTail == nil {
|
||||
tmp := table.itemHidden.Load()
|
||||
prunedTail = &tmp
|
||||
}
|
||||
if *prunedTail != table.itemHidden.Load() {
|
||||
return fmt.Errorf("freezer table %s has differing tail: %d != %d", kind, table.itemHidden.Load(), *prunedTail)
|
||||
}
|
||||
groupTails[table.config.tailGroup] = hidden
|
||||
}
|
||||
}
|
||||
|
||||
if prunedTail == nil {
|
||||
tmp := uint64(0)
|
||||
prunedTail = &tmp
|
||||
}
|
||||
|
||||
f.head.Store(head)
|
||||
f.tail.Store(*prunedTail)
|
||||
return nil
|
||||
}
|
||||
|
||||
// repair truncates all data tables to the same length.
|
||||
// repair brings every table into a consistent state. The common head is taken
|
||||
// as the minimum item count among non-empty tables; freshly added empty tables
|
||||
// are fast-forwarded to that head via tail truncation. Within each tail group
|
||||
// the maximum tail wins, and prunable tables are truncated to it.
|
||||
func (f *Freezer) repair() error {
|
||||
// Determine the common head from non-empty tables. Empty tables are
|
||||
// excluded so that a freshly added table cannot drag the existing head
|
||||
// down to zero on first cold-start.
|
||||
var (
|
||||
head = uint64(math.MaxUint64)
|
||||
prunedTail = uint64(0)
|
||||
hasNonEmpty bool
|
||||
head uint64 = math.MaxUint64
|
||||
)
|
||||
// get the minimal head and the maximum tail
|
||||
for _, table := range f.tables {
|
||||
head = min(head, table.items.Load())
|
||||
prunedTail = max(prunedTail, table.itemHidden.Load())
|
||||
if table.items.Load() == 0 {
|
||||
continue
|
||||
}
|
||||
if items := table.items.Load(); items < head {
|
||||
head = items
|
||||
}
|
||||
hasNonEmpty = true
|
||||
}
|
||||
// apply the pruning
|
||||
for kind, table := range f.tables {
|
||||
// all tables need to have the same head
|
||||
if !hasNonEmpty {
|
||||
head = 0
|
||||
}
|
||||
// Align newly added empty tables to the common head. truncateTail
|
||||
// internally calls resetTo when the requested tail exceeds the current
|
||||
// head, which is exactly what we need here.
|
||||
if head > 0 {
|
||||
for _, table := range f.tables {
|
||||
if table.items.Load() == 0 {
|
||||
if err := table.truncateTail(head); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Truncate every table to the common head.
|
||||
for _, table := range f.tables {
|
||||
if err := table.truncateHead(head); err != nil {
|
||||
return err
|
||||
}
|
||||
if !table.config.prunable {
|
||||
// non-prunable tables have to start at 0
|
||||
}
|
||||
// Per-group tail alignment: take the maximum tail in each group and apply
|
||||
// it to all members. Non-prunable tables must remain at tail 0.
|
||||
groupTails := make(map[string]uint64)
|
||||
for kind, table := range f.tables {
|
||||
if table.config.tailGroup == "" {
|
||||
if table.itemHidden.Load() != 0 {
|
||||
panic(fmt.Sprintf("non-prunable freezer table %s has non-zero tail: %v", kind, table.itemHidden.Load()))
|
||||
}
|
||||
} else {
|
||||
// prunable tables have to have the same length
|
||||
if err := table.truncateTail(prunedTail); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
hidden := table.itemHidden.Load()
|
||||
if t, ok := groupTails[table.config.tailGroup]; !ok || hidden > t {
|
||||
groupTails[table.config.tailGroup] = hidden
|
||||
}
|
||||
}
|
||||
for _, table := range f.tables {
|
||||
if table.config.tailGroup == "" {
|
||||
continue
|
||||
}
|
||||
if err := table.truncateTail(groupTails[table.config.tailGroup]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
f.head.Store(head)
|
||||
f.tail.Store(prunedTail)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,7 +228,6 @@ func (b *memoryBatch) commit(freezer *MemoryFreezer) (items uint64, writeSize in
|
|||
// interface and can be used along with ephemeral key-value store.
|
||||
type MemoryFreezer struct {
|
||||
items uint64 // Number of items stored
|
||||
tail uint64 // Number of the first stored item in the freezer
|
||||
readonly bool // Flag if the freezer is only for reading
|
||||
lock sync.RWMutex // Lock to protect fields
|
||||
tables map[string]*memoryTable // Tables for storing everything
|
||||
|
|
@ -289,13 +288,35 @@ func (f *MemoryFreezer) Ancients() (uint64, error) {
|
|||
return f.items, nil
|
||||
}
|
||||
|
||||
// Tail returns the number of first stored item in the freezer.
|
||||
// This number can also be interpreted as the total deleted item numbers.
|
||||
func (f *MemoryFreezer) Tail() (uint64, error) {
|
||||
// Tail returns the lowest accessible item index for the given tail group.
|
||||
// All tables sharing the group must agree on the tail; an empty group name
|
||||
// refers to non-prunable tables and always returns 0.
|
||||
func (f *MemoryFreezer) Tail(group string) (uint64, error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
return f.tail, nil
|
||||
if group == "" {
|
||||
return 0, nil
|
||||
}
|
||||
var (
|
||||
tail uint64
|
||||
found bool
|
||||
)
|
||||
for _, table := range f.tables {
|
||||
if table.config.tailGroup != group {
|
||||
continue
|
||||
}
|
||||
if !found {
|
||||
tail = table.offset
|
||||
found = true
|
||||
} else if table.offset != tail {
|
||||
return 0, fmt.Errorf("inconsistent tail in group %q: %d vs %d", group, table.offset, tail)
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return 0, fmt.Errorf("unknown tail group: %q", group)
|
||||
}
|
||||
return tail, nil
|
||||
}
|
||||
|
||||
// AncientSize returns the ancient size of the specified category.
|
||||
|
|
@ -375,32 +396,47 @@ func (f *MemoryFreezer) TruncateHead(items uint64) (uint64, error) {
|
|||
return old, nil
|
||||
}
|
||||
|
||||
// TruncateTail discards all data below the provided threshold number.
|
||||
// Note this will only truncate 'prunable' tables. Block headers and canonical
|
||||
// hashes cannot be truncated at this time.
|
||||
func (f *MemoryFreezer) TruncateTail(tail uint64) (uint64, error) {
|
||||
// TruncateTail discards all data below the provided threshold across every
|
||||
// table that belongs to the named tail group. Tables already past the
|
||||
// threshold are left untouched. The previous tail of the group is returned.
|
||||
func (f *MemoryFreezer) TruncateTail(group string, tail uint64) (uint64, error) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
if f.readonly {
|
||||
return 0, errReadOnly
|
||||
}
|
||||
old := f.tail
|
||||
if old >= tail {
|
||||
return old, nil
|
||||
if group == "" {
|
||||
return 0, errors.New("empty tail group")
|
||||
}
|
||||
var (
|
||||
prev uint64
|
||||
found bool
|
||||
)
|
||||
for _, table := range f.tables {
|
||||
if table.config.prunable {
|
||||
if err := table.truncateTail(tail); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if table.config.tailGroup != group {
|
||||
continue
|
||||
}
|
||||
if !found {
|
||||
prev = table.offset
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return 0, fmt.Errorf("unknown tail group: %q", group)
|
||||
}
|
||||
for _, table := range f.tables {
|
||||
if table.config.tailGroup != group {
|
||||
continue
|
||||
}
|
||||
if err := table.truncateTail(tail); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
f.tail = tail
|
||||
if f.items < tail {
|
||||
f.items = tail
|
||||
}
|
||||
return old, nil
|
||||
return prev, nil
|
||||
}
|
||||
|
||||
// SyncAncient flushes all data tables to disk.
|
||||
|
|
@ -430,7 +466,7 @@ func (f *MemoryFreezer) Reset() error {
|
|||
tables[name] = newMemoryTable(name, table.config)
|
||||
}
|
||||
f.tables = tables
|
||||
f.items, f.tail = 0, 0
|
||||
f.items = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ func TestMemoryFreezer(t *testing.T) {
|
|||
tables := make(map[string]freezerTableConfig)
|
||||
for _, kind := range kinds {
|
||||
tables[kind] = freezerTableConfig{
|
||||
noSnappy: true,
|
||||
prunable: true,
|
||||
noSnappy: true,
|
||||
tailGroup: ancienttest.TailGroup,
|
||||
}
|
||||
}
|
||||
return NewMemoryFreezer(false, tables)
|
||||
|
|
@ -38,8 +38,8 @@ func TestMemoryFreezer(t *testing.T) {
|
|||
tables := make(map[string]freezerTableConfig)
|
||||
for _, kind := range kinds {
|
||||
tables[kind] = freezerTableConfig{
|
||||
noSnappy: true,
|
||||
prunable: true,
|
||||
noSnappy: true,
|
||||
tailGroup: ancienttest.TailGroup,
|
||||
}
|
||||
}
|
||||
return NewMemoryFreezer(false, tables)
|
||||
|
|
|
|||
|
|
@ -143,12 +143,12 @@ func (f *resettableFreezer) Ancients() (uint64, error) {
|
|||
return f.freezer.Ancients()
|
||||
}
|
||||
|
||||
// Tail returns the number of first stored item in the freezer.
|
||||
func (f *resettableFreezer) Tail() (uint64, error) {
|
||||
// Tail returns the lowest accessible item index for the given tail group.
|
||||
func (f *resettableFreezer) Tail(group string) (uint64, error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
return f.freezer.Tail()
|
||||
return f.freezer.Tail(group)
|
||||
}
|
||||
|
||||
// AncientSize returns the ancient size of the specified category.
|
||||
|
|
@ -185,13 +185,13 @@ func (f *resettableFreezer) TruncateHead(items uint64) (uint64, error) {
|
|||
return f.freezer.TruncateHead(items)
|
||||
}
|
||||
|
||||
// TruncateTail discards any recent data below the provided threshold number.
|
||||
// It returns the previous value
|
||||
func (f *resettableFreezer) TruncateTail(tail uint64) (uint64, error) {
|
||||
// TruncateTail discards data below the provided threshold for the named tail
|
||||
// group. It returns the previous tail of the group.
|
||||
func (f *resettableFreezer) TruncateTail(group string, tail uint64) (uint64, error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
|
||||
return f.freezer.TruncateTail(tail)
|
||||
return f.freezer.TruncateTail(group, tail)
|
||||
}
|
||||
|
||||
// SyncAncient flushes all data tables to disk.
|
||||
|
|
|
|||
|
|
@ -398,8 +398,8 @@ func TestFreezerSuite(t *testing.T) {
|
|||
tables := make(map[string]freezerTableConfig)
|
||||
for _, kind := range kinds {
|
||||
tables[kind] = freezerTableConfig{
|
||||
noSnappy: true,
|
||||
prunable: true,
|
||||
noSnappy: true,
|
||||
tailGroup: ancienttest.TailGroup,
|
||||
}
|
||||
}
|
||||
f, _ := newFreezerForTesting(t, tables)
|
||||
|
|
@ -409,8 +409,8 @@ func TestFreezerSuite(t *testing.T) {
|
|||
tables := make(map[string]freezerTableConfig)
|
||||
for _, kind := range kinds {
|
||||
tables[kind] = freezerTableConfig{
|
||||
noSnappy: true,
|
||||
prunable: true,
|
||||
noSnappy: true,
|
||||
tailGroup: ancienttest.TailGroup,
|
||||
}
|
||||
}
|
||||
f, _ := newResettableFreezer(t.TempDir(), "", false, 2048, tables)
|
||||
|
|
|
|||
|
|
@ -76,8 +76,8 @@ func (t *table) Ancients() (uint64, error) {
|
|||
|
||||
// Tail is a noop passthrough that just forwards the request to the underlying
|
||||
// database.
|
||||
func (t *table) Tail() (uint64, error) {
|
||||
return t.db.Tail()
|
||||
func (t *table) Tail(group string) (uint64, error) {
|
||||
return t.db.Tail(group)
|
||||
}
|
||||
|
||||
// AncientSize is a noop passthrough that just forwards the request to the underlying
|
||||
|
|
@ -103,8 +103,8 @@ func (t *table) TruncateHead(items uint64) (uint64, error) {
|
|||
|
||||
// TruncateTail is a noop passthrough that just forwards the request to the underlying
|
||||
// database.
|
||||
func (t *table) TruncateTail(items uint64) (uint64, error) {
|
||||
return t.db.TruncateTail(items)
|
||||
func (t *table) TruncateTail(group string, items uint64) (uint64, error) {
|
||||
return t.db.TruncateTail(group, items)
|
||||
}
|
||||
|
||||
// SyncAncient is a noop passthrough that just forwards the request to the underlying
|
||||
|
|
|
|||
|
|
@ -128,9 +128,12 @@ type AncientReaderOp interface {
|
|||
// Ancients returns the ancient item numbers in the ancient store.
|
||||
Ancients() (uint64, error)
|
||||
|
||||
// Tail returns the number of first stored item in the ancient store.
|
||||
// This number can also be interpreted as the total deleted items.
|
||||
Tail() (uint64, error)
|
||||
// Tail returns the lowest accessible item index for the given tail group.
|
||||
// This number can also be interpreted as the total deleted items in the
|
||||
// group. Tables sharing a group are pruned together and therefore agree
|
||||
// on the value. An empty group name refers to non-prunable tables and
|
||||
// always returns 0.
|
||||
Tail(group string) (uint64, error)
|
||||
|
||||
// AncientSize returns the ancient size of the specified category.
|
||||
AncientSize(kind string) (uint64, error)
|
||||
|
|
@ -159,14 +162,16 @@ type AncientWriter interface {
|
|||
// After the truncation, the latest item can be accessed it item_n-1(start from 0).
|
||||
TruncateHead(n uint64) (uint64, error)
|
||||
|
||||
// TruncateTail discards the first n ancient data from the ancient store. The already
|
||||
// deleted items are ignored. After the truncation, the earliest item can be accessed
|
||||
// is item_n(start from 0). The deleted items may not be removed from the ancient store
|
||||
// immediately, but only when the accumulated deleted data reach the threshold then
|
||||
// will be removed all together.
|
||||
// TruncateTail discards the first n items from every table belonging to
|
||||
// the named tail group. Already-deleted items are ignored. After the
|
||||
// truncation, the earliest accessible item in the group is item_n
|
||||
// (starting from 0). Deleted items may not be removed from disk
|
||||
// immediately, but only once the accumulated deleted data reaches the
|
||||
// threshold, at which point they are removed all together.
|
||||
//
|
||||
// Note that data marked as non-prunable will still be retained and remain accessible.
|
||||
TruncateTail(n uint64) (uint64, error)
|
||||
// The previous tail of the group is returned. Tables outside the group
|
||||
// (including non-prunable ones) are untouched.
|
||||
TruncateTail(group string, n uint64) (uint64, error)
|
||||
}
|
||||
|
||||
// AncientWriteOp is given to the function argument of ModifyAncients.
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ func (db *Database) Ancients() (uint64, error) {
|
|||
return resp, err
|
||||
}
|
||||
|
||||
func (db *Database) Tail() (uint64, error) {
|
||||
func (db *Database) Tail(group string) (uint64, error) {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
|
|
@ -99,7 +99,7 @@ func (db *Database) TruncateHead(n uint64) (uint64, error) {
|
|||
panic("not supported")
|
||||
}
|
||||
|
||||
func (db *Database) TruncateTail(n uint64) (uint64, error) {
|
||||
func (db *Database) TruncateTail(group string, n uint64) (uint64, error) {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -378,7 +378,7 @@ func (dl *diskLayer) writeHistory(typ historyType, diff *diffLayer) (bool, error
|
|||
if limit == 0 {
|
||||
return false, nil
|
||||
}
|
||||
tail, err := freezer.Tail()
|
||||
tail, err := freezer.Tail(rawdb.StateHistoryTailGroup)
|
||||
if err != nil {
|
||||
return false, err
|
||||
} // firstID = tail+1
|
||||
|
|
|
|||
|
|
@ -273,7 +273,7 @@ func truncateFromHead(store ethdb.AncientStore, typ historyType, nhead uint64) (
|
|||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
otail, err := store.Tail()
|
||||
otail, err := store.Tail(rawdb.StateHistoryTailGroup)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
@ -303,7 +303,13 @@ func truncateFromTail(store ethdb.AncientStore, typ historyType, ntail uint64) (
|
|||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
otail, err := store.Tail()
|
||||
var group string
|
||||
if typ == typeStateHistory {
|
||||
group = rawdb.StateHistoryTailGroup
|
||||
} else {
|
||||
group = rawdb.TrienodeHistoryTailGroup
|
||||
}
|
||||
otail, err := store.Tail(group)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
@ -315,7 +321,7 @@ func truncateFromTail(store ethdb.AncientStore, typ historyType, ntail uint64) (
|
|||
if otail == ntail {
|
||||
return 0, nil
|
||||
}
|
||||
otail, err = store.TruncateTail(ntail)
|
||||
otail, err = store.TruncateTail(group, ntail)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
@ -430,7 +436,7 @@ func repairHistory(db ethdb.Database, isUBT bool, readOnly bool, stateID uint64,
|
|||
truncTo = min(truncTo, thead)
|
||||
} else {
|
||||
if thead == 0 {
|
||||
_, err = trienodes.TruncateTail(stateID)
|
||||
_, err = trienodes.TruncateTail(rawdb.TrienodeHistoryTailGroup, stateID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -542,7 +542,7 @@ func (i *indexIniter) run(recover bool) {
|
|||
|
||||
// next returns the ID of the next state history to be indexed.
|
||||
func (i *indexIniter) next() (uint64, error) {
|
||||
tail, err := i.freezer.Tail()
|
||||
tail, err := i.freezer.Tail(rawdb.StateHistoryTailGroup)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
|
@ -37,7 +38,7 @@ type HistoryStats struct {
|
|||
// sanitizeRange limits the given range to fit within the local history store.
|
||||
func sanitizeRange(start, end uint64, freezer ethdb.AncientReader) (uint64, uint64, error) {
|
||||
// Load the id of the first history object in local store.
|
||||
tail, err := freezer.Tail()
|
||||
tail, err := freezer.Tail(rawdb.StateHistoryTailGroup)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
|
@ -132,7 +133,7 @@ func storageHistory(freezer ethdb.AncientReader, address common.Address, slot co
|
|||
// historyRange returns the block number range of local state histories.
|
||||
func historyRange(freezer ethdb.AncientReader) (uint64, uint64, error) {
|
||||
// Load the id of the first history object in local store.
|
||||
tail, err := freezer.Tail()
|
||||
tail, err := freezer.Tail(rawdb.StateHistoryTailGroup)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -470,7 +470,7 @@ func checkStateAvail(state stateIdent, exptyp historyType, freezer ethdb.Ancient
|
|||
return 0, fmt.Errorf("unsupported history type: %d, want: %v", toHistoryType(state.typ), exptyp)
|
||||
}
|
||||
// firstID = tail+1
|
||||
tail, err := freezer.Tail()
|
||||
tail, err := freezer.Tail(rawdb.StateHistoryTailGroup)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ func TestTruncateOutOfRange(t *testing.T) {
|
|||
|
||||
// Ensure of-out-range truncations are rejected correctly.
|
||||
head, _ := freezer.Ancients()
|
||||
tail, _ := freezer.Tail()
|
||||
tail, _ := freezer.Tail(rawdb.StateHistoryTailGroup)
|
||||
|
||||
cases := []struct {
|
||||
mode int
|
||||
|
|
|
|||
Loading…
Reference in a new issue