Web API  

Practical Load Testing of ASP.NET Core APIs with k6 and OpenTelemetry

Building a functional API is only the first step toward a successful application. As user traffic grows, APIs must continue to deliver consistent response times, maintain reliability under load, and provide enough operational visibility to identify performance bottlenecks. Without realistic load testing, performance issues often remain hidden until they affect production users.

k6 is an open-source load testing tool that allows developers to simulate realistic traffic patterns, while OpenTelemetry provides standardized observability through traces, metrics, and logs. Together, they help development teams measure API performance and understand why bottlenecks occur.

In this article, you'll learn how to load test ASP.NET Core APIs using k6, integrate OpenTelemetry for observability, and apply production-ready performance testing practices.

Why Load Testing Matters

Functional testing confirms that an API works correctly.

Load testing answers different questions:

  • How many concurrent users can the API handle?

  • Does latency remain consistent under heavy traffic?

  • Which component becomes the bottleneck?

  • Does the application recover after traffic spikes?

  • Are database queries scaling efficiently?

Without load testing, these questions remain unanswered until real users encounter problems.

Performance Testing Types

Different tests serve different purposes.

Test TypePurpose
Load TestingMeasure expected workload
Stress TestingDetermine breaking point
Spike TestingEvaluate sudden traffic increases
Soak TestingMeasure long-term stability
Scalability TestingEvaluate growth behavior

Most production systems benefit from using more than one testing strategy.

Test Architecture

A typical performance testing environment looks like this:

k6
 |
HTTP Requests
 |
Load Balancer
 |
ASP.NET Core API
 |
Database
 |
OpenTelemetry
 |
Monitoring Dashboard

OpenTelemetry captures telemetry while k6 generates realistic traffic.

Creating a Sample API

Create a new ASP.NET Core project.

dotnet new webapi -n LoadTestApi

Example endpoint:

app.MapGet("/products", () =>
{
    return Results.Ok(new[]
    {
        "Laptop",
        "Keyboard",
        "Monitor"
    });
});

This endpoint provides a simple starting point for load testing.

Installing k6

Download and install k6 according to your operating system.

Verify the installation.

k6 version

Once installed, you can execute JavaScript-based performance scripts.

Writing a Load Test

Create a file named script.js.

import http from "k6/http";

export default function ()
{
    http.get(
        "http://localhost:5000/products");
}

Execute the test.

k6 run script.js

This sends repeated requests to the API and collects performance metrics.

Simulating Concurrent Users

Increase the virtual user count.

export const options =
{
    vus: 50,
    duration: "30s"
};

This configuration simulates 50 concurrent users for 30 seconds.

Adjust values based on your expected production workload.

Configuring OpenTelemetry

Enable tracing.

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing =>
    {
        tracing.AddAspNetCoreInstrumentation();
        tracing.AddHttpClientInstrumentation();
    });

Enable metrics.

builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics =>
    {
        metrics.AddAspNetCoreInstrumentation();
    });

Instrumentation allows request flow to be observed during load tests.

Understanding Distributed Tracing

A request may travel through multiple services.

Client
   |
API Gateway
   |
ASP.NET Core API
   |
Database

Distributed tracing links every step using a shared trace identifier, making bottlenecks easier to identify.

Key Performance Metrics

During testing, monitor:

MetricWhy It Matters
Average LatencyOverall response time
P95 LatencyUser experience under load
P99 LatencyWorst-case performance
Requests per SecondThroughput
Error RateReliability
CPU UsageResource utilization
Memory UsageCapacity planning

These metrics provide a balanced view of API performance.

Measuring Database Performance

Application performance often depends on database efficiency.

Monitor:

  • Query duration

  • Connection pool usage

  • Lock contention

  • Index utilization

  • Active connections

Slow database operations frequently become the primary bottleneck during load testing.

Adding Custom Metrics

Applications can expose additional metrics.

Example:

