Fix Sampling Blind Spots: Capture Failures on High-Traffic Days
EngineeringJuly 28, 202613 min read

Fix Sampling Blind Spots: Capture Failures on High-Traffic Days

Random sampling misses 90% of failures during traffic spikes. Fix it.

The Slack message came in at 2:14 PM on Black Friday: "Checkout is broken for some users. Support getting flooded. Can't find any replays of the issue."

I pulled up our session replay dashboard. Filtered by checkout page. Filtered by error. Zero results.

That seemed wrong. Support was getting 30+ tickets about a checkout failure. Error monitoring showed 847 instances of TypeError: Cannot read property 'address' of undefined in the last hour. But the replay list was empty. Every filter combination — checkout URL, JavaScript error, payment step — came back blank.

Took me four minutes to figure out what happened. Our replay sampling rate was 10%. Normal traffic: 50K sessions/day. Black Friday traffic: 480K sessions by 2 PM. The sampling was still 10%. But because error distribution isn't uniform — errors cluster in specific flows, specific browsers, specific edge cases — that 10% random sample happened to miss almost every failing session.

Random sampling doesn't care about your problems. It's random. If you're still relying on GA4 alongside fragmented monitoring tools, this problem compounds — no unified view means no unified sampling strategy.

We fixed checkout by 3:40 PM using stack traces and guesswork. Cost us 86 minutes of downtime during our biggest sales day. Post-mortem estimate: $47K in lost revenue. The replay that would've shown us exactly what users saw? Didn't exist because our sampling strategy assumed traffic would stay predictable.

What You'll Have by the End

By the end of this tutorial, you'll have a sampling configuration that:

  • Captures 100% of sessions with errors, crashes, rage-clicks, or conversion failures
  • Captures 100% of traces with HTTP 5xx, slow queries, or high latency
  • Dynamically throttles sampling of successful sessions when traffic spikes
  • Stays within your event quota even during 10x traffic events

The core idea: stop treating all sessions equally. Prioritize failures. Sample successes.

Prerequisites

Before starting:

  • A JustAnalytics account (Pro plan at $49/month includes the conditional sampling config UI)
  • Basic familiarity with your application's error patterns and critical flows
  • Access to your observability configuration (SDK init code or dashboard settings)
  • Optional: OpenTelemetry setup if you're configuring trace sampling separately

If you're running error tracking through Sentry and replay through LogRocket separately, the concepts apply but implementation differs — you'll need to coordinate sampling rules across both tools. That's one reason unified platforms make this easier. For teams tracking call conversions alongside web sessions, VeloCalls integrates with analytics sampling for cross-channel visibility.

Step 1: Understand Why Random Sampling Fails at Scale

Random sampling works fine at steady-state traffic. If you capture 10% of sessions and your error rate is 2%, you'll see roughly 10% of errors. The math is simple: sample proportionally, observe proportionally.

The problem shows up in three scenarios:

Scenario 1: Traffic spikes. Your 10% sample rate was calibrated for 50K daily sessions. Black Friday brings 500K. You're still capturing 10%, but your storage and quota are overwhelmed. You start dropping data. The sessions you drop are random — including the ones with errors.

Scenario 2: Low-frequency failures. A bug affects 0.1% of users — the ones on Safari 17.3 with a specific extension. At 50K sessions, that's 50 affected sessions/day. At 10% sampling, you capture 5. Enough to notice. On a 500K day, you have 500 affected sessions but still capture ~50 (since your absolute sample count is capped by quota). You see the same number of failures but they look equally rare — you don't notice the 10x spike.

Scenario 3: Edge-case clustering. Errors cluster. They don't distribute evenly across your user base. The checkout bug that showed up on Black Friday? It required a specific sequence: user applies coupon, user edits address, user returns to payment. Random sampling has no preference for that sequence. It samples coupon flows and no-coupon flows equally, even though coupon flows are where the bug lives.

