.NET Backend Observability & Telemetry Integration
.NET Backend Observability: Telemetry Integration & Performance Optimization
Client
E-commerce platform with high-load .NET 8 backend
Challenge
The .NET backend had no observability β no distributed tracing, no centralized metrics, no structured logging. Finding slow requests required manual log digging. The team needed full OpenTelemetry integration: trace propagation from Ingress through .NET to downstream services (PostgreSQL, Redis, RabbitMQ, HTTP upstreams), automatic slow-query detection, and a path to optimize P99 latency.
Solution
1. OpenTelemetry Integration in .NET 8
- Added OpenTelemetry NuGet packages:
OpenTelemetry.Extensions.Hosting,Instrumentation.AspNetCore,Instrumentation.Http,Instrumentation.EntityFrameworkCore,Instrumentation.StackExchangeRedis,Instrumentation.Runtime,Instrumentation.Process,Exporter.OpenTelemetryProtocol - Configured
ActivitySourcewith AspNetCore, HttpClient, EF Core, Redis instrumentations - Enabled W3C
traceparentpropagation; added middleware to returnX-Trace-Idin response headers - Exported via OTLP/gRPC to OTel Collector (
:4317)
2. Infrastructure Layer (Kubernetes / Nginx / OTel Collector)
- Nginx/Ingress: injected
traceparent/tracestateheaders for W3C context propagation - K8s Deployment: environment variables for
OTEL_SERVICE_NAME,OTEL_EXPORTER_OTLP_ENDPOINT, sampling config (parentbased_traceidratioat 20%) - OTel Collector: Tail-based sampling processor β always capture errors (status_code: ERROR), slow requests (>1s latency), probabilistic 5% for the rest; drop health checks
3. Methodology for Finding Slow Requests
- Top-N Slow Endpoints (P95/P99): Grafana dashboards with
histogram_quantile(0.99, sum(rate(http_server_request_duration_seconds_bucket[5m])) by (le, http_route)) - Flame Graphs & Span Waterfall: Analyze trace breakdown β Middleware β Controller β EF Core β Downstream API β Serialization
- Runtime Metrics: Monitor ThreadPool queue length, Gen 2 GC frequency, allocations to detect sync-over-async blocking
- Live Profiling:
dotnet-trace+ Speedscope/PerfView for CPU sampling on production under load
4. Optimization Checklist Applied
| Category | Fixes Applied |
|---|---|
| Database | Added missing indexes (EXPLAIN ANALYZE), eliminated N+1 with .Include()/.Select(), .AsNoTracking() for read-only, tuned connection pooling |
| I/O & Network | Full async/await with CancellationToken, removed .Result/.Wait() |
| Allocations & GC | ArrayPool<T>, Memory<T>, ReadOnlySpan<T>, streaming JSON to Response.BodyWriter, enabled Server GC |
| Caching | Redis distributed cache (FusionCache), OutputCaching for HTTP responses |
| HTTP Clients | IHttpClientFactory / singleton SocketsHttpHandler with PooledConnectionLifetime=15m |
Technologies
Results
β
Full trace coverage: 100% of requests have traceparent, X-Trace-Id returned to clients
β
P99 latency reduced: from 2.3s β 420ms (82% improvement) after optimizing top-5 endpoints
β
Error detection: Tail-sampling captures 100% of 5xx errors and >1s requests even at 20% sample rate
β
Proactive alerting: P99 > SLA alerts + error-rate spikes configured in Grafana/Alertmanager
β
Database optimization: N+1 eliminated, missing indexes added, connection pool tuned β EF Core spans down 65%
β
ThreadPool health: Queue length near zero under peak load; sync-over-async eliminated
β
Load test validated: k6 soak test confirms stable P99 under 2x expected RPS
Architecture
Duration
2 weeks (integration + collector config + optimization sprint + load testing)
Cost
from $2,000