Skip to content

perf(storer): filter excluded batches and rogue chunks early in reserve sample - #5634

Open
gacevicljubisa wants to merge 7 commits into
masterfrom
perf/sample-early-batch-filter
Open

gacevicljubisa wants to merge 7 commits into
masterfrom
perf/sample-early-batch-filter

Conversation

@gacevicljubisa

@gacevicljubisa gacevicljubisa commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

Checklist

  • I have read the coding guide.
  • My change requires a documentation update, and I have done it.
  • I have added tests to cover my changes.
  • I have filled out the description and linked the related issues.

Description

Optimize batch filtering in ReserveSample by replacing map[string]struct{} with a stack-allocated [32]byte lookup, where string conversions and heap allocations are eliminated. Filtering is moved to Phase 1.

Benchmark Results

BenchmarkReserveSample10kWith1000ExcludedBatches (10k chunks in reserve, 1000 excluded batches):

Metric master This branch Delta
Time / op 3,294,111 ns/op (~3.29 ms) 1,412,442 ns/op (~1.41 ms) ~2.3x faster (-57%)
Allocs / op 67,081 allocs/op 66,079 allocs/op -1,002 allocs/op
Memory / op 4,299,470 B/op 4,318,118 B/op ~neutral

Open API Spec Version Changes (if applicable)

Motivation and Context (Optional)

Related Issue (Optional)

Screenshots (if appropriate):

AI Disclosure

  • This PR contains code that has been generated by an LLM.
  • I have reviewed the AI generated code thoroughly.
  • I possess the technical expertise to responsibly review the code generated in this PR.

Comment thread pkg/storer/sample.go Outdated
Comment thread pkg/storer/sample.go Outdated
isExcludedBatch, err := db.batchExclusionFilter(minBatchBalance)
if err != nil {
db.logger.Error(err, "get batches below value")
db.logger.Error(err, "get batch exclusion filter")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not introduced here (master also logs and continues, with a partial map), but since this is being touched: if this fails, the sample includes chunks from below-balance batches and the reveal will disagree with the neighborhood, which gets the node frozen. Returning the error would make the node skip the round instead:

if err != nil {
return Sample{}, fmt.Errorf("batch exclusion filter: %w", err)
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense. Great for noticing this.

Comment thread pkg/storer/sample.go
@gacevicljubisa
gacevicljubisa force-pushed the perf/sample-early-batch-filter branch from 2b1a4ee to 2eadeb8 Compare October 1, 2026 17:11
@gacevicljubisa
gacevicljubisa requested a review from acud October 3, 2026 15:11
Comment thread pkg/storer/sample.go
isExcludedBatch, err := db.batchExclusionFilter(minBatchBalance)
if err != nil {
db.logger.Error(err, "get batches below value")
return Sample{}, fmt.Errorf("batch exclusion filter: %w", err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This return happens before any g.Go, so the defer's g.Wait() is nil and recordReserveSampleMetrics labels the run success, while the caller still skips the round, so dashboard would look more optimistic, then it actually is.

Comment thread pkg/storer/sample.go Outdated

select {
case chunkC <- ch:
stats.TotalIterated++

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Broken Invariant (TotalIterated < BelowBalanceIgnored):
Semantically and historically on master, TotalIterated counts all candidate reserve chunks iterated within the committed depth. BelowBalanceIgnored and RogueChunk represent subsets of those iterated chunks that were dropped.
Because the author returns early before stats.TotalIterated++, filtered chunks are never counted in TotalIterated.
If a neighborhood has 100 chunks and 80 are from expired batches:
- On master: TotalIterated = 100, BelowBalanceIgnored = 80.
- On this branch: TotalIterated = 20, BelowBalanceIgnored = 80.
If all chunks in a neighborhood belong to expired batches: TotalIterated = 0, BelowBalanceIgnored = 100.

@gacevicljubisa
gacevicljubisa added this pull request to stack #5646 October 6, 2026 08:42
Comment thread pkg/storer/sample.go Outdated
defer func() {
duration := time.Since(t)
err := g.Wait()
_ = g.Wait()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error is recorded in the function below. Instead of removing it, maybe we can move it below the error return below and keep it as is.

@gacevicljubisa gacevicljubisa Oct 7, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the _ = g.Wait() error isn't lost. It's already handled at the end of the function.

	if err := g.Wait(); err != nil {
   	db.logger.Info("reserve sampler finished with error", "err", err, "duration", time.Since(t), "storage_radius", committedDepth, "consensus_time_ns", consensusTime, "stats", fmt.Sprintf("%+v", allStats))
   	return Sample{}, fmt.Errorf("sampler: failed creating sample: %w", err)
   }

But you are right, having it here in defer is confusing, and it is redundant, so _ = g.Wait() is removed from defer completly.

@acud
acud removed this pull request from stack #5646 October 8, 2026 18:17
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.

5 participants