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 Type | Purpose |
|---|---|
| Load Testing | Measure expected workload |
| Stress Testing | Determine breaking point |
| Spike Testing | Evaluate sudden traffic increases |
| Soak Testing | Measure long-term stability |
| Scalability Testing | Evaluate 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:
| Metric | Why It Matters |
|---|---|
| Average Latency | Overall response time |
| P95 Latency | User experience under load |
| P99 Latency | Worst-case performance |
| Requests per Second | Throughput |
| Error Rate | Reliability |
| CPU Usage | Resource utilization |
| Memory Usage | Capacity 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:
Join the conversation! Your thoughts help the community grow.