logger.LogInformation(
    "Processing order {Id}",
    orderId);

Useful custom metrics include:

  • Orders processed

  • Cache hits

  • External API calls

  • Background jobs executed

Business metrics complement infrastructure metrics.

Testing API Scalability

Increase traffic gradually.

10 Users
   |
50 Users
   |
100 Users
   |
250 Users

Gradual scaling helps identify the point where performance begins to degrade.

Avoid increasing traffic too aggressively without monitoring resource utilization.

Identifying Bottlenecks

Performance issues often originate from:

  • Database queries

  • External APIs

  • File storage

  • Network latency

  • Thread pool exhaustion

  • Lock contention

OpenTelemetry traces help pinpoint which component contributes most to request latency.

Error Analysis

Monitor:

  • HTTP 500 responses

  • Request timeouts

  • Connection failures

  • Retry attempts

  • Dependency failures

Even low error rates can become significant under sustained traffic.

Production Best Practices

PracticeBenefit
Test production-like workloadsMore accurate results
Measure latency percentilesBetter user experience analysis
Monitor infrastructure metricsIdentify bottlenecks
Enable distributed tracingEasier debugging
Increase traffic graduallySafer testing
Test realistic user behaviorBetter performance insights
Repeat benchmarks regularlyTrack performance changes

Common Mistakes

MistakeBetter Approach
Testing only one endpointCover critical workflows
Measuring average latency onlyInclude P95 and P99 metrics
Ignoring infrastructure monitoringCollect CPU and memory metrics
Testing against development environmentsUse production-like configurations
Running one benchmark onlyRepeat tests for consistency
Optimizing without baseline measurementsEstablish reference metrics first

Troubleshooting

High response latency

Review:

  • Database performance

  • External service dependencies

  • Network latency

  • CPU utilization

Increasing error rates

Check:

  • Connection pool limits

  • Resource exhaustion

  • Timeout configuration

  • Dependency availability

CPU reaches maximum utilization

Investigate:

  • Expensive business logic

  • Serialization overhead

  • Background processing

  • Thread contention

Memory usage continues growing

Inspect:

  • Object allocations

  • Caching strategy

  • Dependency lifetimes

  • Garbage collection activity

k6 and OpenTelemetry Together

Capabilityk6OpenTelemetry
Generate LoadYesNo
Measure LatencyYesYes
Distributed TracingNoYes
Infrastructure MetricsLimitedYes
Request SimulationYesNo
Root Cause AnalysisLimitedExcellent

k6 generates realistic workloads, while OpenTelemetry explains how the application behaves under those workloads.

Frequently Asked Questions

Why use k6 instead of sending requests manually?

Manual testing cannot simulate realistic concurrent traffic. k6 allows developers to model production-like workloads and collect detailed performance metrics.

Why are P95 and P99 latency important?

Average latency may hide slow requests. Percentile metrics reveal how the application performs under less favorable conditions and better represent the user experience.

Does OpenTelemetry replace load testing?

No. OpenTelemetry provides observability, while k6 generates the traffic needed to evaluate application performance.

How often should load testing be performed?

Load testing should be included before major releases, after infrastructure changes, and whenever significant application updates affect performance.

Can load testing identify database problems?

Yes. High traffic often exposes inefficient queries, indexing issues, connection pool limitations, and lock contention that may not appear during functional testing.

Conclusion

Reliable APIs require more than functional correctness—they must continue to perform predictably under real-world traffic conditions. Load testing with k6 helps simulate production workloads, while OpenTelemetry provides the visibility needed to understand how requests flow through the application and where performance bottlenecks occur.

By combining realistic traffic generation, distributed tracing, infrastructure monitoring, and regular benchmarking, development teams can build ASP.NET Core APIs that are scalable, resilient, and ready to support high-volume production environments. Continuous performance testing should become a standard part of the software delivery lifecycle rather than an activity reserved for release day.