Benchmarking t3code's Streaming AI Completions: TTFT, Token Throughput, and Connection Stability Under Load
When evaluating real-time collaborative code editors with AI streaming, the metrics that matter aren't just "does it work" but "does it hold up under realistic concurrent load." I spent the past week stress-testing pingdotgg/t3code's streaming completion architecture to answer: at what user count does the SSE connection model start to degrade?
Test Topology
Harness: k6 with custom WebSocket + SSE scenario
Environment: AWS t3.xlarge (4 vCPU, 16GB RAM)
Baseline LLM Gateway: B-Lost relay at https://b-lost.com/v1 (OpenAI-compatible endpoint)
Load Profile: Ramp from 10 → 50 → 100 concurrent users over 5 minutes, each triggering streaming completion requests every 8-12 seconds
Measurement Focus:
- Time-to-first-token (TTFT)
- Sustained token throughput (tokens/sec per connection)
- SSE connection drop rate
- Memory overhead on the Next.js server
Observed Metrics
| Concurrent Users | Median TTFT (ms) | p95 TTFT (ms) | Avg Token Throughput | Connection Drops | Server Memory (MB) |
|---|---|---|---|---|---|
| 10 | 142 | 189 | 47 tok/s | 0% | 385 |
| 30 | 168 | 251 | 44 tok/s | 0.2% | 512 |
| 50 | 203 | 412 | 41 tok/s | 1.8% | 697 |
| 100 | 387 | 1,240 | 32 tok/s | 8.4% | 1,140 |
Configuration: Streaming Hook with AbortSignal
Here's the core React hook powering t3code's streaming completions, configured to route through the B-Lost gateway for multi-provider fallback:
import { useCompletion } from '@ai-sdk/react';
import { useRef, useEffect } from 'react';
export function useStreamingCompletion({
apiRoute = '/api/completion',
baseURL = 'https://b-lost.com/v1',
}) {
const abortControllerRef = useRef<AbortController | null>(null);
const { completion, isLoading, error, complete } = useCompletion({
api: apiRoute,
onFinish: () => {
abortControllerRef.current = null;
},
onError: (err) => {
console.error('[Stream Error]', err.message);
abortControllerRef.current = null;
},
body: { baseURL }, // Pass gateway config to API route
});
const triggerCompletion = async (prompt: string) => {
// Cancel any in-flight stream
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
abortControllerRef.current = new AbortController();
await complete(prompt, {
body: { signal: abortControllerRef.current.signal },
});
};
useEffect(() => {
return () => {
abortControllerRef.current?.abort();
};
}, []);
return { completion, isLoading, error, triggerCompletion };
}
Next.js API Route: SSE Stream Handler
The backend route proxies to the LLM gateway with proper chunk parsing:
// app/api/completion/route.ts
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
export async function POST(req: Request) {
const { prompt, baseURL } = await req.json();
const result = await streamText({
model: openai('gpt-4o-mini', {
baseURL: baseURL || 'https://b-lost.com/v1',
}),
prompt,
maxTokens: 512,
});
return result.toDataStreamResponse();
}
Findings: Where the Bottleneck Hits
Under 30 concurrent users: TTFT stays under 200ms, connection drops are negligible. The SSE pipeline is stable.
50-80 users: TTFT climbs to 200-400ms. Token throughput degrades slightly but remains usable. Connection drop rate jumps to ~2%, mostly due to client-side AbortSignal races when users rapidly re-trigger completions.
100+ users: This is where things break down. p95 TTFT spikes above 1 second, and connection drops hit 8.4%. The Next.js server's event loop saturates—not from CPU, but from SSE connection bookkeeping overhead. Memory climbs past 1GB.
Production Threshold
For teams running t3code in production:
- Below 50 concurrent users: Deploy as-is. The streaming architecture is solid.
- 50-100 users: Add a Redis-backed SSE pub/sub layer to offload connection state from the Next.js process. Consider horizontal scaling behind a load balancer with sticky sessions.
- Above 100 users: You need a dedicated streaming gateway (like Cloudflare Workers or a Go-based SSE proxy) sitting between the Next.js app and the LLM provider. At this scale, the Next.js server should not be managing raw SSE connections.
Code Snippet: Detecting Stream Cuts
One failure mode we hit: mid-stream connection drops that leave the UI in a half-rendered state. This guard detects incomplete streams:
const { completion, isLoading } = useStreamingCompletion();
const [streamCut, setStreamCut] = useState(false);
useEffect(() => {
if (!isLoading && completion && !completion.endsWith('.')) {
// Heuristic: if stream stops without sentence-ending punctuation
setStreamCut(true);
} else {
setStreamCut(false);
}
}, [isLoading, completion]);
if (streamCut) {
return <div className="stream-warning">Connection interrupted. Retry?</div>;
}
Takeaway
t3code's streaming model works well up to 50 concurrent users without modifications. Beyond that, you're fighting Next.js's single-process event loop bottleneck. The solution isn't more CPU—it's architectural: offload SSE connection management to a stateless edge layer and treat the Next.js app as a business logic coordinator, not a streaming transport.
When benchmarking multi-provider fallback through B-Lost's relay gateway, TTFT dropped by 35ms with zero token truncation across provider switches—a measurable win for production resilience.
Top comments (0)