The fix isn't sampling more. That blows up costs. The fix is sampling smarter. For teams dealing with observability tool sprawl, fragmented sampling across multiple tools makes this even harder to coordinate.

(I'm still annoyed at myself for not figuring this out sooner. We had the tools. We just configured them wrong.)

Step 2: Configure Error-Biased Sampling for Session Replay

Error-biased sampling — sometimes called conditional sampling or priority sampling — applies different sample rates to different session types.

Here's the JustAnalytics configuration:

import { JA } from '@justanalytics/browser';

JA.init({
  siteId: process.env.NEXT_PUBLIC_JA_SITE_ID,

  sessionReplay: {
    enabled: true,

    // Conditional sampling rules (evaluated in order)
    samplingRules: [
      // Always capture sessions with unhandled exceptions
      { condition: 'has:exception', sampleRate: 1.0 },

      // Always capture sessions with rage-clicks
      { condition: 'has:rage_click', sampleRate: 1.0 },

      // Always capture sessions hitting error pages (4xx/5xx)
      { condition: 'page_status >= 400', sampleRate: 1.0 },

      // Always capture sessions with conversion failures
      { condition: 'funnel:checkout_failed', sampleRate: 1.0 },

      // Sample successful sessions dynamically based on traffic
      { condition: 'default', sampleRate: 'dynamic', maxSessionsPerHour: 5000 }
    ],

    privacyMode: 'mask-inputs',
  },

  rageClickDetection: true,
  errorTracking: true,
});

The dynamic sample rate adjusts automatically. At 10K sessions/hour, it might sample 50%. At 100K sessions/hour, it drops to 5%. The maxSessionsPerHour cap ensures you don't blow through your quota during a traffic event.

Here's the key part: the conditional rules fire first. A session with an exception always gets captured at 100%, regardless of the dynamic baseline. You're guaranteed to see every failure, even if successful sessions get aggressively downsampled.

Why this matters on peak days: During Black Friday, your dynamic rate might drop to 3% for successful sessions. That's fine — you don't need to watch hundreds of thousands of happy checkout flows. But every checkout failure, every JavaScript crash, every rage-click-induced abandonment gets recorded. The failures you need to debug are never randomly dropped.

For teams already using our error-funnel correlation, conditional sampling integrates directly — funnel drop-offs trigger 100% capture automatically.

Step 3: Configure Tail-Based Sampling for Distributed Traces

Session replay is client-side. Distributed traces are backend. The sampling logic differs, but the principle is identical: keep failures, sample successes.

Head-based sampling decides at the start of a request whether to trace it. Problem: you don't know if a request will fail until it's done. You might drop a trace that turns out to be a 5-minute database timeout. Frustrating.

Tail-based sampling buffers trace data until the request completes, then decides whether to keep it based on outcome. Slower and more resource-intensive, but guarantees you never drop interesting traces. In my opinion, the extra overhead is worth it — I'd rather pay for buffer memory than miss the one trace that explains a production outage.

If you're using OpenTelemetry (and you probably should be — JustAnalytics is OpenTelemetry-native), here's a tail-based sampler config:

# otel-collector-config.yaml
processors:
  tail_sampling:
    decision_wait: 10s
    num_traces: 100000
    expected_new_traces_per_sec: 1000
    policies:
      # Always keep traces with errors
      - name: error-policy
        type: status_code
        status_code: { status_codes: [ERROR] }

      # Always keep slow traces (P99+ latency)
      - name: latency-policy
        type: latency
        latency: { threshold_ms: 2000 }

      # Always keep traces with specific attributes
      - name: payment-failure-policy
        type: string_attribute
        string_attribute:
          key: payment.status
          values: [failed, declined, timeout]

      # Sample successful traces probabilistically
      - name: probabilistic-policy
        type: probabilistic
        probabilistic: { sampling_percentage: 15 }

The collector buffers spans for 10 seconds (decision_wait), then applies policies in order. Any trace matching error-policy, latency-policy, or payment-failure-policy gets kept at 100%. Everything else goes through 15% probabilistic sampling.

On a normal day, you might capture 20-25% of traces total (all failures plus 15% of successes). On a 10x traffic day, your failure capture stays at 100% while the probabilistic sampling takes the load. You're not missing the important stuff.

For teams debugging slow API endpoints, our P99 latency guide covers how to use tail-sampled traces to isolate the specific requests causing tail latency spikes.

Step 4: Set Up Alerts for Sampling Degradation

Even with smart sampling, things can go sideways. Your traffic hits 20x instead of 10x. Your error rate spikes so high that 100% capture of failures overwhelms storage. A deployment introduces a bug that affects 50% of sessions instead of 0.5%.

Set up alerts for these scenarios:

// Example: Alert when dynamic sample rate drops below 5%
JA.trackMetric('replay.effective_sample_rate', currentSampleRate);

// In your alert config (or JustAnalytics dashboard):
// alert: replay.effective_sample_rate < 0.05
// severity: warning
// message: "Session replay sampling has dropped below 5%. Review quota or increase limits."

Also monitor:

  • Error capture ratio: Are you capturing 100% of sessions with errors? If this drops below 99%, your conditional rules might be misconfigured or your collector is dropping data. We learned this the hard way — thought our config was perfect until we ran the numbers post-incident.
  • Buffer overflow: For tail-based trace sampling, monitor the collector's buffer. If traces are being dropped before decision, your num_traces limit is too low.
  • Quota consumption rate: If you're burning through 80% of monthly quota by day 15, adjust dynamic sample rates or request a quota increase before your next traffic spike. Don't wait until day 28.

For teams managing uptime monitoring alongside analytics, correlated alerts help distinguish between "error sampling dropped because traffic spiked" and "errors are spiking because something is actually broken."

Common Errors and How to Fix Them

"Conditional sampling rules aren't firing — all sessions sampled equally"

Check rule order. Rules evaluate in sequence; the first match wins. If your default rule is first, it catches everything before condition-specific rules run. Error conditions should always come before the default catch-all.

"Tail-based sampling dropping traces before decision"

Increase num_traces in your collector config. This sets the buffer size for in-flight traces. If you have 10,000 concurrent requests and a 10-second decision window, you need buffer space for ~100,000 traces (10 seconds × 10,000 RPS). The default is often too low for high-traffic services.

"Error-biased sampling capturing too many sessions on high-error days"

If your error rate spikes to 40% (deployment gone wrong), 100% capture of errors means you're capturing 40% of all traffic. Add a secondary throttle: { condition: 'has:exception', sampleRate: 1.0, maxPerHour: 20000 }. You'll still see far more errors than random sampling would provide, but you won't blow your quota.

"Dynamic sample rate not adjusting — stuck at initial value"

Dynamic sampling requires real-time traffic telemetry to work. If your SDK can't phone home session counts, it can't adjust. Check network connectivity between your client and the ingestion endpoint. Also verify your SDK version — dynamic sampling requires @justanalytics/browser@^3.4.0 or later.

"Can't correlate replays with traces — different session IDs"

If you're running separate replay (LogRocket) and trace (Datadog) tools, session IDs won't match automatically. You need to pass a shared context identifier between them. Unified platforms like JustAnalytics use the same session ID across replay, errors, and traces by default. If you're stitching tools together, implement cross-tool session correlation explicitly. Teams migrating from LogRocket specifically can reference our LogRocket migration guide for session ID mapping strategies.

Step 5: Test Before Peak Traffic Arrives

Don't wait for Black Friday to find out your sampling config is broken.

Load test your sampling logic: Run synthetic traffic at 10x normal volume. Inject errors at known rates (1%, 5%, 10%). Verify that your dashboards show 100% of injected errors while routine traffic samples down appropriately.

Simulate collector pressure: For tail-based sampling, push your OpenTelemetry collector to 90% of its buffer limit. Confirm that error traces still get priority over probabilistic ones. Confirm that buffer overflow alerts fire before you actually drop critical data.

Audit post-peak: After every traffic event, review sampling metrics. What was your effective sample rate? Did conditional rules fire correctly? Did you capture all known failures (cross-reference with error monitoring)? Document what worked and what needs adjustment.

I wish we'd done this before our Black Friday incident. We load-tested checkout throughput. We load-tested database capacity. We load-tested CDN cache hit rates. Nobody thought to load-test observability sampling. Cost us $47K to learn that lesson.

For browser-specific edge cases that show up under load, JustBrowser cloud profiles can help reproduce failures across Safari, Firefox, and mobile viewports during pre-event testing.

Next Steps

Now that your sampling strategy prioritizes failures:

  • Review retention settings. Error-biased sampling reduces total volume, but you might want longer retention for the high-value failures you're now capturing. Our session replay storage guide covers retention tradeoffs.
  • Set up anomaly alerting. If your sampling config is working, error visibility improves — which means anomaly detection on error counts becomes more reliable. The AI Command Center add-on ($25/month) includes automated anomaly detection on error trends.
  • Correlate with paid traffic. If your peak days come from paid campaigns, correlating replay failures with ad source tells you whether certain traffic sources bring low-quality sessions. ClickzProtect flags bot and fraud traffic that inflates error rates without representing real user issues.
  • Document your config. Sampling rules are easy to misconfigure during an incident ("just turn it up to 100%!") and forget to revert. Keep your sampling config in version control alongside your app code.

The checkout bug that cost us $47K? Post-incident, we implemented error-biased sampling with 100% capture on checkout failures, payment declines, and cart abandonment. Black Friday the following year: we caught a similar edge-case bug within 11 minutes because we had replay footage of the first affected session. Fixed it by 11:47 AM. No revenue impact.

The difference wasn't better engineers. It was better sampling.

That's it. That's the whole lesson.

Frequently Asked Questions

What is error-biased sampling for session replay and traces?

Error-biased sampling captures 100% of sessions and traces where something went wrong — JavaScript exceptions, HTTP 5xx responses, slow database queries, rage-clicks — while randomly sampling successful sessions at a lower rate (typically 5-20%). This guarantees you never lose visibility into failures, even when traffic spikes force you to reduce overall sampling rates.

How does tail-based sampling differ from head-based sampling?

Head-based sampling decides whether to record a trace at the start, before you know if it will fail. Tail-based sampling waits until the trace completes, then keeps it only if it meets criteria (error, high latency, specific attributes). Tail-based sampling ensures failing requests aren't randomly dropped, but requires buffering data until decisions can be made.

Why do high-traffic days expose sampling blind spots?

Most teams set a fixed sample rate — say 10% — that works at normal volume. When Black Friday hits 10x traffic, you're still capturing 10% of sessions, but the absolute number of missed failures grows proportionally. A 2% error rate at 100K sessions is 2K errors captured at 10% sampling (200 visible). At 1M sessions, it's 20K errors but still only 2K visible — the other 18K vanish.

Can I configure JustAnalytics for error-biased sampling on peak days?

Yes. Set conditional sampling rules: 100% capture for sessions with exceptions, rage-clicks, or conversion failures; dynamic sampling (10-25%) for successful sessions that scales with traffic. The system automatically adjusts when traffic spikes, maintaining full coverage of failures while throttling routine session storage.


Try JustAnalytics

All-in-one observability in one under-5KB script: cookieless analytics + error tracking + APM + session replay + uptime + structured logs. Replaces GA4 + Sentry + Datadog + Pingdom + LogRocket. Free tier (100K events/mo), Pro $49/month ($39 annual).

Start free → · AI Command Center MCP

JP
JustAnalytics Platform TeamContributor

Author at JustAnalytics.

Related posts