Skip to content

feat(session-replay-browser): add flushIntervalConfig to tune rrweb event-split cadence#1744

Open
lewgordon-amplitude wants to merge 5 commits into
mainfrom
lew/sr-flush-interval-config
Open

feat(session-replay-browser): add flushIntervalConfig to tune rrweb event-split cadence#1744
lewgordon-amplitude wants to merge 5 commits into
mainfrom
lew/sr-flush-interval-config

Conversation

@lewgordon-amplitude
Copy link
Copy Markdown
Collaborator

@lewgordon-amplitude lewgordon-amplitude commented May 8, 2026

Summary

Adds flushIntervalConfig: { minIntervalMs, maxIntervalMs } to SessionReplayLocalConfig so customers (or specific deployments) can raise the rrweb event-split cadence without waiting on an SDK upgrade.

Context

Even after SR-4280 landed (honoring X-Session-Replay-Event-Skipped server back-pressure), we're still seeing significant 200+throttle traffic on v1.30.2. Walking the splitting logic (BaseEventsStore.shouldSplitEventsList) explains why:

  • The interval starts at MIN_INTERVAL = 500ms and grows by 500ms after each split, capped at MAX_INTERVAL = 10s.
  • A busy session fires roughly 10 batches in the first 30 seconds before settling at 6/min — comparable to 1.5 minutes of steady-state traffic packed into the first half-minute.
  • All of that happens before the first server response can arrive, so SR-4280's flushPauseUntilMs doesn't dampen the upfront burst. The throttle pause only affects the next schedule, not in-flight or already-queued contexts.
  • flushPauseUntilMs and killedSessions are in-memory and per-instance, so every fresh page load / SDK init repeats the burst from scratch.

This PR doesn't touch the defaults — it just exposes the knob. We can roll out higher values for specific high-volume customers (or recommend tuning for self-hosted standalone integrations) without shipping a behavior change to everyone.

What's in here

  • flushIntervalConfig?: FlushIntervalConfig on SessionReplayLocalConfig, with minIntervalMs and maxIntervalMs fields (both optional).
  • Validation in SessionReplayLocalConfig — clamps non-finite or sub-100ms values, raises max to match min if a caller inverts them, warns via the configured logger.
  • Wires the values into createEventsManager('replay') (the interaction manager already had its own INTERACTION_MIN_INTERVAL plumbing).
  • New test/config/local-config.test.ts covering defaults, custom values, clamping, and inversion.
  • New tests in session-replay.test.ts verifying the values flow into createEventsManager.

Test plan

  • pnpm --filter @amplitude/session-replay-browser build — clean
  • npx jest --testPathIgnorePatterns=e2e — 915/915 pass, 100% coverage
  • pnpm lint:eslint / pnpm lint:prettier — clean (warnings are pre-existing, unrelated files)
  • Try a higher minIntervalMs (e.g., 3000ms) on a heavy-traffic test page and confirm the early-burst request count drops as expected

Follow-ups (not in this PR)

  • Bumping default MIN_INTERVAL once we have data on customer impact of the new knob.
  • Persisting flushPauseUntilMs / killedSessions to localStorage keyed by API key, so cold starts honor the server's last directive instead of re-discovering it.
  • Exposing this via remote config so backend ops can dial cadence per-org.

🤖 Generated with Claude Code


Note

Medium Risk
Introduces new customer-facing tuning for replay flush cadence and fixes a timing initialization regression that affects when the first event split occurs, which can change request patterns in production. Changes are guarded behind optional config but touch core event batching/splitting behavior.

Overview
Adds flushIntervalConfig (minIntervalMs/maxIntervalMs) to SessionReplayLocalConfig to let callers tune rrweb event-split/flush cadence, including sanitization (100ms floor, Infinity support for max, and min/max cross-validation with warnings).

Wires the config through SessionReplay into createEventsManager('replay'), and fixes BaseEventsStore so the first split interval correctly honors caller-supplied minInterval (not the class-field default).

Adds unit/integration coverage for the validator and forwarding behavior, plus Playwright e2e tests and a configurable Playwright baseURL; updates the SR capture test page to accept interval params via querystring.

Reviewed by Cursor Bugbot for commit ce43e66. Bugbot is set up for automated code reviews on this repo. Configure here.

…vent-split cadence

Exposes `flushIntervalConfig: { minIntervalMs, maxIntervalMs }` so customers facing
high upfront request volume (and the resulting 200 + X-Session-Replay-Event-Skipped
throttle responses) can raise the cadence without an SDK upgrade for the team.
Defaults are unchanged (500ms / 10s).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lewgordon-amplitude lewgordon-amplitude requested a review from a team as a code owner May 8, 2026 20:24
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

