Skip to content

perf(replay): Use hash set for mask view class lookups - #6410

Merged
romtsn merged 3 commits into
mainfrom
perf/replay-mask-class-lookup
Oct 29, 2026
Merged

romtsn merged 3 commits into
mainfrom
perf/replay-mask-class-lookup

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

📜 Description

SentryMaskingOptions.maskViewClasses and unmaskViewClasses were backed by CopyOnWriteArraySet, and its contains() scans the whole list. This PR backs both with Collections.newSetFromMap(new ConcurrentHashMap<>()) instead:

  • contains, add, and remove are now O(1).
  • It's still thread-safe, and iterating over it never throws a ConcurrentModificationException.
  • It works on minSdk 21. I avoided ConcurrentHashMap.newKeySet() because it needs API 24.
  • The public API is unchanged: the field and getter types are still Set<String>, and checkKotlinAbi passes.

Behavior change: the sets no longer keep insertion order. The only place that order showed up is the maskedViewClasses and unmaskedViewClasses lists in the replay options event. Those lists are informational, so SessionCaptureStrategyTest now checks their contents without checking order.

💡 Motivation and Context

During each replay capture, ViewHierarchyNode.shouldMask walks every view's superclass chain on the main thread (Class.isAssignableFrom(Set<String>)). At each level it calls contains() on both sets, and most views match neither. With a linear contains(), the cost of each capture grew with views × class depth × (mask + unmask list size). This came up for a customer considering mask lists with thousands of entries. With constant-time lookups, list size no longer affects capture cost. Compose nodes (ComposeViewHierarchyNode) get the same improvement.

💚 How did you test it?

I ran these locally on JDK 21:

  • :sentry:test: SentryMaskingOptionsTest, SentryReplayOptionsTest, RRWebOptionsEventSerializationTest
  • :sentry-android-replay:testReleaseUnitTest: SessionCaptureStrategyTest, viewhierarchy.* (masking, container, and Compose masking tests)
  • :sentry-android-core:testReleaseUnitTest: SentryScreenshotOptionsTest, ScreenshotEventProcessorTest, ManifestMetadataReaderTest
  • :sentry:spotlessApply, :sentry-android-replay:spotlessApply (no changes), :sentry:checkKotlinAbi

I haven't benchmarked this on a device.

📝 Checklist

  • I added GH Issue ID & Linear ID
  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.

via roman.

--

View Junior Session [Sentry]

maskViewClasses and unmaskViewClasses were backed by a
CopyOnWriteArraySet, whose contains() is a linear scan. The view
hierarchy traversal checks both sets for every superclass of every
view on the main thread, so capture cost grew with the size of the
configured class lists.

Back both sets with a ConcurrentHashMap key set for O(1) lookups
while keeping thread-safe, non-throwing iteration.

Co-Authored-By: Roman Zavarnitsyn <roman.zavarnitsyn@sentry.io>
@sentry-junior
sentry-junior Bot requested review from a team and adinauer as code owners October 28, 2026 20:53
@sentry-junior
sentry-junior Bot requested review from 0xadam-brown and runningcode and removed request for a team October 28, 2026 20:53
Co-Authored-By: Roman Zavarnitsyn <roman.zavarnitsyn@sentry.io>
@sentry

sentry Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
SDK Size io.sentry.tests.size 8.62.0 (1) release

⚙️ sentry-android Build Distribution Settings

@romtsn romtsn added the ship-it PR is ready to merge from a reviewer perspective label Oct 28, 2026
ConcurrentHashMap-backed sets reject null keys, while the previous
CopyOnWriteArraySet accepted them. Hybrid SDKs (e.g. React Native via
ReadableArray.getString) can pass null despite the @NotNull annotation,
which would now throw during SDK init.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@romtsn
romtsn merged commit 21a827f into main Oct 29, 2026
43 of 45 checks passed
@romtsn
romtsn deleted the perf/replay-mask-class-lookup branch October 29, 2026 09:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ship-it PR is ready to merge from a reviewer perspective

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants