Skip to content

[blocklist]: replace O(N²) slice scans in updateInternal with map lookups - #7140

Merged
zalegrala merged 3 commits into
grafana:mainfrom
zalegrala:zl/blocklist-updateinternal-o-n2-fix
Jun 2, 2026
Merged

[blocklist]: replace O(N²) slice scans in updateInternal with map lookups#7140
zalegrala merged 3 commits into
grafana:mainfrom
zalegrala:zl/blocklist-updateinternal-o-n2-fix

Conversation

@zalegrala

@zalegrala zalegrala commented May 11, 2026

Copy link
Copy Markdown
Contributor

What this PR does:

Optimizes updateInternal (tempodb/blocklist/list.go). The add loop called slices.ContainsFunc(final, ...) on every incoming block — scanning up to 100K existing entries per block added. With ~1K new blocks per compaction cycle, that's 100M UUID comparisons per tenant per poll.

All four slices.ContainsFunc scans (remove, compactedAdd, compactedRemove, and the final dedup check) are replaced with map[backend.UUID]struct{} lookups built once before the loops, reducing the add phase from O(N·M) to O(N+M).

Benchmark (BenchmarkUpdateInternalLargeAdd — 100K existing blocks, 1K new adds):

# Before
BenchmarkUpdateInternalLargeAdd-16    51    231565779 ns/op    811008 B/op    1 allocs/op
BenchmarkUpdateInternalLargeAdd-16    51    241285117 ns/op    811117 B/op    1 allocs/op
BenchmarkUpdateInternalLargeAdd-16    50    238569548 ns/op    811008 B/op    1 allocs/op

# After
BenchmarkUpdateInternalLargeAdd-16    2493    4238611 ns/op    3175962 B/op    258 allocs/op
BenchmarkUpdateInternalLargeAdd-16    2352    4509954 ns/op    3176071 B/op    258 allocs/op
BenchmarkUpdateInternalLargeAdd-16    2415    4553389 ns/op    3175943 B/op    258 allocs/op

~53× faster per call. The alloc count increase reflects map bucket overhead for the 100K-capacity finalIDs map; memory stays under 4MB per call.

Checklist

  • Tests updated
  • Documentation added
  • CHANGELOG.md updated - the order of entries should be [CHANGE], [FEATURE], [ENHANCEMENT], [BUGFIX]
@zalegrala
zalegrala force-pushed the zl/blocklist-updateinternal-o-n2-fix branch 2 times, most recently from d076b9e to 7648e1a Compare May 14, 2026 20:06
@zalegrala
zalegrala marked this pull request as ready for review May 14, 2026 21:45
Copilot AI review requested due to automatic review settings May 14, 2026 21:45

Copilot AI left a comment

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.

Pull request overview

Replaces four slices.ContainsFunc linear scans in List.updateInternal with pre-built map[backend.UUID]struct{} lookups, turning the regular-blocks add phase from O(N·M) to O(N+M). The benchmark (BenchmarkUpdateInternalLargeAdd) demonstrates ~53× speedup on a 100K-existing + 1K-add workload. Logic for the compacted-blocks branch is similarly converted, preserving the original semantics (compacted-add still dedupes against the pre-existing set only, matching prior behavior).

Changes:

  • Build removeIDs, compactedAddIDs, compactedRemoveIDs, and finalIDs maps once and use map membership checks in both the regular and compacted update loops.
  • Drop the slices import and the hasID / hasIDC closure helpers.
  • Add BenchmarkUpdateInternalLargeAdd to guard against future regressions.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
tempodb/blocklist/list.go Replace O(N²) slice scans in updateInternal with map-based lookups; logic remains equivalent to the prior implementation.
tempodb/blocklist/list_test.go New benchmark exercising the large-add path (100K existing + 1K adds) to demonstrate and protect the perf improvement.
Comment thread tempodb/blocklist/list.go
@zalegrala
zalegrala force-pushed the zl/blocklist-updateinternal-o-n2-fix branch from 7648e1a to 46346d4 Compare May 14, 2026 21:57
Copilot AI review requested due to automatic review settings May 14, 2026 21:57
@zalegrala zalegrala changed the title [blocklist]: fix O(N²) slice scans in updateInternal May 14, 2026

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread CHANGELOG.md
electron0zero
electron0zero previously approved these changes May 15, 2026

@electron0zero electron0zero left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lgtm :shipit:

zalegrala added 3 commits June 2, 2026 18:23
Replace slices.ContainsFunc calls with map-based O(1) lookups throughout
updateInternal. The add loop previously scanned the full `final` slice on
every iteration — 1K new blocks × 100K existing = 100M comparisons per
tenant per poll cycle. All four slice scans (remove, compactedAdd,
compactedRemove, and final-dedup) are now O(1) map lookups built once
before the loops.

BenchmarkUpdateInternalLargeAdd (100K existing, 1K adds):
  before: 236,473,481 ns/op
  after:    4,433,651 ns/op (~53× faster)
@zalegrala
zalegrala force-pushed the zl/blocklist-updateinternal-o-n2-fix branch from 9ab0f77 to 40cf814 Compare June 2, 2026 18:24
@zalegrala
zalegrala requested a review from zhxiaogg as a code owner June 2, 2026 18:24
@zalegrala
zalegrala merged commit 1d09e0e into grafana:main Jun 2, 2026
39 of 53 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants