Building Giga: Scaling Sei Archival State to 200,000 TPS

Sei Giga has a roadmap target of 200,000 transactions per second. This throughput presents a challenge at every layer of the stack, from consensus to execution to storage. To meet these strict requirements, Sei has decided to split the archival state into two tiers: short-term, which stores hours or days of state, and long-term, which stores the state from genesis. This post covers the design of the short-term archival layer, which stores the account state.
Problem statement
The archival state allows the node to return the state of any account at any point in time. This is required by RPC endpoints such as eth_getStorageAt, eth_getBalance, and others that allow the replay of a transaction with a preloaded state.
Conceptually, historical information can be stored as follows. Let 0xbeef be an account whose balance changes over time at the following block heights:
account | block | value |
|---|---|---|
0xbeef | 0 | 0 |
0xbeef | 200 | 30 |
0xbeef | 350 | 44 |
To find the state of 0xbeef at block height 270, a lookup scans backward to the first modification, in this case at block 200. Thus, the state of 0xbeef at block 270 is 30.

Figure 1. Storing the new value: a lookup scans backward to the last change, and pruning has to decide key by key what to keep.
However, at Giga throughput, this produces several TB per day. This means that Sei can produce in just one day the equivalent state that Ethereum has produced in 10 years. No off-the-shelf key-value stores scale to these requirements.
That is why Sei has decided to split the archival storage into two tiers:
Short-term: A small window of hours or days behind the tip.
Long-term: Every single block, transaction, receipt, log, and state change since genesis.
This post covers the fundamentals of the short-term archival state design, reviewing the state of the art, key design decisions, and tradeoffs to meet Sei’s requirements:
Massive insertion throughput: Sustaining hundreds of thousands of inserts per second to keep up with the chain.
Efficient reads: Key-value lookups in single-digit milliseconds and thousands of reads per second.
Efficient pruning: Pruning state without degrading insertion or read throughput.
Undo logs
Multiple Ethereum clients, such as Geth and Reth, store historical state changes as undo logs. This means that instead of recording that 0xbeef was updated to 30 at block height 200, they record the previous value that was present.
In other words, instead of storing this:
account | block | value |
|---|---|---|
0xbeef | 0 | 0 |
0xbeef | 200 | 30 |
0xbeef | 350 | 44 |
An undo log stores the following structure. As shown, undo logs save space in some scenarios because they do not need to record the newest value explicitly:
account | block | prev_value |
|---|---|---|
0xbeef | 200 | 0 |
0xbeef | 350 | 30 |
However, an undo log relies on the live state to answer queries beyond the latest stored block height (which is already stored by every node):
account | live_state |
|---|---|
0xbeef | 44 |
Because the new value set at height 350 (44) is not stored inside the undo log, querying 0xbeef's balance at block 400 requires consulting the live state. Scanning proceeds forward instead of backward:
Without undo logs: Querying height
270searches backward and finds the answer (30) at block200.With undo logs: Querying height
270searches forward and finds the answer (30) at block350. If no undo entry is found forward, it defaults to the live state.
In database literature, undo logs are primarily used to revert transactions. For instance, to revert changes made at block 350, the database reads the undo log record to retrieve and restore the previous value.

Figure 2. An undo log stores the previous value: a lookup scans forward to the next change, falls back to the live state, and pruning is a single cutoff.
While reverting transactions is useful, what makes undo logs particularly compelling for Sei is efficient pruning. Pruning here means removing historical state older than a designated retention window (e.g., keeping only the last few hours of history on an RPC node).
Because lookups with undo logs scan forward rather than backward, the pruning criteria become straightforward: any entry below the cutoff block height can be safely deleted.
By contrast, the naive approach without undo logs cannot easily do this. Using the earlier example, pruning state prior to block 250 cannot simply delete the block 200 entry, because backward lookups for block 270 still require it:
account | block | value | note |
|---|---|---|---|
0xbeef | 0 | 0 | |
0xbeef | 200 | 30 | can't prune this |
250 | prune cutoff | ||
0xbeef | 350 | 44 |
Consequently, pruning without undo logs requires iterating through historical keys to determine which old entries must be retained. For billions of keys, this overhead does not scale.
Indexing state changes
There are different ways to index undo logs. Clients like Reth and Geth divide historical state into:
Append-only files containing per-block undo logs ordered by key.
A key-history index tracking the blocks in which each key changed, ordered by block height.
A simplified representation looks like this:
Block 100:
0xbeef: 10
0xcafe: 35
Block 200:
0xbeef : 44
History index:
0xbeef: [100, 200]
0xcafe: [100]This approach works well for Ethereum and chains with lower throughput because it offers fast reads. However, it fails to scale for Sei due to two main bottlenecks:
Two-step pruning: Deleting append-only files is straightforward, but updating the history indexes requires iterating across keys and rewriting index structures.
Read-modify-write index updates: Maintaining the history index incurs read overhead. If
0xbeefis updated at height300, the existing index ([100, 200]) must be read before appending300and writing the update back.
To satisfy Sei’s performance targets, pruning cannot involve key iteration (which degrades performance for minutes or hours), and the write path cannot depend on reads. Sei addresses both issues using a bucketed undo log design.
Sei’s bucketed undo log
Sei eliminates the history index and instead organizes undo logs into buckets. A bucket represents a range of contiguous blocks, with entries sorted internally by key and block height.
For example, with a bucket size of 100 blocks, Bucket 0 covers [0, 100), Bucket 1 covers [100, 200), and so on. Entries are ordered primarily by bucket, account, and block:
bucket | account | block | prev_value |
|---|---|---|---|
0 | 0xbeef | 10 | 3 |
0 | 0xbeef | 30 | 19 |
0 | 0xbeef | 55 | 3 |
0 | 0xbeef | 99 | 19 |
0 | 0xcafe | 10 | 33 |
0 | 0xcafe | 58 | 33 |
1 | 0xbeef | 120 | 50 |
1 | 0xcafe | 130 | 11 |
1 | 0xcafe | 140 | 14 |
This design maximizes insertion throughput by removing read-modify-write operations from the write path, supporting Sei's massive transaction volume.
It also streamlines pruning: entire buckets can be dropped at the storage layer without iterating keys or cleaning custom indexes. Most LSM-based key-value stores support range deletions at the SSTable level without writing tombstones, completing in milliseconds to seconds without temporary storage amplification or compaction overhead. The bucket size defines the pruning granularity (e.g., a bucket size of 100 allows pruning in increments of 100 blocks).

Figure 3. Sei’s bucketed undo log: rows sort by bucket, then key, then block. Dropping bucket 0 is one range delete, and a Bloom filter skips bucket 2 without a disk read. Bucket 2 and the live values are illustrative, added to show a Bloom filter skip.
This design is specifically tailored for short-term archival state. As the number of buckets grows, worst-case lookup times could increase. Bloom filters are used to quickly skip buckets in memory when a key is absent, keeping the active bucket count low.
Conclusion
Achieving 200,000 transactions per second presents significant challenges for archival state management. Sei resolves this by splitting storage into short-term and long-term tiers. The short-term tier stores recent state on single nodes using a bucketed undo log approach optimized for high write throughput and low-overhead pruning.
By avoiding custom secondary indexes that require read-modify-write operations, the write path remains unbottlenecked. Meanwhile, organizing historical changes into block-range buckets enables rapid pruning of old data without impacting system performance.
An upcoming post will cover the long-term storage solution for retaining all historical blocks, transactions, receipts, logs, and state from genesis.