Research

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

Sei Labs cover reading Scaling Sei Archival State to 200,000 TPS, with a fan of fine coloured lines on black.

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 270 searches backward and finds the answer (30) at block 200.

  • With undo logs: Querying height 270 searches forward and finds the answer (30) at block 350. 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 0xbeef is updated at height 300, the existing index ([100, 200]) must be read before appending 300 and 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.