What is sampling
The RUM SDK controls sampling through thesessionSampleRate parameter, a value from 0 to 100 representing the percentage of sessions to collect:
sessionReplaySampleRate is a second-stage sample applied on top of collected sessions: with sessionSampleRate: 20 and sessionReplaySampleRate: 10, sessions with replay recordings account for 2% of total traffic.
How sampling works
Understanding the following four rules resolves most “I configured the sampling rate but it doesn’t behave as expected” confusion.1. The unit is the session, not the user or the event
The sampling decision happens when a session starts: the SDK flips a coin once with probabilitysessionSampleRate. If the session wins the draw, it is reported in full; otherwise it stays completely silent. There is no such thing as “20% of events within a session get reported” — session data is either complete or absent.
The same user’s session may be sampled today and not sampled tomorrow. The default sampling mechanism is not anchored to specific users.
2. The decision is sticky within a session
The draw result is persisted with the session state (stored in a cookie on the web). A session lasts up to 4 hours while the user stays active, and expires after 15 minutes of inactivity; refreshing or navigating between pages does not trigger a new draw. Only when the session expires and a new one starts is the decision made again, using the sampling rate in effect at that time.This means that after you change the sampling rate, new sessions immediately follow the new rate, while existing sessions keep their original decision until they expire naturally. This is exactly the right semantics for gradual rollout, but it also means the change does not take full effect instantly.
3. It is a probability, not an exact quota
Each session’s draw is independent, with no global coordination. A 20% sampling rate is an expected value: the more traffic you have, the closer the actual collection ratio gets to 20% (law of large numbers); with low traffic, fluctuation is noticeable — collecting 13 or 28 out of 100 sessions is perfectly normal.4. The sampling rate is frozen at initialization
sessionSampleRate is fixed when init() is called. It cannot be changed at runtime, and init() cannot be called a second time within a page’s lifecycle. To change the sampling rate, the next initialization (on the web, the next page load) must receive the new value — the dynamic adjustment approaches below are built around this fact.
Choosing a sampling rate
Best practice 1: make the sampling rate dynamically adjustable
A sampling rate hardcoded in your source requires a release for every adjustment. Instead, externalize it to your own configuration service and read it during SDK initialization:- Web
- Android
- iOS
- Never block initialization waiting for configuration. Synchronously waiting for a config API loses early page data, and config service jitter would delay RUM startup. The right pattern is “initialize immediately with the cached value + refresh the cache asynchronously for next time” — the new rate taking effect one page load later is perfectly acceptable.
- Call
stopSession()when the rate changes (web / Mini Program only). Because the decision is sticky within a session (rule 2), a user who lost the draw under 20% will stay silent even after a new page initializes at 100% — for up to 4 hours — because the existing session keeps its old decision.stopSession()expires the current session immediately; the user’s next interaction starts a new session and re-draws under the new rate. Note that this trick does not work on mobile: the sampling rate is frozen into the sampler at initialization, so a new session afterstopSession()still draws under the old rate. By default the new value takes effect at the next cold start; if you need it immediately, see the advanced approach for mobile below. - Always have a fallback for fetch failures. When the config API is unavailable, fall back to the cached or built-in default value so collection is never interrupted.
Advanced for mobile: applying a new sampling rate immediately
A mobile app process can stay alive for days, so “takes effect at the next cold start” may not be enough for scenarios like incident investigation, where you need full collection right now. In that case, use the full rebuild path: callstopInstance() to stop the current SDK instance, then re-initialize with the new sampling rate. The key discipline: when a new config arrives, only record it — don’t rebuild immediately. Wait for a quiet lifecycle moment, such as the app returning to the foreground — rebuilding in the middle of user activity cuts off the current view and session context.
- Android
- iOS
Best practice 2: business-defined custom sampling
The default random draw treats every user equally, but businesses often want differentiation: collect all VIP users, watch canary users closely, always sample users who recently hit errors. You can achieve this by moving the draw from the SDK into your business code: your code decides whether the current session is sampled, and passes only0 or 100 as sessionSampleRate, reducing it to an on/off switch.
- Web
- Android
- iOS
Why hash bucketing instead of random numbers
The base rule useshash(userId) % 100 instead of Math.random(), which brings two properties the default draw lacks:
- User-level stability: the same user always gets the same decision, so you can answer “does user A have data?” — all sessions of a sampled user are present, and an unsampled user definitively has none.
- Monotonic rollout: when the rate goes from 20% to 100%, every previously sampled user stays sampled, and the newly added users are a pure increment, keeping the data continuous and comparable. Changing the
saltreshuffles all buckets.
Caveats
- The decision must be stable within a session. If you draw with
Math.random()on every page load, different pages within the same session may reach different conclusions, while the SDK only honors the session’s first decision — the symptom is “configured 100 but nothing gets reported”, which is very hard to debug. Deterministic hashing avoids this by construction. - Rule changes also require switching sessions. When a user moves from the “not sampled” bucket to the “sampled” bucket, follow best practice 1: on web / Mini Program, call
stopSession()once when the current decision differs from the previously cached one; on mobile, the change takes effect at the next cold start by default, or rebuild the instance following the advanced approach. - Use
sessionSampleRate: 0instead of skippinginit(). Skipping initialization breaksaddAction/addErrorcalls scattered through your code, forcing null checks everywhere; passing 0 initializes the SDK into a silent state with a unified code path. - Platform-side data reflects what was actually collected. With custom sampling, the platform cannot know your true sampling ratio; the session volume you see is the collected volume, and cannot be extrapolated to total traffic. If you need full-traffic estimates, compute them on your side based on your own sampling rules.
Best practice 3: full error capture with proportional sampling for the rest
A common requirement is “cut data volume to 20%, but never miss an error”. SettingsessionSampleRate to 20 will not do it: sampling works per session, and a session that loses the draw reports nothing at all — errors included (rule 1). You would lose roughly four fifths of your errors along with the volume. The correct shape is to collect every session so no error is lost, then bucket on a stable key inside beforeSend so only the winning share keeps full data:
- Error events: always kept — 100% of errors
- Failed requests: always kept — 100% of API failures (HTTP 5xx and request failures are resource events, not error events)
- Successful requests, user actions, long tasks: kept only for the winning 20% — the bulk of your volume converges to 20%
One more thing if you use Session Replay: it needs an alignment step. Natively, replay is a second-stage sample on top of collected sessions — but this recipe sets
sessionSampleRate to 100, so the SDK believes every session is collected in full and draws replay across all of them; some land outside the detail bucket and show up as “has a replay, but no behavior data”. Each tab gives the matching fix — skip it if you do not use replay.
- Session bucketing
- User bucketing
The complete data-sampling configuration:Replay alignment: the session ID does not exist yet at
init(), and the replay rate is frozen at initialization, so turn the automatic draw off, switch to manual recording, and perform the second draw yourself once initialization completes. Merge the two options below into the init() above and run the decision once after init():The manual approach has one known boundary: when a session expires and renews while the page stays open (15 minutes of inactivity or 4 hours total), the new session has a new ID and falls into a new bucket, and the SDK exposes no session-renewal event. Pages with very long dwell times need to poll
getInternalContext().session_id for changes to stay strictly aligned.DETAIL_SAMPLE_RATE × REPLAY_SAMPLE_RATE — exactly what native sessionSampleRate: 20 plus sessionReplaySampleRate: 10 yields at 2%.
Three things to know before adopting this
- Volume will not land exactly on 20%. View events are the skeleton of a session and cannot be dismissed, so they still report at 100%. How far volume actually falls depends on the share of resource and action events in your data.
- Absolute counts need to be scaled. The platform still shows 100% of sessions, but only about 20% carry full data. Ratios and percentiles such as error rate and P75 are unaffected (a random sample still represents the whole), but absolute counts such as total resource requests or total clicks must be scaled by
1 / 20%. - Session Replay needs one explicit alignment step. This recipe sets
sessionSampleRateto 100, so the SDK assumes every session is collected in full and draws replay across all of them. Skip the alignment step in the matching tab above and replay lands on sessions outside the bucket — you get the recording, but the session behind it holds no behavior data.
Platform support
For mobile platforms, the default recommendation is the “initialize with the cached value at startup + fetch and cache the latest value asynchronously” strategy, with the new rate taking effect on the next cold start. For scenarios that truly need immediate effect (such as incident investigation), follow the advanced approach and rebuild the SDK instance at a quiet lifecycle moment.
FAQ
I changed the sampling rate from 20% to 100% — why do some users still have no data?
I changed the sampling rate from 20% to 100% — why do some users still have no data?
Existing sessions’ decisions are sticky (rule 2). Sessions that lost the draw before the change stay silent until they expire (15 minutes of inactivity, or 4 hours maximum). If you use the dynamic configuration approach, make sure
stopSession() is called when the rate changes.With a 20% sampling rate, why isn't the actual collected ratio exactly 20%?
With a 20% sampling rate, why isn't the actual collected ratio exactly 20%?
Sampling is an independent probabilistic draw, not a quota (rule 3). The more traffic, the closer to the configured value; fluctuation under low traffic is normal. If you need precise control over which users are collected, use the hash bucketing approach from business-defined custom sampling.
Can I report only errors and nothing else?
Can I report only errors and nothing else?
Strictly reporting only errors is not possible — view events are the backbone of a session and cannot be turned off. But you can get very close, by combining two independent controls:Understand the three costs before adopting this setup:
- The sampling rate decides which sessions are collected. It operates on whole sessions, and an unsampled session reports nothing at all, errors included (rule 1). So lowering the sampling rate is not a way to save volume — it drops your errors along with everything else.
- The event switches decide which events each collected session reports.
trackResources,trackLongTasks,trackUserInteractionsandtrackWebVitalsare independent of sampling and apply to every session that is collected.
sessionSampleRate to 100 so no error is missed, then use the event switches to suppress non-error data. Resource events are usually the bulk of the volume, so trimming them pays off the most.Keep resource collection on instead, and use beforeSend to discard only the successful requests:- View events are still reported. At least one per page, plus throttled updates whenever metrics or event counts change, plus a keep-alive update every 5 minutes while the session is active. This baseline cannot be removed, and
beforeSendcannot discard view events either. - Error evidence is reduced to a stack trace. With
trackUserInteractionsoff, you no longer know what the user clicked before the error, which makes investigation noticeably harder. - Traced requests are not affected by the resource switch. Requests matching
allowedTracingUrlsare still reported even with resource collection off (flagged as not indexed, so they do not count toward volume), so traffic does not drop to zero.
I want to cut data volume to 20% but still capture every error — how do I configure that?
I want to cut data volume to 20% but still capture every error — how do I configure that?
Setting
sessionSampleRate to 20 will not do it — a session that loses the draw reports nothing, errors included (rule 1). Collect every session so no error is lost, then bucket by session or user so only a share of them keep full data. The full recipe is in best practice 3.