Overview
The SDK’s performance is determined by several factors: container startup time, command execution overhead, network latency, and resource utilization. This guide covers optimization strategies for each area.
Container lifecycle optimization
Keep containers warm
Containers sleep after inactivity. Keep them warm to avoid cold starts:
const sandbox = getSandbox(env.SANDBOX, 'warm-container', {
keepAlive: true, // Never auto-sleep
sleepAfter: '1h' // Or extend sleep timeout
});
Cold start characteristics:
- Initial startup: 2-5 seconds
- Subsequent operations: <50ms
Use keepAlive when:
- Handling interactive user sessions
- Running latency-sensitive applications
- Maintaining long-running processes
Avoid keepAlive when:
- Running batch jobs (pay for idle time)
- Testing or development (wastes resources)
- Handling sporadic requests
Reuse sandbox instances
// Bad: Creates new container for each request
export default {
async fetch(request: Request, env: Env) {
const sandbox = getSandbox(env.SANDBOX, `user-${Date.now()}`);
await sandbox.exec('command');
return new Response('OK');
}
};
// Good: Reuses containers by user ID
export default {
async fetch(request: Request, env: Env) {
const userId = request.headers.get('X-User-ID');
const sandbox = getSandbox(env.SANDBOX, `user-${userId}`);
await sandbox.exec('command');
return new Response('OK');
}
};
Destroy idle containers
Clean up containers to free resources:
// Destroy container when done
await sandbox.destroy();
// Or use auto-sleep for automatic cleanup
const sandbox = getSandbox(env.SANDBOX, 'auto-cleanup', {
keepAlive: false, // Default
sleepAfter: '10m' // Sleep after 10 minutes of inactivity
});
Command execution optimization
Batch commands together
// Bad: Multiple round-trips (3 HTTP requests)
await sandbox.exec('cd /workspace');
await sandbox.exec('npm install');
await sandbox.exec('npm run build');
// Good: Single command with chaining (1 HTTP request)
await sandbox.exec('cd /workspace && npm install && npm run build');
Latency savings:
- Single command: ~50ms total
- Three commands: ~150ms (3x round-trip)
// Create a session for related work
const session = await sandbox.createSession({
id: 'build-session',
cwd: '/workspace'
});
// Working directory persists
await session.exec('npm install');
await session.exec('npm run build'); // Already in /workspace
await session.exec('npm test');
Parallelize independent operations
// Serialize: 30 seconds total
await sandbox.exec('npm run build-frontend'); // 15s
await sandbox.exec('npm run build-backend'); // 15s
// Parallelize: 15 seconds total
await Promise.all([
sandbox.exec('npm run build-frontend'),
sandbox.exec('npm run build-backend')
]);
Commands in the same session run sequentially due to mutex synchronization. Use different sessions or the default session to enable parallelism.
Stream output for long-running commands
// Bad: Waits for completion, buffers all output in memory
const result = await sandbox.exec('npm install');
console.log(result.stdout); // Only see output after completion
// Good: Stream output in real-time
const process = await sandbox.startProcess({
command: 'npm install'
});
for await (const log of process.logs()) {
console.log(log.output); // See output as it happens
}
File operation optimization
Use streaming for large files
// Bad: Reads entire file into memory (10MB limit)
const content = await sandbox.readFile('/large-file.bin');
// Good: Stream file contents
const result = await sandbox.exec('cat /large-file.bin');
// Or write to bucket mount for direct access
Batch file operations
// Bad: Multiple API calls
for (const file of files) {
await sandbox.writeFile(file.path, file.content);
}
// Good: Create files in one command
const script = files.map(f =>
`echo ${JSON.stringify(f.content)} > ${f.path}`
).join(' && ');
await sandbox.exec(script);
Use bucket mounts for large datasets
// Mount R2 bucket with dataset
await sandbox.mountBucket('my-dataset', '/mnt/data', {
endpoint: 'https://account-id.r2.cloudflarestorage.com',
credentials: { ... },
s3fsOptions: [
'use_cache=/tmp/s3cache',
'stat_cache_expire=3600'
]
});
// Access files directly without copying
await sandbox.exec('python analyze.py /mnt/data/large-dataset.csv');
Network optimization
Use WebSocket transport in Workers/DO
// HTTP: Each operation is a sub-request
for (let i = 0; i < 100; i++) {
await sandbox.exec(`echo ${i}`); // 100 sub-requests
}
// WebSocket: Single connection, multiplexed requests
const sandbox = getSandbox(env.SANDBOX, 'ws-sandbox', {
useWebSocket: true
});
for (let i = 0; i < 100; i++) {
await sandbox.exec(`echo ${i}`); // 1 sub-request (upgrade)
}
When to use WebSocket:
- Running in Durable Object with many operations
- Complex workflows (50+ SDK calls)
- Approaching Workers sub-request limits (1,000 per request)
Reduce round-trips with combined operations
// Bad: 3 round-trips
const exists = await sandbox.exists('/config.json');
if (exists) {
const content = await sandbox.readFile('/config.json');
const parsed = JSON.parse(content);
}
// Good: 1 round-trip with error handling
try {
const content = await sandbox.readFile('/config.json');
const parsed = JSON.parse(content);
} catch (error) {
if (error instanceof FileNotFoundError) {
// Handle missing file
}
}
Cache read-only data
class ConfigCache {
private cache = new Map<string, any>();
async get(sandbox: Sandbox, key: string) {
if (this.cache.has(key)) {
return this.cache.get(key);
}
const content = await sandbox.readFile(`/config/${key}.json`);
const parsed = JSON.parse(content);
this.cache.set(key, parsed);
return parsed;
}
}
Concurrency and parallelism
Understanding the concurrency model
The SDK has multiple concurrency layers:
| Layer | Concurrency Model | Serialization |
|---|
| Durable Object | Event loop (single-threaded) | Storage operations only |
| Container HTTP | Event loop (single-threaded) | Per-session mutex |
| Shell Processes | True parallelism (OS processes) | None |
Maximize parallel execution
// Use different sessions for parallel execution
const sessions = await Promise.all([
sandbox.createSession({ id: 'build-frontend' }),
sandbox.createSession({ id: 'build-backend' }),
sandbox.createSession({ id: 'run-tests' })
]);
// All three run in parallel
await Promise.all([
sessions[0].exec('npm run build'),
sessions[1].exec('npm run build'),
sessions[2].exec('npm test')
]);
Control concurrency
// Limit concurrent operations to avoid resource exhaustion
class ConcurrencyLimiter {
constructor(private limit: number) {}
async map<T, R>(
items: T[],
fn: (item: T) => Promise<R>
): Promise<R[]> {
const results: R[] = [];
for (let i = 0; i < items.length; i += this.limit) {
const batch = items.slice(i, i + this.limit);
const batchResults = await Promise.all(batch.map(fn));
results.push(...batchResults);
}
return results;
}
}
const limiter = new ConcurrencyLimiter(5);
// Process 100 files with max 5 concurrent operations
await limiter.map(files, async (file) => {
return sandbox.writeFile(file.path, file.content);
});
Resource management
Monitor memory usage
// Check container memory usage
const result = await sandbox.exec('free -m');
console.log('Memory:', result.stdout);
// Clean up large files
await sandbox.exec('rm -rf /tmp/*');
Limit process resources
// Limit memory for a command
await sandbox.exec('ulimit -v 1000000 && memory-intensive-command');
// Set CPU limits
await sandbox.exec('nice -n 19 cpu-intensive-command');
Clean up background processes
const process = await sandbox.startProcess({
command: 'npm run dev'
});
try {
// Do work
await doWork();
} finally {
// Always cleanup
await sandbox.killProcess(process.id);
}
Code execution optimization
Preload dependencies
// Create a context once
const context = await sandbox.createContext({
packages: ['numpy', 'pandas', 'matplotlib']
});
// Reuse for multiple executions (dependencies already loaded)
await sandbox.runCode('import numpy as np; ...', {
language: 'python',
contextId: context.id
});
await sandbox.runCode('import pandas as pd; ...', {
language: 'python',
contextId: context.id
});
Use persistent contexts
// Variables persist across executions
const context = await sandbox.createContext();
await sandbox.runCode('x = 42', {
language: 'python',
contextId: context.id
});
await sandbox.runCode('print(x)', { // x is still defined
language: 'python',
contextId: context.id
});
Monitoring and profiling
Measure operation latency
class PerformanceMonitor {
async measure<T>(
name: string,
fn: () => Promise<T>
): Promise<T> {
const start = Date.now();
try {
return await fn();
} finally {
const duration = Date.now() - start;
console.log(`${name}: ${duration}ms`);
}
}
}
const monitor = new PerformanceMonitor();
await monitor.measure('exec', async () => {
return sandbox.exec('npm install');
});
Track container metrics
interface ContainerMetrics {
operations: number;
totalDuration: number;
avgDuration: number;
}
class MetricsCollector {
private metrics: ContainerMetrics = {
operations: 0,
totalDuration: 0,
avgDuration: 0
};
record(duration: number) {
this.metrics.operations++;
this.metrics.totalDuration += duration;
this.metrics.avgDuration =
this.metrics.totalDuration / this.metrics.operations;
}
report() {
return { ...this.metrics };
}
}
Profile command execution
// Time individual commands
const result = await sandbox.exec('time npm install');
console.log('Execution time:', result.stderr); // time output goes to stderr
// Profile with detailed timing
await sandbox.exec('bash -x script.sh'); // Shows each command as it runs
Caching strategies
Cache expensive computations
class ResultCache {
private cache = new Map<string, any>();
async compute(
key: string,
fn: () => Promise<any>,
ttl: number = 3600000 // 1 hour
) {
const cached = this.cache.get(key);
if (cached && cached.expires > Date.now()) {
return cached.value;
}
const value = await fn();
this.cache.set(key, {
value,
expires: Date.now() + ttl
});
return value;
}
}
const cache = new ResultCache();
const result = await cache.compute('build-output', async () => {
return sandbox.exec('npm run build');
});
Use directory backups
// Cache built dependencies
await sandbox.exec('npm install');
const backup = await sandbox.createBackup({
dir: '/workspace/node_modules',
ttl: 86400 // 24 hours
});
// Restore in new sandbox (skip npm install)
const newSandbox = getSandbox(env.SANDBOX, 'new-instance');
await newSandbox.restoreBackup(backup.id);
Best practices summary
Container lifecycle
- ✅ Reuse containers by user/session ID
- ✅ Use
keepAlive: true for interactive sessions
- ✅ Set appropriate
sleepAfter timeouts
- ✅ Destroy containers when completely done
- ❌ Avoid creating new containers per request
Command execution
- ✅ Batch related commands with
&&
- ✅ Parallelize independent operations
- ✅ Use sessions to maintain state
- ✅ Stream output for long-running commands
- ❌ Avoid excessive round-trips
File operations
- ✅ Stream large files
- ✅ Use bucket mounts for datasets
- ✅ Batch file writes
- ✅ Cache read-only data
- ❌ Avoid reading large files into memory
Network
- ✅ Use WebSocket in Workers/DO for many operations
- ✅ Combine operations to reduce round-trips
- ✅ Cache frequently-accessed data
- ❌ Avoid unnecessary file existence checks
Resource management
- ✅ Clean up background processes
- ✅ Monitor memory usage
- ✅ Limit resource-intensive operations
- ✅ Delete unused sessions
- ❌ Avoid memory leaks from unclosed streams
Before deploying to production: