feat: GPU offload, signup limit, load tests & capacity planning

- Route all AI workloads (Ollama, STT, TTS, Image Gen) to GPU server
  (192.168.178.11) via LAN instead of host.docker.internal
- Upgrade default model to gemma3:12b and max concurrent to 5
- Add daily signup limit service (MAX_DAILY_SIGNUPS env var)
- Add GET /api/v1/auth/signup-status public endpoint
- Add k6 load test suite (web-apps, auth, sync-websocket, ollama)
- Add capacity planning documentation
- Fix: add eslint-config to sveltekit-base and calendar Dockerfiles

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-28 21:14:24 +01:00
parent 16367384c7
commit 9276d9a212
12 changed files with 683 additions and 14 deletions

View file

@ -0,0 +1,84 @@
/* eslint-disable no-undef, no-console, @typescript-eslint/no-unused-vars */
import ws from 'k6/ws';
import { check, sleep } from 'k6';
import { Rate, Counter, Trend } from 'k6/metrics';
const errorRate = new Rate('errors');
const messagesReceived = new Counter('ws_messages_received');
const messagesSent = new Counter('ws_messages_sent');
const connectTime = new Trend('ws_connect_time', true);
const SYNC_URL = __ENV.SYNC_URL || 'ws://localhost:3050';
export const options = {
stages: [
{ duration: '30s', target: 10 },
{ duration: '3m', target: 30 },
{ duration: '30s', target: 0 },
],
thresholds: {
errors: ['rate<0.10'],
ws_connect_time: ['p(95)<1000'],
},
};
export default function () {
const url = `${SYNC_URL}/ws`;
const startTime = Date.now();
const res = ws.connect(url, {}, function (socket) {
const connected = Date.now() - startTime;
connectTime.add(connected);
socket.on('open', () => {
// Send a sync handshake (collection subscription)
const handshake = JSON.stringify({
type: 'subscribe',
collections: ['tasks', 'events', 'contacts'],
userId: `loadtest-vu-${__VU}`,
lastSyncTimestamp: new Date(Date.now() - 60000).toISOString(),
});
socket.send(handshake);
messagesSent.add(1);
});
socket.on('message', (data) => {
messagesReceived.add(1);
// Parse and validate sync messages
try {
const msg = JSON.parse(data);
check(msg, {
'has type field': (m) => m.type !== undefined,
});
} catch (_) {
// Binary or non-JSON message
}
});
socket.on('error', (e) => {
errorRate.add(true);
console.error(`WS error VU ${__VU}: ${e.error()}`);
});
// Keep connection alive for 10-30 seconds (simulates real user session)
const sessionDuration = Math.random() * 20 + 10;
// Send periodic sync pings
const pingInterval = setInterval(() => {
socket.send(JSON.stringify({ type: 'ping' }));
messagesSent.add(1);
}, 5000);
sleep(sessionDuration);
clearInterval(pingInterval);
socket.close();
});
const ok = check(res, {
'WS connection status is 101': (r) => r && r.status === 101,
});
errorRate.add(!ok);
sleep(Math.random() * 2 + 1);
}