-
Notifications
You must be signed in to change notification settings - Fork 885
Implement new Giga GarbageCollector interface #3868
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
e71f50b
6935ce7
42a667d
c9f3de9
2a10f31
f43b8b3
47a6dfd
45b80e0
29e7d7d
7ccdf29
9b96d16
c07fa75
a682ec3
9c0e746
5104f64
c15c7f1
6ee2f11
12797bc
a0de18c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,10 +5,11 @@ import ( | |
| "time" | ||
|
|
||
| littdb "github.com/sei-protocol/sei-chain/sei-db/db_engine/litt" | ||
| "github.com/sei-protocol/sei-chain/sei-db/management/gc" | ||
| ) | ||
|
|
||
| // LittBlockConfig configures a LittDB-backed types.BlockDB. | ||
| type LittBlockConfig struct { | ||
| // BlockDBConfig configures a LittDB-backed types.BlockDB. | ||
| type BlockDBConfig struct { | ||
| // Litt is the underlying LittDB configuration, including the data directory | ||
| // paths. The block store builds its two tables (blocks, qcs) on top of this | ||
| // DB. Required; use DefaultConfig to obtain one with sane defaults, then | ||
|
|
@@ -20,23 +21,45 @@ type LittBlockConfig struct { | |
| // watermark to advance past the record, so even an over-eager watermark | ||
| // cannot delete data younger than Retention. Must be positive. | ||
| Retention time.Duration | ||
|
|
||
| // RetentionWindow is how much history this store keeps beyond the shared rollback | ||
| // window of the StorageGarbageCollector that manages it, in blocks. It is what | ||
| // gc.PrunableStore.GetRetentionWindow answers: | ||
| // | ||
| // > 0 → that many blocks of history beyond the rollback window | ||
| // 0 → keep history to serve rollback window only | ||
| // -1 → never prune this store (gc.InfiniteRetentionWindow) | ||
| // | ||
| // Zero does NOT mean "keep everything" here, unlike the KeepRecent fields on | ||
| // StateStoreConfig and ReceiptStoreConfig, where 0 disables pruning. It is the most | ||
| // aggressive setting this field has; "keep everything" is -1. Assigning a KeepRecent | ||
| // value to this field inverts the retention it asks for. | ||
| // | ||
| // This is an input to a minimum shared across every managed store, not a policy applied | ||
| // to this store alone: a deep window here also holds back receiptDB and the SC/SS | ||
| // snapshots. Must be >= gc.InfiniteRetentionWindow. | ||
| // | ||
| // Independent of Retention, which is a wall-clock TTL failsafe underneath the watermark. | ||
| // Both must permit reclamation before any record is dropped. | ||
| RetentionWindow int64 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be useful to call out the following invariants:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest we paste this block of invariants at each RetentionWindow or RollbackWindow config (with wording adjusted a little, the above is specific to block storage). |
||
| } | ||
|
|
||
| // DefaultConfig returns a LittBlockConfig preloaded with all defaults, rooted at | ||
| // DefaultConfig returns a BlockDBConfig preloaded with all defaults, rooted at | ||
| // dir. Override fields as needed, then pass it to NewBlockDB (which validates). | ||
| func DefaultConfig(dir string) (*LittBlockConfig, error) { | ||
| func DefaultConfig(dir string) (*BlockDBConfig, error) { | ||
| littConfig, err := littdb.DefaultConfig(dir) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to build litt config: %w", err) | ||
| } | ||
| return &LittBlockConfig{ | ||
| Litt: littConfig, | ||
| Retention: 24 * time.Hour, | ||
| return &BlockDBConfig{ | ||
| Litt: littConfig, | ||
| Retention: 24 * time.Hour, | ||
| RetentionWindow: 10000, | ||
|
yzang2019 marked this conversation as resolved.
Outdated
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] This default is a fleet-wide policy in a per-store field. As the |
||
| }, nil | ||
| } | ||
|
|
||
| // Validate performs a sanity check on the configuration. | ||
| func (c *LittBlockConfig) Validate() error { | ||
| func (c *BlockDBConfig) Validate() error { | ||
| if c == nil { | ||
| return fmt.Errorf("config is required") | ||
| } | ||
|
|
@@ -46,5 +69,9 @@ func (c *LittBlockConfig) Validate() error { | |
| if c.Retention <= 0 { | ||
| return fmt.Errorf("config.Retention must be positive (got %s)", c.Retention) | ||
| } | ||
| if c.RetentionWindow < gc.InfiniteRetentionWindow { | ||
| return fmt.Errorf("config.RetentionWindow must be >= %d (got %d)", | ||
| gc.InfiniteRetentionWindow, c.RetentionWindow) | ||
| } | ||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,18 +12,22 @@ import ( | |
| "github.com/sei-protocol/sei-chain/sei-tendermint/libs/utils" | ||
| ) | ||
|
|
||
| // ledgerTableName is the single table holding both blocks and QCs. They share | ||
| // one table so a crash leaves a contiguous write-order prefix spanning both | ||
| // record kinds (see NewBlockDB), which is what guarantees a persisted block is | ||
| // always covered by a persisted QC. | ||
| const ledgerTableName = "ledger" | ||
| // tableName is the single table holding blocks and QCs both, despite the name. They share one | ||
| // table so a crash leaves a contiguous write-order prefix spanning both record kinds (see | ||
| // NewBlockDB), which is what guarantees a persisted block is always covered by a persisted QC. | ||
| // | ||
| // This value is persisted layout, not just an identifier: littdb puts a table's data at | ||
| // <root>/<tableName>/segments, so changing it makes NewBlockDB open a fresh empty table while the | ||
| // old data sits untouched under the previous name — neither served nor reclaimed. | ||
| const tableName = "blocks" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] The comment you added here states the hazard precisely — a rename makes Since the check is cheap and the comment already argues for it:
yzang2019 marked this conversation as resolved.
|
||
|
|
||
| var _ types.BlockDB = (*blockDB)(nil) | ||
|
|
||
| // blockDB is a durable types.BlockDB backed by LittDB | ||
| type blockDB struct { | ||
| db littdb.DB | ||
| table littdb.Table | ||
| db littdb.DB | ||
| table littdb.Table | ||
| config *BlockDBConfig | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] This field is assigned once ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [suggestion] Struct fields aren't caught by the |
||
|
|
||
| // watermark is a retention floor, always a QC boundary (a GlobalRange().First): | ||
| // PruneBefore rounds a requested prune point down to the start of the cohort | ||
|
|
@@ -77,7 +81,7 @@ type blockDB struct { | |
| // underlying LittDB is built from config.Litt, and the two tables apply | ||
| // config.Retention as a TTL failsafe (pruning never reclaims data younger than | ||
| // that even once the watermark has advanced past it). | ||
| func NewBlockDB(config *LittBlockConfig) (types.BlockDB, error) { | ||
| func NewBlockDB(config *BlockDBConfig) (types.BlockDB, error) { | ||
| if err := config.Validate(); err != nil { | ||
| return nil, fmt.Errorf("invalid block db config: %w", err) | ||
| } | ||
|
|
@@ -96,7 +100,7 @@ func NewBlockDB(config *LittBlockConfig) (types.BlockDB, error) { | |
| // guarantees a persisted block is always covered by a persisted QC. It also | ||
| // backs the write-order cursors and contiguous-QC recovery. ShardingFactor | ||
| // > 1, or splitting blocks and QCs across two tables, would void this. | ||
| tableConfig := littdb.DefaultTableConfig(ledgerTableName) | ||
| tableConfig := littdb.DefaultTableConfig(tableName) | ||
| tableConfig.TTL = config.Retention | ||
| tableConfig.GCFilter = s.gcFilter | ||
| tableConfig.ShardingFactor = 1 // DO NOT CHANGE!! | ||
|
|
@@ -107,6 +111,7 @@ func NewBlockDB(config *LittBlockConfig) (types.BlockDB, error) { | |
| } | ||
|
|
||
| s.table = table | ||
| s.config = config | ||
|
|
||
| if err := s.recoverCursors(); err != nil { | ||
| _ = db.Close() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| package littblock | ||
|
|
||
| import ( | ||
| "github.com/sei-protocol/sei-chain/sei-db/management/gc" | ||
| "github.com/sei-protocol/sei-chain/sei-tendermint/autobahn/types" | ||
| ) | ||
|
|
||
| // blockDB joins the shared prune cycle as a contiguous store: everything from its retention | ||
| // floor up to its head is retained, so it can serve a rollback to any height in between. The | ||
| // collector owns the decision of how deep to prune; PruneBefore stays the direct entry point | ||
| // for callers holding a types.BlockDB. | ||
| var _ gc.PrunableStore = (*blockDB)(nil) | ||
|
|
||
| func (s *blockDB) Name() string { | ||
| return "BlockDB" | ||
| } | ||
|
|
||
| // ExternalPruning is unconditionally true: this store has no pruner of its own for the collector to | ||
| // collide with. LittDB's GC reclaims what PruneBefore has already released, on a config.Retention | ||
| // timer, so it enforces no retention policy — it only carries out one this store has recorded. | ||
| func (s *blockDB) ExternalPruning() bool { | ||
| return true | ||
| } | ||
|
|
||
| // PruneBelow advances the retention watermark to blockNumber. It only records the watermark; | ||
| // reclamation happens on LittDB's own GC schedule and no earlier than config.Retention (see | ||
| // PruneBefore). | ||
| // | ||
| // blockNumber is a minimum shared across every managed store, so it may sit above this store's | ||
| // own head — a store that ingests ahead of blockDB pulls the head up, and a QC boundary can | ||
| // leave the newest retained cohort below it. PruneBefore caps the request at the newest | ||
| // retained (block, QC) pair, which is what keeps the store from emptying itself here. | ||
| func (s *blockDB) PruneBelow(blockNumber uint64) error { | ||
| return s.PruneBefore(types.GlobalBlockNumber(blockNumber)) | ||
| } | ||
|
|
||
| // GetRetentionWindow reports the configured history beyond the collector's shared rollback | ||
| // window. See BlockDBConfig.RetentionWindow for the meaning of each value, and note that it is | ||
| // an input to a fleet-wide minimum rather than a policy applied to this store alone. | ||
| func (s *blockDB) GetRetentionWindow() int64 { | ||
| if s.config.RetentionWindow < 0 { | ||
| return gc.InfiniteRetentionWindow | ||
| } | ||
| return s.config.RetentionWindow | ||
| } | ||
|
|
||
| // GetPruningBoundary returns cutLine, the contract's answer for a contiguous store: every block | ||
| // at or above the watermark is retained, so cutLine itself is restorable and nothing below it | ||
| // has to be held back. | ||
| // | ||
| // Unconditional on purpose. A store whose floor already sits above cutLine — bootstrapped | ||
| // mid-chain, or pruned there by an earlier cycle — still answers cutLine, because the | ||
| // PruneBefore that follows is a no-op on it while a lower answer would hold back every other | ||
| // store. CannotServeRollback is never right here: this store fills from its own ingest path, | ||
| // so it has no replay range for another store's data to protect. | ||
| func (s *blockDB) GetPruningBoundary(cutLine uint64) uint64 { | ||
| return cutLine | ||
| } | ||
|
|
||
| // GetLatestBlock returns the newest block number written, or 0 when none has been. | ||
| // | ||
| // Global block numbers start at genesis block 0, so a store holding only that block is | ||
| // indistinguishable from an empty one and is excluded from the collector's head. That is the | ||
| // safe direction: it drops out of the head minimum rather than dragging every store's cut line | ||
| // to 0, and the prune it then receives is capped by PruneBefore to a no-op. | ||
| // | ||
| // Reports the written cursor, not the flushed one. A block that a crash would lose still counts | ||
| // as ingested — recovery re-derives this cursor from what survived, so the head can only move | ||
| // back, never past a prune that was already issued. | ||
| func (s *blockDB) GetLatestBlock() (uint64, error) { | ||
| s.mu.Lock() | ||
| defer s.mu.Unlock() | ||
| if !s.hasBlocks { | ||
| return 0, nil | ||
| } | ||
| return uint64(s.lastBlockNumber), nil | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[suggestion] This comment states as fact that
ExternalPruningis taggedmapstructure:"-", butreceipt_config.go:74tags itmapstructure:"external-pruning". Pertestutil/configtest/AGENTS.mdthese manifest exclusions are the recorded contract a replacement implementation reads, so a comment that misdescribes the tag is the specific drift the suite is meant to prevent. Fix the tag (preferred) or the comment.