Skip to content

perf(core): Batch and coalesce scope-persistence disk writes#5791

Draft
runningcode wants to merge 2 commits into
mainfrom
no/java-628-batch-scope-persistence
Draft

perf(core): Batch and coalesce scope-persistence disk writes#5791
runningcode wants to merge 2 commits into
mainfrom
no/java-628-batch-scope-persistence

Conversation

@runningcode

Copy link
Copy Markdown
Contributor

Summary

Scope persistence wrote to disk on every scope mutation, which a customer cold-start trace (JAVA-628) showed to be the single largest SDK cost during startup — larger than init itself:

  • FileObjectQueue#add ×107 = 460 ms — every breadcrumb was a synchronous, fsync'd QueueFile append (the file was opened in rwd mode).
  • PersistingScopeObserver#store ×310 = 376 mssetContexts/setTrace/setUser/setTags/… each rewrote their whole file on every change, even though only the latest value matters.

What changed

Instead of writing eagerly on each mutation, mutations are coalesced and flushed together on a short debounce window (100 ms) on the Sentry executor, mirroring the existing LoggerBatchProcessor pattern:

  • Latest-wins per file — each scope field keeps only its most recent pending value, collapsing the many redundant rewrites into a handful.
  • Batched breadcrumbs — breadcrumbs are buffered and appended together behind a single fsync. QueueFile gains an opt-in buffered-write mode (synchronousWrites(false)) plus a sync() method; only the breadcrumb queue opts in, the ANR-profile queues keep synchronous writes.
  • Mutations no longer run inline on the caller/main thread.

Tradeoff

Persistence exists to enrich crash/ANR events on the next launch and was already asynchronous, so nothing changes about the "data is only needed if the process dies" contract — the debounce just widens the potential loss window to ~100 ms of the most recent mutations before a crash. On-disk format is unchanged; restore is unaffected.

Notes

  • Reuses the existing enableScopePersistence option; the debounce interval is an internal constant.
  • The customer trace also flagged a 24 ms main-thread addBreadcrumb — the code path is (and remains) async; that number still needs to be verified against the trace and is intentionally not addressed here.

🤖 Generated with Claude Code

Scope persistence wrote to disk on every scope mutation, which dominated
SDK cost during startup: each breadcrumb triggered a synchronous fsync'd
QueueFile append, and every other scope field (contexts, trace, user,
tags, ...) rewrote its whole file on each change even though only the
latest value matters.

Coalesce mutations instead of writing eagerly. Each field keeps only its
latest pending value and is flushed once per debounce window; breadcrumbs
are buffered and appended together behind a single fsync (QueueFile gains
an opt-in buffered-write mode plus sync()). This trades a small data-loss
window (~100ms before the process dies) for far fewer writes and fsyncs.

Persistence exists to enrich crash/ANR events on the next launch, and was
already asynchronous, so the widened loss window is acceptable.
@linear-code

linear-code Bot commented Jul 20, 2026

Copy link
Copy Markdown

JAVA-628

@sentry

sentry Bot commented Jul 20, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

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

⚙️ sentry-android Build Distribution Settings

@github-actions

Copy link
Copy Markdown
Contributor

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 334.74 ms 380.29 ms 45.55 ms
Size 0 B 0 B 0 B

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
5b1a06b 310.56 ms 362.79 ms 52.22 ms
ad8da22 339.92 ms 407.37 ms 67.45 ms
d15471f 307.28 ms 381.85 ms 74.57 ms
2195398 345.88 ms 411.71 ms 65.82 ms
ee747ae 396.82 ms 441.67 ms 44.86 ms
abfcc92 304.04 ms 370.33 ms 66.29 ms
22f4345 312.78 ms 347.40 ms 34.62 ms
d15471f 304.55 ms 408.43 ms 103.87 ms
ad8da22 362.98 ms 453.94 ms 90.96 ms
bbc35bb 298.53 ms 372.17 ms 73.64 ms

App size

Revision Plain With Sentry Diff
5b1a06b 0 B 0 B 0 B
ad8da22 1.58 MiB 2.29 MiB 719.83 KiB
d15471f 1.58 MiB 2.13 MiB 559.54 KiB
2195398 0 B 0 B 0 B
ee747ae 1.58 MiB 2.10 MiB 530.95 KiB
abfcc92 1.58 MiB 2.13 MiB 557.31 KiB
22f4345 1.58 MiB 2.29 MiB 719.83 KiB
d15471f 1.58 MiB 2.13 MiB 559.54 KiB
ad8da22 1.58 MiB 2.29 MiB 719.83 KiB
bbc35bb 1.58 MiB 2.12 MiB 553.01 KiB

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.

1 participant