When our authentication API began slowing down during peak traffic hours, we knew something was wrong. Customers complained about long wait times after hitting the Login button. Some users even experienced random failures. What started as a “small delay issue” soon turned into a real performance bottleneck that affected the entire product.
This is the story of how we used k6 to find the root cause, fix the weaknesses, and improve our API performance by almost 40% in two days.
Project Background
The project was a mid-sized SaaS platform offering a dashboard for business customers. Every user had to log in before accessing the system. The Login API was one of the most frequently called endpoints. On a typical day:
- ~15,000 daily logins
- Peak traffic during 9–11 AM
- SLA required p95 response time < 500ms
Things ran smoothly until our user base grew. At around 300 requests per minute, the login service began to choke.
The Performance Problem
The symptoms were clear:
- p95 response time went above 1.2 seconds
- Occasional timeouts (especially in the mornings)
- High CPU usage on the backend server
- Increasing database query time
We had some monitoring tools, but the data was too scattered. We needed a structured, repeatable way to reproduce the issue.
That’s when we brought in k6.
Why We Chose k6
We had tried JMeter earlier, but found it:
- Heavy to run
- Hard to maintain scripts
- Not developer-friendly
- Difficult to integrate into CI
k6, on the other hand, was everything we needed:
- Simple JavaScript-based scripting
- Fast execution
- Works in Docker and CI/CD
- Easy to set thresholds
- Exports metrics to Grafana
- Very easy to scale the load
It felt like using a modern tool designed for modern APIs.
Designing the Test Scenario
We wanted a realistic simulation of login traffic. So we planned a load model like this:
export const options = {
stages: [
{ duration: '1m', target: 50 },
{ duration: '2m', target: 100 },
{ duration: '3m', target: 100 },
{ duration: '1m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95) < 500'],
http_req_failed: ['rate < 0.01'],
},
};The script sent login requests with real credentials, validated response structure, and checked status codes. We also added custom metrics for DB latency returned by the API.
Before Optimization: Test Results
The results were clear and concerning:
| Metric | Result (Before) |
| p95 response time | 1200ms |
| Error rate | 5% |
| Max CPU usage | ~80% |
| Throughput | Only 60% of expected |
| DB query time | ~480ms |
Our Grafana dashboard showed sharp spikes in latency during the ramp-up stage. The API could not handle 100 VUs for sustained periods.
This helped us recreate the exact issue we faced in production.
Root Cause Analysis: What k6 Helped Reveal
The k6 tests pointed us toward the real bottlenecks.
1. Slow database queries
The API performed three separate queries to fetch user details, roles, and settings. This caused unnecessary load on the database.
2. Missing database indexes
The login table had grown large, and searching by email was slow due to lack of proper indexing.
3. Unnecessary JSON payload
The API returned more fields than required during login, making serialization slow.
4. Verbose logging inside loops
Debug-level logs running for each request increased CPU load.
5. No caching layer
Every login fetched fresh configuration settings, even though they changed rarely.
k6’s clear metrics made the degradation pattern obvious.
The Fixes Implemented
We worked on multiple improvements:
- Added proper indexing: Searching by email became instantly faster.
- Combined queries: We reduced three queries to one optimized JOIN query.
- Implemented a small Redis cache: Frequently accessed user settings were cached for 5 minutes.
- Reduced payload size: We removed unnecessary fields from the response.
- Moved logs to async and reduced verbosity: This brought down CPU usage significantly.
After Optimization: k6 Re-Test Results
We reran the same k6 script with the same load model.
| Metric | Before | After |
| p95 latency | 1200ms | 350ms |
| Error rate | 5% | 0% |
| Throughput | +60% | 2× improvement |
| CPU usage | 80% | 50% |
| DB query time | 480ms | 120ms |
| DB query time | 480ms | 120ms |
The difference was dramatic.
The API easily handled 100 VUs for long durations and passed the threshold:
- http_req_duration p(95) < 500ms
- http_req_failed < 1%
Lessons Learned
Here are our key takeaways:
- Performance testing should start early.
Issues become harder to fix later. - k6 makes it easy for developers and QA to collaborate.
Scripts are simple JavaScript functions. - Load tests should run in CI/CD.
We now run k6 smoke load tests on every deployment. - Observability is essential.
k6 + Grafana gave deep insights. - Small optimizations can create big improvements.
Conclusion
k6 helped us turn a slow, unreliable login API into a fast and stable service. Its fast scripting, clear metrics, and CI-friendly design made performance testing simple and effective. This case study is one example, but the same approach can be applied to any API or microservice.
If you work with APIs and want to prevent performance issues before they hit production, k6 is one of the best tools you can add to your workflow.
