Session Replay Browser E2E Results

passed  119 passed

Details

stats  119 tests across 11 suites
duration  2 minutes, 45 seconds
commit  ce43e66

Comment thread packages/session-replay-browser/src/session-replay.ts
Comment thread packages/session-replay-browser/src/config/local-config.ts Outdated
…st default bounds

If a customer set only `minIntervalMs` higher than the default `MAX_INTERVAL` (10s) (or
only `maxIntervalMs` below `MIN_INTERVAL`), the SDK's class-field default would silently
clamp the effective interval, negating the customer's tune-up. Sanitization now cross-checks
against effective bounds and raises/lowers the unspecified side to match the user-supplied
value, with a logger warning.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lewgordon-amplitude
Copy link
Copy Markdown
Collaborator Author

bugbot run

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

1 issue from previous review remains unresolved.

Fix All in Cursor

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit c1d3f4e. Configure here.

…st split

Class-field initializer `private interval = this.minInterval` ran before the
constructor body, freezing `interval` at the class-field default (500ms) regardless
of `args.minInterval`. Moved the assignment into the constructor body after the
override, so a customer setting `flushIntervalConfig.minIntervalMs: 5000` actually
gets a 5s wait before the first split — instead of the burst this PR was meant
to prevent. Adds regression tests verifying first-split behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lewgordon-amplitude
Copy link
Copy Markdown
Collaborator Author

bugbot run

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Infinity for maxIntervalMs clamps to floor, creating maximum aggression
    • Separated non-finite value handling to clamp Infinity to MAX_INTERVAL instead of MIN_FLUSH_INTERVAL_FLOOR_MS, preserving the intent of 'no cap' for maximum intervals.

Create PR

Or push these changes by commenting:

@cursor push 9407c5a6ac
Preview (9407c5a6ac)
diff --git a/packages/session-replay-browser/src/config/local-config.ts b/packages/session-replay-browser/src/config/local-config.ts
--- a/packages/session-replay-browser/src/config/local-config.ts
+++ b/packages/session-replay-browser/src/config/local-config.ts
@@ -134,8 +134,15 @@
     }
   }
   if (raw.maxIntervalMs !== undefined) {
-    if (!Number.isFinite(raw.maxIntervalMs) || raw.maxIntervalMs < MIN_FLUSH_INTERVAL_FLOOR_MS) {
+    if (!Number.isFinite(raw.maxIntervalMs)) {
+      // For maxIntervalMs, Infinity means "no cap" — clamp to MAX_INTERVAL to preserve that intent.
+      // NaN is invalid and also gets the large-default treatment for a maximum bound.
       loggerProvider.warn(
+        `flushIntervalConfig.maxIntervalMs ${raw.maxIntervalMs} is not finite; clamping to ${MAX_INTERVAL}ms.`,
+      );
+      sanitized.maxIntervalMs = MAX_INTERVAL;
+    } else if (raw.maxIntervalMs < MIN_FLUSH_INTERVAL_FLOOR_MS) {
+      loggerProvider.warn(
         `flushIntervalConfig.maxIntervalMs ${raw.maxIntervalMs} is below floor ${MIN_FLUSH_INTERVAL_FLOOR_MS}ms; clamping.`,
       );
       sanitized.maxIntervalMs = MIN_FLUSH_INTERVAL_FLOOR_MS;

You can send follow-ups to the cloud agent here.

Comment thread packages/session-replay-browser/src/config/local-config.ts
…no upper bound"

Previously `!Number.isFinite()` clamped Infinity to the 100ms floor — directly
inverting the customer's intent. Math.min(Infinity, x) === x is well-defined in
BaseEventsStore.shouldSplitEventsList, so Infinity is a valid "no cap on growth"
sentinel. Validator now rejects only NaN and sub-floor numbers for max; min still
clamps Infinity (where "never split" is also broken).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lewgordon-amplitude
Copy link
Copy Markdown
Collaborator Author

bugbot run

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit c5d8d09. Configure here.

Adds Playwright spec covering full real-browser SDK init with the new option:
custom min/max delivers events on explicit flush, Infinity max is accepted,
and sub-floor min is clamped without aborting init. Also threads PLAYWRIGHT_BASE_URL
through e2e/playwright.config.ts so the suite can target a non-default port,
and teaches sr-capture-test.html to read flushIntervalConfig from URL params.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants