feat: Add k6 connection and message rate limiter tests#556
feat: Add k6 connection and message rate limiter tests#556cameri merged 5 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.
| 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).
| } 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.
3dba290 to
449acf6
Compare
🦋 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 |
|
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