Scaling Guide
Performance tips and scaling strategies for Verani applications.
Architecture Choice Matters
The biggest scaling decision is your architecture choice:
Per-Connection Architecture (Recommended)
Use createConnectionHandler() + createRoomHandler() for:
- Unlimited horizontal scaling - Each user has their own DO
- No single bottleneck - No single DO handles all connections
- Cost-efficient - Idle connections hibernate independently
- Better fault isolation - One user's DO crash doesn't affect others
import { defineConnection, createConnectionHandler, createRoomHandler } from "verani";
// Each user gets their own DO
const UserConnection = createConnectionHandler(defineConnection({
onConnect(ctx) {
ctx.actor.joinRoom("chat");
}
}));
// Rooms coordinate membership and broadcast
const ChatRoom = createRoomHandler({ name: "ChatRoom" });
// Worker routes to per-user DO
export default {
async fetch(request) {
const userId = extractUserId(request);
const stub = UserConnection.get(userId); // User-specific DO
return stub.fetch(request);
}
};
Legacy Global Router (Not Recommended for Scale)
The legacy defineRoom() + createActorHandler() pattern puts all connections in one DO:
// LEGACY: All connections in one DO - bottleneck!
const stub = ChatRoom.get(""); // Same DO for everyone
Limits:
- ~1,000 WebSocket connections per Actor
- ~10,000 messages/second per Actor
- Single-threaded bottleneck
Performance Tips
1. Use Per-Connection Architecture
The single biggest performance improvement. See above.
2. Batch Messages
Send multiple updates in one message:
// Instead of multiple sends
const updates = [];
updates.push(update1, update2, update3);
// Send as a single batched message via room
await ctx.emit.toRoom("chat").emit("batch.update", { updates });
3. Enable Hibernation
Verani handles this automatically, but make sure you're not keeping DOs awake unnecessarily:
- Don't use
setInterval()in the Actor - Don't keep long-running promises
- Let the Actor sleep when idle
4. Optimize Persisted State
When using state persistence:
- Only persist what you need: Don't persist frequently-changing data like typing indicators
- Use shallow mode: Default shallow tracking is faster than deep proxying
- Batch updates: Multiple state changes trigger multiple persistence operations
// Good: Only persist meaningful state
state: {
messageCount: 0, // Persist this
settings: { maxUsers: 100 } // Persist this
// Don't persist: typing indicators, cursor positions, etc.
},
persistedKeys: ["messageCount", "settings"],
5. Use Rooms for Selective Broadcasting
RoomDOs only broadcast to members:
// Join specific rooms instead of one global room
await ctx.actor.joinRoom("project-123");
// Broadcast only reaches room members
await ctx.emit.toRoom("project-123").emit("update", data);
Scaling Characteristics
Per-Connection Architecture
| Metric | Capacity |
|---|---|
| Users | Unlimited (1 DO per user) |
| Messages/sec | Scales with users |
| Memory | Distributed across DOs |
| Hibernation | Per-user (efficient) |
Legacy Architecture
| Metric | Capacity |
|---|---|
| Connections/Actor | ~1,000 |
| Messages/sec | ~10,000 per Actor |
| Memory | All in one DO |
| Hibernation | All-or-nothing |
Horizontal Scaling Example
1 million users with per-connection architecture:
1,000,000 Users
↓
1,000,000 ConnectionDOs (1 per user)
↓
N RoomDOs (1 per room/channel)
↓
Automatic global distribution
Each ConnectionDO:
- Handles 1 WebSocket
- Hibernates independently when idle
- Costs nothing when sleeping
Cost Estimation
Cloudflare Workers pricing (as of 2024):
Free Tier:
- 100,000 requests/day
- 10ms CPU time per request
Paid Plan ($5/month):
- 10 million requests/month included
- $0.50 per million additional requests
- Durable Objects: $0.15 per million requests
Example Costs:
| Users | Messages/sec | Monthly Cost |
|---|---|---|
| 100 | 10 | Free |
| 1,000 | 100 | ~$5 |
| 10,000 | 1,000 | ~$20 |
| 100,000 | 10,000 | ~$100 |
Estimates only. Actual costs depend on usage patterns.
Related Documentation
- Deployment Guide - Deployment steps
- Monitoring Guide - Logs and metrics
- Configuration Guide - Actor routing strategies
- Persistence Concepts - State persistence and performance