feat: Add k6 connection and message rate limiter tests - #556
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.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
| 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).
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
| } 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.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
3dba290 to
449acf6Compare🦋 Changeset detectedLatest commit: 0a33009 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
saniddhyaDubey
commented
Apr 30, 2026
Rebased onto latest main and addressed all review feedback.
|
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