Application Insights sampling: the types, the defaults, and why it beats filtering
Sampling keeps a representative share of whole operations and records on each kept item how many it stands for, so totals and charts stay statistically correct. The classic .NET SDK 2.x samples adaptively at five items per second per server host; SDK 3.x and the Microsoft OpenTelemetry Distro default to rate-limited sampling at five traces per second; ingestion sampling at the service is a fallback Microsoft labels not recommended. Filtering is different: it drops items without a trace, so request and failure counts come out low.
Sampling is the part of Application Insights that people switch off when numbers look wrong, and then switch back on when the bill arrives. Both reactions come from the same misunderstanding: assuming that a sampled resource holds “some of the data” in the way a filtered one does. It doesn’t. A sampled resource holds a statistically fair subset plus the multiplier needed to restore the totals.
How sampling works
The decision is made per operation, using the operation ID of the incoming request. Either the whole operation is kept (the request, its dependency calls, exceptions and traces) or none of it is, so when you open a kept request in the portal its children are all there. Items with no operation ID, such as work on a background thread without an HTTP context, fall back to a plain percentage per telemetry type.
Every kept item records the rate it was sampled at. In the classic schema that is the itemCount column: a request kept at 20% has itemCount 5. The portal’s charts multiply by it, and your own queries must too.
Three kinds of telemetry are never sampled, by any method: sessions, metrics (custom metrics included) and performance counters.
The four kinds of sampling
| Kind | Where it runs | Default in |
|---|---|---|
| Adaptive | Classic .NET SDK, in the app | ASP.NET and ASP.NET Core SDK 2.x, Azure Functions |
| Fixed-rate | Classic SDKs, JavaScript SDK, Java | Off unless configured |
| Rate-limited | SDK 3.x, OpenTelemetry distros, Java agent | SDK 3.x, Microsoft OpenTelemetry Distro, Java agent 3.4.0+ |
| Ingestion | Application Insights service endpoint | Off; set in the portal |
Adaptive sampling adjusts the percentage to stay near a target rate, recalculated from a moving average. Fixed-rate keeps the percentage you choose. Rate-limited sampling caps the number of traces kept per second. Ingestion sampling drops items as they arrive at the service: it saves storage and cost, but not network traffic, and it only applies to data the SDK didn’t already sample.
Classic SDK 2.x
The .NET Classic API SDK 2.x is deprecated and retires on March 31, 2027, but it still runs a great deal of production code, so its defaults are worth knowing precisely.
Adaptive sampling is on by default in the ASP.NET and ASP.NET Core SDKs. The target is five telemetry items per second on each server host, and the SDK adds two adaptive processors by default: one for Event items and one for everything else, so custom events are sampled separately from requests and dependencies. Each SDK instance decides independently, which means ten instances behind a load balancer aim for ten times the volume.
In ASP.NET Core you switch the default off and register your own sampler on the processor chain. This example keeps a fixed 10%:
using Microsoft.ApplicationInsights.AspNetCore.Extensions;
using Microsoft.ApplicationInsights.Extensibility;
var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<TelemetryConfiguration>(config =>
{
var chain = config.DefaultTelemetrySink.TelemetryProcessorChainBuilder;
chain.UseSampling(10); // percent; choose a value close to 100/N
chain.Build();
});
builder.Services.AddApplicationInsightsTelemetry(new ApplicationInsightsServiceOptions
{
EnableAdaptiveSampling = false,
});
ASP.NET apps on .NET Framework do the same in ApplicationInsights.config by replacing the adaptive processor node with the fixed-rate one:
<TelemetryProcessors>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.SamplingTelemetryProcessor, Microsoft.AI.ServerTelemetryChannel">
<SamplingPercentage>20</SamplingPercentage>
</Add>
</TelemetryProcessors>
Choose a fixed percentage close to 100/N for a whole number N: 50, 33.33, 25, 20, 10, 1. The ApplicationInsights.config guide covers the rest of that file.
To exempt specific items, the 2.x answer is a telemetry initializer that sets SamplingPercentage to 100 on them. Initializers run before the sampling processor, so every sampling method leaves those items alone.
Today: SDK 3.x and the Microsoft OpenTelemetry Distro
Both current .NET paths replaced adaptive sampling with a rate-limited sampler built for OpenTelemetry traces.
Application Insights .NET SDK 3.x removed EnableAdaptiveSampling and both sampling processors. It offers two settings on TelemetryConfiguration, ApplicationInsightsServiceOptions, appsettings.json or applicationinsights.config: TracesPerSecond (the default mode, five per second) or SamplingRatio (0.0 to 1.0; setting it switches rate limiting off). Requests and dependencies share one rate, and per-type rules such as “always keep exceptions” are no longer supported.
The Microsoft OpenTelemetry Distro for .NET, Node.js and Python also defaults to five traces per second. In .NET you switch to a fixed ratio by clearing the rate limit:
using Microsoft.OpenTelemetry;
var builder = WebApplication.CreateBuilder(args);
builder.UseMicrosoftOpenTelemetry(options =>
{
options.Exporters = ExportTarget.AzureMonitor;
options.AzureMonitor.TracesPerSecond = null; // turn off rate-limited sampling
options.AzureMonitor.SamplingRatio = 0.1F; // keep about 10% of traces
});
Node.js does the same with samplingRatio and tracesPerSecond: 0:
const { useMicrosoftOpenTelemetry } = require("@microsoft/opentelemetry");
useMicrosoftOpenTelemetry({
azureMonitor: {
azureMonitorExporterOptions: {
connectionString: process.env.APPLICATIONINSIGHTS_CONNECTION_STRING,
},
},
samplingRatio: 0.1,
tracesPerSecond: 0,
});
Python reads the standard OpenTelemetry variables, set before the distro starts; don’t carry over the older sampling_ratio and traces_per_second arguments:
export OTEL_TRACES_SAMPLER="microsoft.fixed_percentage"
export OTEL_TRACES_SAMPLER_ARG=0.1
Java uses APPLICATIONINSIGHTS_SAMPLING_PERCENTAGE or APPLICATIONINSIGHTS_SAMPLING_REQUESTS_PER_SECOND; since agent 3.4.0 the default is rate-limited at about five requests per second, and logs outside a request are always collected.
Two details catch people out. First, logs can follow the trace decision: trace-based log sampling drops log records that belong to an unsampled trace. It is on by default in .NET (EnableTraceBasedLogsSampler) and Java, off by default in Node.js and Python. Second, Live Metrics requires the Azure Monitor sampler; replacing it with a generic OpenTelemetry sampler breaks the live view. See enabling Live Metrics from code.
Count correctly in KQL
With sampling on, count() counts stored rows, not requests. Weight everything by itemCount:
requests
| where timestamp > ago(1d)
| summarize requests = sum(itemCount),
p95_ms = percentilew(duration, itemCount, 95)
by operation_Name
To see whether, and how hard, each type is being sampled, use Microsoft’s check:
union requests, dependencies, pageViews, browserTimings, exceptions, traces
| where timestamp > ago(1d)
| summarize RetainedPercentage = 100 / avg(itemCount) by bin(timestamp, 1h), itemType
Anything below 100 is sampled. The latency by operation and failed requests by result code queries in the library are already sampling-aware, and the KQL builder writes the weighting for you.
Why filtering is not a substitute
A telemetry processor (classic) or span processor (OpenTelemetry) that drops items is a filter. Microsoft’s own warning is blunt: filtering skews the statistics you see in the portal and makes it hard to follow related items. Nothing records what was dropped, so log-based request counts, failure rates and any alert built on them come out low, and an operation can lose its request while keeping its dependencies.
Filtering still has a place: health-check probes, synthetic traffic and other items you never want to count at all. Drop those deliberately and document it. For “we send too much”, sample.
Ingestion sampling and the daily cap
Ingestion sampling is set under Usage and estimated costs > Data Sampling. It is a last resort for apps you can’t redeploy, and Microsoft labels it not recommended for OpenTelemetry apps: it has no control over which traces and spans survive, so more traces arrive broken. If incoming data was already sampled below 100%, the service ignores the setting entirely.
The daily cap is a different tool. For a workspace-based resource the effective cap is the lower of the Application Insights and Log Analytics caps; when it’s hit, collection stops for the rest of a 24-hour period, data that arrives over the cap is still billed, and the reset hour can’t be changed. Treat it as a fuse. To turn a cap into a sampling percentage, use the sampling and cost calculator, and see Application Insights pricing for what each GB costs.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Request counts far below the load balancer’s | count() instead of sum(itemCount), or a filter | Weight by itemCount; review processors that drop items |
| Volume still too high after setting ingestion sampling | The SDK already samples, so ingestion sampling is ignored | Lower the SDK rate instead |
| Volume grows with every new instance | Adaptive and rate-limited targets apply per instance | Divide the target by the instance count |
| Traces broken across services | Each app samples independently | Use the same sampler and rate in every service |
| Logs missing for some requests | Trace-based log sampling | Expected; turn it off only if you need every log |
| “Throttled” data at peak | Over 32,000 events per second, measured over a minute | Sample harder; the limit applies per connection string |
| Live Metrics empty after a sampler change | Not using the Azure Monitor sampler | Restore the distro’s sampler |
Queries for this topic
-
Sampling rate in effect, per telemetry type
requestsdependenciespageViewsbrowserTimingsexceptionstracescustomEventsavailabilityResults cost
Returns: One row per telemetry type with the rows stored, the items they stand for, and the percentage of items kept by sampling over the last 24 hours.
-
Telemetry volume by table, in GB per day
requestsdependenciesexceptionstracescustomEventspageViewsavailabilityResultsperformanceCounterscustomMetricsbrowserTimings cost
Returns: One row per table with the billed gigabytes over the last 7 complete days, the stored rows, the items they stand for after sampling, and the average gigabytes per day, largest first.
-
Request latency p50, p95 and p99 by operation
requests latency
Returns: One row per operation name with its request count and its p50, p95 and p99 duration in milliseconds, slowest p95 first.
Frequently asked questions
Is Application Insights sampling on by default?
On the server side, yes. The classic ASP.NET and ASP.NET Core SDK 2.x and Azure Functions use adaptive sampling at five items per second per host. SDK 3.x, the Microsoft OpenTelemetry Distro for .NET, Node.js and Python, and the Java agent from 3.4.0 use rate-limited sampling at about five traces or requests per second.
Are custom metrics and performance counters sampled?
No. Microsoft states that sessions, metrics (including custom metrics) and performance counters are never sampled by any of the sampling types, and OpenTelemetry metrics are never sampled either. That makes metrics the right signal for alerts that must not miss anything.
Why does the portal show a different sampling rate from the one I set?
The Data Sampling setting on Usage and estimated costs is ingestion sampling, and the service ignores it when the SDK already sampled the data. The tile shows the value you set, not the rate in effect. Query itemCount to see what is really retained.
Can I keep every exception while sampling requests?
In the classic 2.x SDK, yes: exclude the Exception type from the sampling processor, or set SamplingPercentage to 100 in a telemetry initializer for the items you must keep. SDK 3.x removed per-type sampling; one decision applies to all trace telemetry.
Sources, checked
- Telemetry sampling in Azure Application Insights (Classic API)
- Sampling in Azure Application Insights with OpenTelemetry
- Configure OpenTelemetry in Application Insights (Enable sampling)
- Migrate Application Insights Classic API SDKs to OpenTelemetry
- Monitor .NET and Node.js applications with Application Insights (Classic API 2.x)
- Set daily cap on Log Analytics workspace
- Azure Monitor service limits (Application Insights)