perf(core): Batch and coalesce scope-persistence disk writes#5791
Draft
runningcode wants to merge 2 commits into
Draft
perf(core): Batch and coalesce scope-persistence disk writes#5791runningcode wants to merge 2 commits into
runningcode wants to merge 2 commits into
Conversation
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.
📲 Install BuildsAndroid
|
Contributor
Performance metrics 🚀
|
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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'dQueueFileappend (the file was opened inrwdmode).PersistingScopeObserver#store×310 = 376 ms —setContexts/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
LoggerBatchProcessorpattern:QueueFilegains an opt-in buffered-write mode (synchronousWrites(false)) plus async()method; only the breadcrumb queue opts in, the ANR-profile queues keep synchronous writes.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
enableScopePersistenceoption; the debounce interval is an internal constant.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