Skip to content

feat: Add k6 connection and message rate limiter tests#556

Open
saniddhyaDubey wants to merge 3 commits intocameri:mainfrom
saniddhyaDubey:perf/issue-469-k6-redis-testing
Open

feat: Add k6 connection and message rate limiter tests#556
saniddhyaDubey wants to merge 3 commits intocameri:mainfrom
saniddhyaDubey:perf/issue-469-k6-redis-testing

Conversation

@saniddhyaDubey
Copy link
Copy Markdown
Collaborator

Add k6 Load Testing Suite for Rate Limiters

Description

Adds k6 load tests to validate connection and message rate limiting behavior. (#469)

Tests Added:

  1. connection-rate-limiter.ts - Tests 12 conn/sec limit across 4 load stages
  2. message-rate-limiter.ts - Tests 3 msg/min limit with continuous REQ messages

Both tests require a running Docker instance and output formatted metrics showing success/rejection rates.

Motivation

Automate rate limiter testing. Previously manual (wscat only). Provides:

  • Repeatable load testing under controlled conditions
  • Quantified rejection rates vs configured limits
  • Foundation for future tests (event limits, subscriptions, etc.)

How to Test

docker-compose up -d
npm run test:connection
npm run test:message

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.
Screenshot 2026-04-20 at 11 27 18 PM

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.
Screenshot 2026-04-20 at 11 25 16 PM

Types of changes

  • New feature
  • Non-functional (foundation setup)

Checklist

  • Code follows project style
  • Tests added and passing
  • Docs added

@socket-security
Copy link
Copy Markdown

socket-security Bot commented Apr 21, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​types/​k6@​1.7.01001007989100

View full report

@coveralls
Copy link
Copy Markdown
Collaborator

coveralls commented Apr 21, 2026

Coverage Status

coverage: 74.533%. remained the same — saniddhyaDubey:perf/issue-469-k6-redis-testing into cameri:main

@cameri cameri requested a review from Copilot April 21, 2026 03:38
@cameri cameri self-assigned this Apr 21, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/k6 and 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.

Comment thread package.json
"nostream": minor
---

perf: added k6 testing for redis on connection and message service rate limiting
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
perf: added k6 testing for redis on connection and message service rate limiting
perf: added k6 testing for connection and message service rate limiting

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +39
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);
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment thread test/integration/performance/connection-limiting-k6.ts
};

export default function () {
const res = ws.connect(relayUrl, null, function (socket) {
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
const res = ws.connect(relayUrl, null, function (socket) {
const res = ws.connect(relayUrl, {}, function (socket) {

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +75
} 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';
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,83 @@
import { check, sleep } from 'k6';
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's move both files to test/performance

Comment thread package.json
"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'",
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"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",

Comment thread package.json
"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'",
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"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",

Comment thread package.json
"@types/chai-as-promised": "^7.1.5",
"@types/express": "4.17.21",
"@types/js-yaml": "4.0.5",
"@types/k6": "^1.7.0",
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants