feat: Add k6 connection and message rate limiter tests#556
feat: Add k6 connection and message rate limiter tests#556saniddhyaDubey wants to merge 3 commits intocameri:mainfrom
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Adds a k6-based load testing suite intended to validate relay connection and message rate limiting behavior under concurrent WebSocket load.
Changes:
- Added k6 WebSocket load test scripts for message and connection limiting.
- Added npm scripts to run the new k6 tests (with a Docker container check).
- Added
@types/k6and a changeset entry for the release.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
test/integration/performance/message-limiting-k6.ts |
New k6 script to send REQ messages continuously and summarize NOTICE/EOSE/EVENT outcomes. |
test/integration/performance/connection-limiting-k6.ts |
New k6 script to create many WebSocket connections and summarize success vs. rate-limited outcomes. |
package.json |
Adds test:connection / test:message scripts and @types/k6. |
package-lock.json |
Locks @types/k6 dependency. |
.changeset/jolly-canyons-glow.md |
Release note for adding the k6 tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "nostream": minor | ||
| --- | ||
|
|
||
| perf: added k6 testing for redis on connection and message service rate limiting |
There was a problem hiding this comment.
Changeset text says this adds k6 testing "for redis". The added scripts exercise relay rate limiting over WebSocket; they don’t directly test Redis behavior, so the release note is misleading. Consider rewording to describe rate limiter load/performance tests without tying it to a specific storage backend.
| perf: added k6 testing for redis on connection and message service rate limiting | |
| perf: added k6 testing for connection and message service rate limiting |
| const res = ws.connect(relayUrl, {}, function (socket) { | ||
| socket.on('close', () => { | ||
| socketClosed = true; | ||
| connectionRateLimited.add(1); | ||
| }); | ||
|
|
||
| socket.on('open', () => { | ||
| connectionSuccess.add(1); | ||
| }); | ||
|
|
||
| socket.setTimeout(() => { | ||
| if (!socketClosed) { | ||
| socket.close(); | ||
| } | ||
| }, 3000); |
There was a problem hiding this comment.
connection_rate_limited is incremented on every socket close, including the intentional close triggered by setTimeout. This will misclassify successful connections as rate-limited and also misses true handshake rejections (e.g. HTTP 429), where the close/open handlers never run. Consider classifying rate-limited connections based on the ws.connect() response status (count non-101 responses) and only treating early/abnormal closes as rate limiting if you can reliably identify them (e.g. close code/reason).
| }; | ||
|
|
||
| export default function () { | ||
| const res = ws.connect(relayUrl, null, function (socket) { |
There was a problem hiding this comment.
ws.connect(relayUrl, null, ...): k6's ws.connect expects a params object as the second argument. Passing null is unnecessary and can lead to runtime issues depending on how k6 handles the params value. Prefer {} (or omit the params arg if supported by your k6 version).
| const res = ws.connect(relayUrl, null, function (socket) { | |
| const res = ws.connect(relayUrl, {}, function (socket) { |
| } catch (e: any) { | ||
| errorCounter.add(1); | ||
| console.error('Failed to parse message:', e.message); | ||
| } | ||
| }); | ||
|
|
||
| socket.setTimeout(function () { | ||
| socket.close(); | ||
| }, 9000); | ||
| }); | ||
|
|
||
| check(res, { | ||
| 'status 101': (r) => r && r.status === 101, | ||
| }); | ||
| } | ||
|
|
||
| export function handleSummary(data: any) { | ||
| const notices = data.metrics?.notice_messages?.values?.count || 0; | ||
| const eoses = data.metrics?.eose_messages?.values?.count || 0; | ||
| const events = data.metrics?.event_messages?.values?.count || 0; | ||
| const iterations = data.metrics?.iterations?.values?.count || 0; | ||
| const wsSessions = data.metrics?.ws_sessions?.values?.count || 0; | ||
| const msgsSent = data.metrics?.ws_msgs_sent?.values?.count || 0; | ||
| const msgsReceived = data.metrics?.ws_msgs_received?.values?.count || 0; | ||
| const dataReceived = data.metrics?.data_received?.values?.count || 0; | ||
| const checks = data.metrics?.checks?.values?.passes || 0; | ||
|
|
||
| const totalMessages = notices + eoses + events; | ||
| const successRate = totalMessages > 0 ? ((eoses + events) / totalMessages * 100).toFixed(2) : 0; | ||
|
|
||
| const rate = parseFloat(successRate as string); | ||
| const successStatus = rate >= 80 ? '✓ GOOD' : rate >= 50 ? '⚠ MODERATE' : '✗ POOR'; | ||
| const rateLimitStatus = notices > 0 ? '⚠ ACTIVE' : '✓ INACTIVE'; |
There was a problem hiding this comment.
This file contains TypeScript-only syntax (catch (e: any), data: any, successRate as string). k6 run executes JavaScript and will fail to parse TS annotations unless you add a transpilation step. Either remove TS-only syntax / use JSDoc types and rename to .js, or update the npm scripts to transpile before running k6.
| @@ -0,0 +1,83 @@ | |||
| import { check, sleep } from 'k6'; | |||
There was a problem hiding this comment.
let's move both files to test/performance
| "test:load": "node -r ts-node/register ./scripts/security-load-test.ts", | ||
| "smoke:nip03": "node -r ts-node/register scripts/smoke-nip03.ts", | ||
| "test:integration": "cucumber-js", | ||
| "test:connection": "docker ps | grep nostream > /dev/null && k6 run test/integration/performance/connection-limiting-k6.ts || echo 'Error: nostream container not running'", |
There was a problem hiding this comment.
| "test:connection": "docker ps | grep nostream > /dev/null && k6 run test/integration/performance/connection-limiting-k6.ts || echo 'Error: nostream container not running'", | |
| "test:performance:connection-rate-limit": "k6 run test/performance/connection-limiting-k6.ts", |
| "smoke:nip03": "node -r ts-node/register scripts/smoke-nip03.ts", | ||
| "test:integration": "cucumber-js", | ||
| "test:connection": "docker ps | grep nostream > /dev/null && k6 run test/integration/performance/connection-limiting-k6.ts || echo 'Error: nostream container not running'", | ||
| "test:message": "docker ps | grep nostream > /dev/null && k6 run test/integration/performance/message-limiting-k6.ts || echo 'Error: nostream container not running'", |
There was a problem hiding this comment.
| "test:message": "docker ps | grep nostream > /dev/null && k6 run test/integration/performance/message-limiting-k6.ts || echo 'Error: nostream container not running'", | |
| "test:performance:message-rate-limit": "k6 run test/performance/message-limiting-k6.ts", |
| "@types/chai-as-promised": "^7.1.5", | ||
| "@types/express": "4.17.21", | ||
| "@types/js-yaml": "4.0.5", | ||
| "@types/k6": "^1.7.0", |
There was a problem hiding this comment.
do we not need k6 as well? or is it installed globally?
looks like we need to update the docs as well to document this. we can add it to CONTRIBUTING.md
Add k6 Load Testing Suite for Rate Limiters
Description
Adds k6 load tests to validate connection and message rate limiting behavior. (#469)
Tests Added:
connection-rate-limiter.ts- Tests 12 conn/sec limit across 4 load stagesmessage-rate-limiter.ts- Tests 3 msg/min limit with continuous REQ messagesBoth tests require a running Docker instance and output formatted metrics showing success/rejection rates.
Motivation
Automate rate limiter testing. Previously manual (wscat only). Provides:
How to Test
Metrics output shows connection/message success rates and type breakdown.
Screenshots:
Message Rate Limiter Test:

Tests relay's ability to reject excess REQ messages at 3 msg/min limit. Shows 40 NOTICE rejections, 16 EOSE acceptances, and 72 EVENT results (62.50% success rate), with Redis message-specific rate limit keys stored alongside connection limits.
Connection Rate Limiter Test:

Tests relay's ability to reject excess connections at 12 conn/sec limit. Shows 350 successful connections vs 350 rate-limited rejections (50% rejection at 2x load), with Redis storing rate limit state visible in Docker sidebar.
Types of changes
Checklist