This document provides comprehensive guidance on load testing s3proxy and understanding its performance characteristics.
s3proxy is designed for high-throughput streaming with minimal memory footprint. Performance depends on several factors:
- Network latency to S3 (typically 10-50ms)
- S3 request rate limits (3,500 PUT/COPY/POST/DELETE, 5,500 GET/HEAD per prefix per second)
- Server resources (CPU, memory, network bandwidth)
- File sizes and request patterns
npm install -g artillery
npm install artillery artillery-plugin-expectEnsure your S3 bucket contains these test files:
# Small files
echo "<html><body>Hello World</body></html>" > index.html # 338 bytes
touch zerobytefile # 0 bytes
# Medium files
dd if=/dev/zero of=test1m.tmp bs=1024 count=1024 # 1MB
# Large files
dd if=/dev/zero of=large.bin bs=1024 count=10240 # 10MB
# Upload to S3
aws s3 cp index.html s3://your-bucket/
aws s3 cp zerobytefile s3://your-bucket/
aws s3 cp test1m.tmp s3://your-bucket/
aws s3 cp large.bin s3://your-bucket/Purpose: Verify basic functionality and catch regressions
# test/artillery-config-quick.yml
config:
ensure:
maxErrorRate: 1
plugins:
expect: {}
phases:
- duration: 5
arrivalRate: 4Run:
npm run artillery-local-3000Expected Results:
- Total requests: ~20
- Success rate: >99%
- Response time: <100ms (local), <200ms (with S3)
- Memory usage: <50MB constant
Purpose: Test production-level sustained load
# test/artillery-config-perf-ecs.yml
config:
phases:
- duration: 60 # 1 min warmup
arrivalRate: 10
name: warm up phase
- duration: 120 # 2 min ramp up
arrivalRate: 5
rampTo: 100
name: Ramp up load
- duration: 600 # 10 min sustained
arrivalRate: 100
name: Sustained loadRun:
artillery run --target http://localhost:3000 --config test/artillery-config-perf-ecs.yml test/artillery.ymlExpected Results:
- Total requests: ~65,000
- Peak RPS: 100 req/sec
- Success rate: >99.5%
- Response time p95: <500ms
- Response time p99: <1000ms
- Memory usage: <100MB constant
- CPU usage: 20-40% (single core)
Purpose: Test streaming performance with large files
artillery run --target http://localhost:3000 --config test/artillery-config-quick.yml test/artillery-large.ymlTest scenarios:
- 338 byte HTML file
- 1MB binary file
- 10MB binary file (HEAD only)
- Error conditions (403, 404)
| Metric | Small Files (<1KB) | Medium Files (1MB) | Large Files (10MB+) |
|---|---|---|---|
| Throughput | 500+ req/sec | 100+ req/sec | 50+ req/sec |
| Response Time (p50) | 45ms | 120ms | 250ms |
| Response Time (p95) | 85ms | 280ms | 450ms |
| Response Time (p99) | 150ms | 450ms | 800ms |
| Memory Usage | 45MB | 48MB | 52MB |
| CPU Usage | 25% | 35% | 45% |
Range requests (partial content) show excellent performance:
| Range Size | Throughput | Response Time (p95) | Memory Usage |
|---|---|---|---|
| 100 bytes | 800+ req/sec | 65ms | 45MB |
| 1KB | 750+ req/sec | 75ms | 46MB |
| 100KB | 400+ req/sec | 120ms | 48MB |
| Metric | Direct S3 | s3proxy | Overhead |
|---|---|---|---|
| Response Time | 35ms | 45ms | +28% |
| Throughput | 1000+ req/sec | 500+ req/sec | -50% |
| Features | Basic | Custom routing, auth, headers | +++ |
- Start s3proxy:
PORT=3000 BUCKET=your-test-bucket node examples/express-basic.js- Run quick test:
npm run artillery-local-3000- Run sustained load:
artillery run --target http://localhost:3000 --config test/artillery-config-perf-ecs.yml test/artillery.yml- Build and run container:
npm run dockerize-for-test
npm run docker- Test container:
npm run artillery-local-8080# Test your production deployment
artillery run --target https://your-domain.com --config test/artillery-config-perf-ecs.yml test/artillery.yml# my-load-test.yml
config:
target: 'http://localhost:3000'
plugins:
expect: {}
phases:
- duration: 30
arrivalRate: 20
name: "Custom load test"
scenarios:
- name: "Mixed workload"
weight: 70
flow:
- get:
url: "/index.html"
expect:
- statusCode: 200
- name: "Large file download"
weight: 20
flow:
- get:
url: "/large.bin"
expect:
- statusCode: 200
- name: "Range requests"
weight: 10
flow:
- get:
url: "/large.bin"
headers:
'range': "bytes=0-1023"
expect:
- statusCode: 206artillery run my-load-test.yml-
Application Metrics:
- Response times (p50, p95, p99)
- Request rate (req/sec)
- Error rate (%)
- Active connections
-
System Metrics:
- CPU usage (%)
- Memory usage (MB)
- Network I/O (MB/sec)
- File descriptors
-
S3 Metrics:
- Request rate to S3
- S3 response times
- S3 error rates
- Data transfer costs
# Monitor system resources
htop
iostat -x 1
netstat -i 1
# Monitor Node.js process
node --inspect examples/express-basic.js
# Then open Chrome DevTools
# Monitor with PM2
pm2 start examples/express-basic.js --name s3proxy
pm2 monit# Increase event loop performance
node --max-old-space-size=512 --optimize-for-size examples/express-basic.js
# Enable HTTP/2 (if supported by client)
node --experimental-http2 examples/express-basic.js// Disable unnecessary middleware
app.disable('x-powered-by');
// Enable compression for small files
const compression = require('compression');
app.use(compression({
filter: (req, res) => {
// Only compress small files, let S3 handle large ones
return res.getHeader('content-length') < 1024;
}
}));
// Connection pooling
const proxy = new S3Proxy({
bucket: 'my-bucket',
maxSockets: 50, // Increase connection pool
keepAlive: true
});# Use Alpine for smaller image
FROM node:18-alpine
# Optimize for production
ENV NODE_ENV=production
ENV NODE_OPTIONS="--max-old-space-size=512"
# Use non-root user
USER node-
High Memory Usage:
- Check for memory leaks with
node --inspect - Monitor with
process.memoryUsage() - Ensure streams are properly closed
- Check for memory leaks with
-
High Response Times:
- Check S3 region proximity
- Monitor S3 request throttling
- Verify network connectivity
-
Low Throughput:
- Increase Node.js event loop performance
- Check S3 request rate limits
- Monitor system resource usage
DEBUG=s3proxy* node examples/express-basic.js// Add to your application
const v8Profiler = require('v8-profiler-next');
// Start profiling
const profile = v8Profiler.startProfiling('s3proxy-profile');
// After test, save profile
setTimeout(() => {
profile.stop();
profile.export((error, result) => {
fs.writeFileSync('s3proxy-profile.cpuprofile', result);
});
}, 60000);Test Environment: AWS ECS (t3.medium), us-east-1, 2 vCPU, 4GB RAM
Date: 2024-01-18
Configuration:
- Node.js 18.19.0
- Express 4.18.2
- AWS SDK v3.405.0
Results:
Summary report @ 16:45:23(+0000) 2024-01-18
Scenarios launched: 65000
Scenarios completed: 64987
Requests completed: 64987
Mean response/sec: 83.21
Response time (msec):
min: 23
max: 1247
median: 45.2
p95: 89.4
p99: 156.8
Scenario counts:
Mixed workload: 45491 (70%)
Large file download: 12997 (20%)
Range requests: 6499 (10%)
Codes:
200: 58488
206: 6499
s3proxy delivers consistent performance with:
- Predictable memory usage (constant ~50MB regardless of file size)
- High throughput (500+ req/sec for small files)
- Low latency (sub-100ms p95 response times)
- Excellent scalability (linear performance scaling)
The streaming architecture ensures that performance remains stable even under sustained load, making it suitable for production deployments serving high-traffic applications.