Tune Sentry Sampling for Django Without Losing Important Data

Control Sentry volume and cost for Django: keep error sample_rate at 1.0, set traces_sample_rate or a traces_sampler that keeps slow and critical transactions, drop health checks and noisy errors with before_send, and use rate limits and spike protection.

Problem statement

Sentry's quota runs out mid-month, or the bill jumps after enabling performance monitoring. Health check transactions, bot traffic, and one noisy error dominate the volume, while the transactions you care about (checkout, signup, slow admin pages) are sampled away. Sentry lets you choose per event what to keep: errors, traces, and profiles each have their own sampling controls, and filters can drop noise before it is sent.

Sentry volume controlErrorskeep all, filter noiseTracestraces_samplerDrophealth checks, botsQuotarate limits, spikesKeep every real error; sample traces by importance, not uniformly.
  1. Keep sample_rate=1.0 for errors; reduce error noise with ignore_errors and before_send instead.
  2. Replace a flat traces_sample_rate with a traces_sampler that drops health checks and keeps important or slow transactions at higher rates.
  3. Drop static file and bot transactions, and group or ignore expected exceptions (Http404, client disconnects).
  4. Set project rate limits and spike protection in Sentry so a runaway loop cannot consume the quota.
  5. Sample profiles relative to traces with profiles_sample_rate or profile_session_sample_rate.

Step-by-step solution

1. Keep errors, filter noise

import sentry_sdk
from django.core.exceptions import DisallowedHost

def before_send(event, hint):
    exc = hint.get("exc_info", (None, None, None))[1]
    if isinstance(exc, (BrokenPipeError, ConnectionResetError)):
        return None                      # client went away
    return event

sentry_sdk.init(
    dsn=os.environ["SENTRY_DSN"],
    sample_rate=1.0,
    ignore_errors=[DisallowedHost],
    before_send=before_send,
)

Dropping error events by sampling hides real bugs; filtering by type keeps everything that matters. Use Sentry's inbound filters (browser extensions, legacy browsers, localhost) for frontend projects.

2. Sample traces by importance

def traces_sampler(ctx):
    if ctx.get("parent_sampled") is not None:
        return ctx["parent_sampled"]            # follow upstream decisions
    path = (ctx.get("wsgi_environ") or {}).get("PATH_INFO", "")
    if path.startswith(("/healthz", "/static/", "/favicon.ico")):
        return 0.0
    if path.startswith(("/checkout/", "/accounts/signup/")):
        return 1.0
    if path.startswith("/api/"):
        return 0.1
    return 0.05

sentry_sdk.init(dsn=..., traces_sampler=traces_sampler)

Celery task transactions arrive with celery_job in the context; sample them by task name the same way.

event type and recommendationSample rateHowErrors100 percentfilter types insteadHealth check traces!0traces_samplerCheckout, signup traces100 percenttraces_samplerOther web traces5 to 10 percenttraces_samplerCelery task tracesby task namecelery_job contextUniform sampling wastes quota on traffic you do not care aboutdjango-deployment.com
Follow parent sampling decisions so distributed traces stay complete.

3. Reduce span noise

Each trace can contain hundreds of spans (one per SQL query, cache call, or template). That is useful, but large transactions with N+1 patterns consume quota quickly. Fix the N+1 queries themselves, and consider disabling integrations you do not need, such as Redis spans if they add little.

4. Protect the quota

In Sentry's project settings, set rate limits per DSN key (events per minute) and enable spike protection. A deploy that introduces an exception in every request then cannot use the month's quota in an hour. Alerts on event volume let you react quickly.

5. Profiles and releases

Profiling multiplies data volume. Sample profiles as a fraction of sampled traces:

sentry_sdk.init(dsn=..., traces_sampler=traces_sampler, profiles_sample_rate=0.2)

Set release and environment so you can compare error rates per release and filter staging out of production views.

transactions sent (illustrative)flat 20 percent26Mdrop health checks12Mtraces_sampler by path4M0M15M30MDropping noise and sampling by value cuts volume while keeping key tracesdjango-deployment.com
Numbers depend on traffic; the relative effect is typical.

Explanation

The Sentry SDK makes sampling decisions at the start of each transaction (for traces) and for each error event. sample_rate applies to errors, traces_sample_rate or traces_sampler to transactions, and profile sampling to profiled transactions. A sampler function sees context such as the WSGI environment or Celery task, so it can assign rates by route, user type, or importance. Filters drop events after creation but before sending, which saves quota and network.

Edge cases / notes

  • Dynamic sampling. Sentry's server-side dynamic sampling adjusts what is retained on some plans; SDK sampling still controls what is sent.
  • Distributed tracing. Frontend and backend should agree; honoring parent_sampled keeps traces complete across services.
  • Self-hosted. Self-hosted Sentry has no quota, but storage and processing still scale with volume.

Next steps

FAQ

Should I sample Sentry errors?

Usually not. Keep sample_rate=1.0 and filter noisy error types instead.

What is traces_sampler?

A function that returns a sample rate per transaction, letting you keep important routes and drop noise.

How do I stop health checks from using quota?

Return 0 for health check paths in traces_sampler.

How do I avoid a runaway error using all my quota?

Set rate limits per DSN key and enable spike protection in the Sentry project.

Keep going