Skip to content

triedb: truncate history states in a batch way #32069

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

jsvisa
Copy link
Contributor

@jsvisa jsvisa commented Jun 20, 2025

closes #31829

I'm running the Hoodie testnet, and from some metrics of the rewinding procedure, I see that 14ms out of 20ms was used in the truncateFromHead operation.
Therefore, I propose running the truncateFromHead operation periodically, instead of executing it for each block.

@jsvisa jsvisa requested a review from rjl493456442 as a code owner June 20, 2025 11:59
@MariusVanDerWijden
Copy link
Member

Are we saving something when we execute truncatehead fewer times? If it takes 10ms per block, if we now only do it every 10 blocks would it take 100ms when we do it?

@jsvisa
Copy link
Contributor Author

jsvisa commented Jun 23, 2025

If it takes 10ms per block, if we now only do it every 10 blocks would it take 100ms when we do it?

We only need ~10ms for a 10 blocks to truncate it. The most time was used in the freezer's truncate table, which was dominated by:

  • file system operations(eg: file truncate and sync)
  • metadata update

These operations have similar overhead whether you're truncating 1 item or 100 items.

here is a simple test(not sure should be commited into this PR):

func TestFreezerTruncateHeadPerformance(t *testing.T) {
	t.Parallel()

	// Create a freezer with test data
	tables := map[string]freezerTableConfig{"test": {noSnappy: true}}
	f, _ := newFreezerForTesting(t, tables)
	defer f.Close()

	// Fill the freezer with 1000 items of 1KB each
	const numItems = 1000
	const itemSize = 1024

	item := make([]byte, itemSize)
	for i := range item {
		item[i] = byte(i % 256)
	}

	// Write test data
	_, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
		for i := uint64(0); i < numItems; i++ {
			if err := op.AppendRaw("test", i, item); err != nil {
				return err
			}
		}
		return nil
	})
	if err != nil {
		t.Fatal("Failed to write test data:", err)
	}

	// Verify initial state
	checkAncientCount(t, f, "test", numItems)

	for _, tt := range []uint64{1, 10, 100, 500} {
		t.Run(fmt.Sprintf("Truncate %d items", tt), func(t *testing.T) {
			// Ensure we start with full data for each test
			if _, err := f.Ancients(); err != nil {
				t.Fatal("Failed to get ancient count:", err)
			}

			start := time.Now()
			prevHead, err := f.TruncateHead(numItems - tt)
			duration := time.Since(start)

			if err != nil {
				t.Fatalf("TruncateHead(%d items) failed: %v", tt, err)
			}
			if prevHead != numItems {
				t.Fatalf("Expected previous head to be %d, got %d", numItems, prevHead)
			}

			// Verify the truncation worked
			checkAncientCount(t, f, "test", numItems-tt)

			t.Logf("Truncated %d items in %v (%.2f µs/item)", tt, duration, float64(duration.Microseconds())/float64(tt))

			// Restore the data for the next test
			_, err = f.ModifyAncients(func(op ethdb.AncientWriteOp) error {
				for i := uint64(numItems - tt); i < numItems; i++ {
					if err := op.AppendRaw("test", i, item); err != nil {
						return err
					}
				}
				return nil
			})
			if err != nil {
				t.Fatal("Failed to restore test data:", err)
			}
			checkAncientCount(t, f, "test", numItems)
		})
	}
}

result as below:

$ go test ./core/rawdb/... -v -run TestFreezerTruncateHeadPerformance
...
=== RUN   TestFreezerTruncateHeadPerformance/Truncate_1_items
    freezer_test.go:476: Truncated 1 items in 7.694166ms (7694.00 µs/item)
=== RUN   TestFreezerTruncateHeadPerformance/Truncate_10_items
    freezer_test.go:476: Truncated 10 items in 12.905042ms (1290.50 µs/item)
=== RUN   TestFreezerTruncateHeadPerformance/Truncate_100_items
    freezer_test.go:476: Truncated 100 items in 17.731833ms (177.31 µs/item)
=== RUN   TestFreezerTruncateHeadPerformance/Truncate_500_items
    freezer_test.go:476: Truncated 500 items in 26.861417ms (53.72 µs/item)
...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Optimize state rollback performance
2 participants