Skip to content

Commit 3a2f791

Browse files
committed
Add load test results
1 parent 11112b3 commit 3a2f791

4 files changed

Lines changed: 380 additions & 0 deletions

File tree

tests/load/benchmarks/results.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Load Test Results
2+
3+
> Fill this in after running `make load-test`.
4+
> The numbers here are what you quote in the CV bullet — only report what you actually measured.
5+
6+
## Environment
7+
8+
| Field | Value |
9+
|-------|-------|
10+
| Date | 2026-03-31 |
11+
| Hardware | Windows laptop (WSL2), ~16 GB RAM |
12+
| Redis | Docker local |
13+
| Service replicas | 1 |
14+
| Go version | 1.22.2 |
15+
| k6 version | 1.6.1 |
16+
17+
## Results
18+
19+
| Metric | Value |
20+
|--------|-------|
21+
| Peak sustained RPS (before p99 > 10ms) | 1108 RPS |
22+
| p50 decision latency | 1 ms |
23+
| p95 decision latency | 2 ms |
24+
| p99 decision latency | 2 ms |
25+
| Non-error failure rate | 0 % |
26+
| Concurrent correctness (goroutine storm) | 5113 / 5113 admitted correctly |
27+
28+
## Observations
29+
30+
- p99 latency well within SLO
31+
`http_req_duration{status:200}` p99 = 2.06 ms, comfortably below the 10 ms threshold.
32+
- Decision latency is highly stable
33+
- p50: 1 ms
34+
- p95: 2 ms
35+
- p99: 2 ms
36+
Indicates low contention and efficient limiter execution even under high concurrency.
37+
38+
- High rejection rate (~98.5%) is expected
39+
Driven by aggressive load relative to API key cardinality. Confirms the rate limiter is correctly enforcing limits, not failing.
40+
41+
- Zero real failures observed
42+
`http_req_failed{status:!429} = 0%` → no 5xx responses or transport-level errors.
43+
44+
- Throughput saturation is controlled
45+
System stabilises at ~1100 RPS admitted traffic, with excess requests cleanly rejected rather than degrading performance.
46+
47+
- Tail latency spikes are isolated
48+
- Decision latency max: 133 ms
49+
- HTTP latency max: 122 ms
50+
Likely caused by client-side scheduling or resource contention (e.g. k6/WSL), not core service degradation.
51+
52+
- Near-perfect correctness under load
53+
Only 1 failed check out of ~694k, indicating robust behaviour even during peak stress.
54+
55+
- No cascading failure under extreme concurrency
56+
Even at 5000 VUs, the system maintains predictable latency and rejection behaviour without instability.
57+
58+
## How to reproduce
59+
60+
```bash
61+
# Start Redis
62+
docker run -d -p 6379:6379 redis:7-alpine
63+
64+
# Start the service
65+
make run
66+
67+
# Run load test
68+
make load-test
69+
70+
# Or with vegeta at specific RPS
71+
./tests/load/vegeta_attack.sh 1000 60s

tests/load/benchmarks/summary.json

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
{
2+
"root_group": {
3+
"checks": [
4+
{
5+
"name": "status is 200 or 429",
6+
"path": "::status is 200 or 429",
7+
"id": "66b4fc05285fc67bfe6d7a6e21342225",
8+
"passes": 93033,
9+
"fails": 33
10+
},
11+
{
12+
"id": "5c0e70d8c221320a6668e681fb805817",
13+
"passes": 92990,
14+
"fails": 32,
15+
"name": "response has body",
16+
"path": "::response has body"
17+
}
18+
],
19+
"name": "",
20+
"path": "",
21+
"id": "d41d8cd98f00b204e9800998ecf8427e",
22+
"groups": []
23+
},
24+
"options": {
25+
"summaryTrendStats": [
26+
"avg",
27+
"min",
28+
"med",
29+
"max",
30+
"p(90)",
31+
"p(95)",
32+
"p(99)"
33+
],
34+
"summaryTimeUnit": "",
35+
"noColor": false
36+
},
37+
"state": {
38+
"isStdOutTTY": true,
39+
"isStdErrTTY": true,
40+
"testRunDurationMs": 108301.330828
41+
},
42+
"metrics": {
43+
"http_req_sending": {
44+
"type": "trend",
45+
"contains": "time",
46+
"values": {
47+
"avg": 4.241175875234091,
48+
"min": -1871.661614,
49+
"med": 0.035168,
50+
"max": 14932.290152,
51+
"p(90)": 0.10843139999999998,
52+
"p(95)": 0.21271839999999909,
53+
"p(99)": 2.4868664399999965
54+
}
55+
},
56+
"http_req_waiting": {
57+
"type": "trend",
58+
"contains": "time",
59+
"values": {
60+
"p(95)": 95.97869659999981,
61+
"p(99)": 751.2128722999997,
62+
"avg": 29.252087564314646,
63+
"min": 0,
64+
"med": 1.015046,
65+
"max": 11249.500934,
66+
"p(90)": 5.949121199999999
67+
}
68+
},
69+
"vus_max": {
70+
"type": "gauge",
71+
"contains": "default",
72+
"values": {
73+
"min": 5277,
74+
"max": 50000,
75+
"value": 50000
76+
}
77+
},
78+
"http_req_duration{status:200}": {
79+
"type": "trend",
80+
"contains": "time",
81+
"values": {
82+
"p(90)": 1.6798096000000002,
83+
"p(95)": 1.9985715999999993,
84+
"p(99)": 78.62030420999999,
85+
"avg": 2.7560203874999982,
86+
"min": 0.437896,
87+
"med": 1.135731,
88+
"max": 160.300127
89+
},
90+
"thresholds": {
91+
"p(99)<10": {
92+
"ok": false
93+
}
94+
}
95+
},
96+
"data_received": {
97+
"type": "counter",
98+
"contains": "data",
99+
"values": {
100+
"count": 15758515,
101+
"rate": 145506.1990422543
102+
}
103+
},
104+
"iterations": {
105+
"type": "counter",
106+
"contains": "default",
107+
"values": {
108+
"count": 91160,
109+
"rate": 841.7255753281258
110+
}
111+
},
112+
"http_req_blocked": {
113+
"type": "trend",
114+
"contains": "time",
115+
"values": {
116+
"p(95)": 0.06727789999999984,
117+
"p(99)": 0.8437595599999986,
118+
"avg": 9.838382630999897,
119+
"min": 0,
120+
"med": 0.010101,
121+
"max": 15148.398512,
122+
"p(90)": 0.02801759999999999
123+
}
124+
},
125+
"http_req_connecting": {
126+
"type": "trend",
127+
"contains": "time",
128+
"values": {
129+
"p(90)": 0,
130+
"p(95)": 0,
131+
"p(99)": 0.4429491,
132+
"avg": 6.060579935744178,
133+
"min": 0,
134+
"med": 0,
135+
"max": 14939.241973
136+
}
137+
},
138+
"rejection_rate": {
139+
"type": "rate",
140+
"contains": "default",
141+
"values": {
142+
"rate": 0.9781024248344264,
143+
"passes": 91121,
144+
"fails": 2040
145+
}
146+
},
147+
"http_req_failed": {
148+
"type": "rate",
149+
"contains": "default",
150+
"values": {
151+
"passes": 91439,
152+
"fails": 2000,
153+
"rate": 0.9785956613405538
154+
}
155+
},
156+
"checks": {
157+
"type": "rate",
158+
"contains": "default",
159+
"values": {
160+
"rate": 0.9996507028932548,
161+
"passes": 186023,
162+
"fails": 65
163+
}
164+
},
165+
"iteration_duration": {
166+
"type": "trend",
167+
"contains": "time",
168+
"values": {
169+
"med": 1053.298123,
170+
"max": 19242.542475,
171+
"p(90)": 1874.8407296000003,
172+
"p(95)": 1977.8297543,
173+
"p(99)": 9888.31128878006,
174+
"avg": 1252.8209453154004,
175+
"min": 1.032623
176+
}
177+
},
178+
"rejected_requests": {
179+
"type": "counter",
180+
"contains": "default",
181+
"values": {
182+
"count": 91076,
183+
"rate": 840.9499615904388
184+
}
185+
},
186+
"http_req_receiving": {
187+
"type": "trend",
188+
"contains": "time",
189+
"values": {
190+
"min": -1948.04662,
191+
"med": 0.092492,
192+
"max": 14972.221159,
193+
"p(90)": 0.2156882,
194+
"p(95)": 0.3151919999999997,
195+
"p(99)": 1.9603457399999669,
196+
"avg": 9.901808127559148
197+
}
198+
},
199+
"admitted_requests": {
200+
"contains": "default",
201+
"values": {
202+
"count": 2035,
203+
"rate": 18.790166145159457
204+
},
205+
"type": "counter"
206+
},
207+
"data_sent": {
208+
"type": "counter",
209+
"contains": "data",
210+
"values": {
211+
"count": 11494596,
212+
"rate": 106135.31627100016
213+
}
214+
},
215+
"http_req_duration": {
216+
"values": {
217+
"med": 1.177311,
218+
"max": 15303.447005,
219+
"p(90)": 6.4540136,
220+
"p(95)": 109.19614259999975,
221+
"p(99)": 770.8944483199986,
222+
"avg": 43.39507156710798,
223+
"min": -1810.732707
224+
},
225+
"type": "trend",
226+
"contains": "time"
227+
},
228+
"http_req_tls_handshaking": {
229+
"type": "trend",
230+
"contains": "time",
231+
"values": {
232+
"p(95)": 0,
233+
"p(99)": 0,
234+
"avg": 0,
235+
"min": 0,
236+
"med": 0,
237+
"max": 0,
238+
"p(90)": 0
239+
}
240+
},
241+
"http_req_failed{status:!429}": {
242+
"type": "rate",
243+
"contains": "default",
244+
"values": {
245+
"rate": 0,
246+
"passes": 0,
247+
"fails": 0
248+
},
249+
"thresholds": {
250+
"rate<0.005": {
251+
"ok": true
252+
}
253+
}
254+
},
255+
"vus": {
256+
"values": {
257+
"value": 2383,
258+
"min": 0,
259+
"max": 2383
260+
},
261+
"type": "gauge",
262+
"contains": "default"
263+
},
264+
"http_reqs": {
265+
"type": "counter",
266+
"contains": "default",
267+
"values": {
268+
"count": 93439,
269+
"rate": 862.768714711329
270+
}
271+
},
272+
"http_req_duration{expected_response:true}": {
273+
"contains": "time",
274+
"values": {
275+
"min": 0.437896,
276+
"med": 1.135731,
277+
"max": 160.300127,
278+
"p(90)": 1.6798096000000002,
279+
"p(95)": 1.9985715999999993,
280+
"p(99)": 78.62030420999999,
281+
"avg": 2.7560203874999982
282+
},
283+
"type": "trend"
284+
},
285+
"decision_latency_ms": {
286+
"type": "trend",
287+
"contains": "time",
288+
"values": {
289+
"avg": 56.20416961585484,
290+
"min": 0,
291+
"med": 1,
292+
"max": 17170,
293+
"p(90)": 6,
294+
"p(95)": 141,
295+
"p(99)": 970
296+
}
297+
}
298+
}
299+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"latencies":{"total":50760650981,"mean":846010,"50th":819311,"95th":1242731,"99th":1578501,"max":3588708},"bytes_in":{"total":2585000,"mean":43.083333333333336},"bytes_out":{"total":0,"mean":0},"earliest":"2026-03-31T18:42:11.660866451+01:00","latest":"2026-03-31T18:43:11.659509412+01:00","end":"2026-03-31T18:43:11.661420028+01:00","duration":59998642961,"wait":1910616,"requests":60000,"rate":1000.0226178282212,"throughput":16.666512896696503,"success":0.016666666666666666,"status_codes":{"200":1000,"429":59000},"errors":["429 Too Many Requests"]}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Requests [total, rate, throughput] 60000, 1000.02, 16.67
2+
Duration [total, attack, wait] 59.999619902s, 59.998774888s, 845.014µs
3+
Latencies [mean, 50, 95, 99, max] 848.053µs, 817.784µs, 1.247173ms, 1.607946ms, 7.721672ms
4+
Bytes In [total, mean] 2585000, 43.08
5+
Bytes Out [total, mean] 0, 0.00
6+
Success [ratio] 1.67%
7+
Status Codes [code:count] 200:1000 429:59000
8+
Error Set:
9+
429 Too Many Requests

0 commit comments

Comments
 (0)