23 KiB
Backend Optimization Operation
You are executing the backend operation to optimize backend API performance, algorithms, caching, and concurrency handling.
Parameters
Received: $ARGUMENTS (after removing 'backend' operation name)
Expected format: target:"api|algorithms|caching|concurrency|all" [endpoints:"endpoint-list"] [load_profile:"low|medium|high"] [priority:"low|medium|high|critical"]
Parameter definitions:
target(required): What to optimize -api,algorithms,caching,concurrency, orallendpoints(optional): Specific API endpoints to optimize (comma-separated, e.g., "/api/users,/api/posts")load_profile(optional): Expected load level -low,medium,high(default: medium)priority(optional): Optimization priority -low,medium,high,critical(default: high)
Workflow
1. Identify Backend Framework and Runtime
Detect backend technology:
# Check package.json for framework
grep -E "express|fastify|koa|nestjs|hapi" package.json 2>/dev/null
# Check for runtime
node --version 2>/dev/null || echo "No Node.js"
python --version 2>/dev/null || echo "No Python"
go version 2>/dev/null || echo "No Go"
ruby --version 2>/dev/null || echo "No Ruby"
# Check for web framework files
ls -la server.js app.js main.py app.py main.go 2>/dev/null
2. Profile API Performance
Node.js Profiling:
# Start application with profiling
node --prof app.js
# Or use clinic.js for comprehensive profiling
npx clinic doctor -- node app.js
# Then make requests to your API
# Process the profile
node --prof-process isolate-*.log > profile.txt
# Use clinic.js flame graph
npx clinic flame -- node app.js
API Response Time Analysis:
# Test endpoint response times
curl -w "@curl-format.txt" -o /dev/null -s "http://localhost:3000/api/users"
# curl-format.txt content:
# time_namelookup: %{time_namelookup}\n
# time_connect: %{time_connect}\n
# time_appconnect: %{time_appconnect}\n
# time_pretransfer: %{time_pretransfer}\n
# time_redirect: %{time_redirect}\n
# time_starttransfer: %{time_starttransfer}\n
# time_total: %{time_total}\n
# Load test with k6
npx k6 run --vus 50 --duration 30s loadtest.js
APM Tools (if available):
- New Relic: Check transaction traces
- DataDog: Review APM dashboard
- Application Insights: Analyze dependencies
3. API Optimization
3.1. Fix N+1 Query Problems
Problem Detection:
// BEFORE (N+1 problem)
app.get('/api/users', async (req, res) => {
const users = await User.findAll(); // 1 query
for (const user of users) {
// N additional queries (1 per user)
user.posts = await Post.findAll({ where: { userId: user.id } });
}
res.json(users);
});
// Total: 1 + N queries for N users
Solution - Eager Loading:
// AFTER (eager loading)
app.get('/api/users', async (req, res) => {
const users = await User.findAll({
include: [{ model: Post, as: 'posts' }] // Single query with JOIN
});
res.json(users);
});
// Total: 1 query
// Performance improvement: ~95% faster for 100 users
Solution - DataLoader (for GraphQL or complex cases):
const DataLoader = require('dataloader');
// Batch load posts by user IDs
const postLoader = new DataLoader(async (userIds) => {
const posts = await Post.findAll({
where: { userId: { $in: userIds } }
});
// Group posts by userId
const postsByUserId = {};
posts.forEach(post => {
if (!postsByUserId[post.userId]) {
postsByUserId[post.userId] = [];
}
postsByUserId[post.userId].push(post);
});
// Return posts in same order as userIds
return userIds.map(id => postsByUserId[id] || []);
});
// Usage
app.get('/api/users', async (req, res) => {
const users = await User.findAll();
// Load posts in batch
await Promise.all(
users.map(async (user) => {
user.posts = await postLoader.load(user.id);
})
);
res.json(users);
});
// Total: 2 queries (users + batched posts)
3.2. Implement Response Caching
In-Memory Caching (Simple):
const cache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
function cacheMiddleware(key, ttl = CACHE_TTL) {
return (req, res, next) => {
const cacheKey = typeof key === 'function' ? key(req) : key;
const cached = cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < ttl) {
return res.json(cached.data);
}
// Override res.json to cache the response
const originalJson = res.json.bind(res);
res.json = (data) => {
cache.set(cacheKey, { data, timestamp: Date.now() });
return originalJson(data);
};
next();
};
}
// Usage
app.get('/api/users',
cacheMiddleware(req => `users:${req.query.page || 1}`),
async (req, res) => {
const users = await User.findAll();
res.json(users);
}
);
Redis Caching (Production):
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);
async function cacheMiddleware(keyFn, ttl = 300) {
return async (req, res, next) => {
const cacheKey = keyFn(req);
try {
const cached = await redis.get(cacheKey);
if (cached) {
return res.json(JSON.parse(cached));
}
const originalJson = res.json.bind(res);
res.json = async (data) => {
await redis.setex(cacheKey, ttl, JSON.stringify(data));
return originalJson(data);
};
next();
} catch (error) {
console.error('Cache error:', error);
next(); // Continue without cache on error
}
};
}
// Usage with cache invalidation
app.get('/api/posts/:id', cacheMiddleware(req => `post:${req.params.id}`, 600), async (req, res) => {
const post = await Post.findByPk(req.params.id);
res.json(post);
});
app.put('/api/posts/:id', async (req, res) => {
const post = await Post.update(req.body, { where: { id: req.params.id } });
// Invalidate cache
await redis.del(`post:${req.params.id}`);
res.json(post);
});
3.3. Add Request Compression
const compression = require('compression');
app.use(compression({
// Compress responses > 1KB
threshold: 1024,
// Compression level (0-9, higher = better compression but slower)
level: 6,
// Only compress certain content types
filter: (req, res) => {
if (req.headers['x-no-compression']) {
return false;
}
return compression.filter(req, res);
}
}));
// Typical compression results:
// - JSON responses: 70-80% size reduction
// - Text responses: 60-70% size reduction
// - Already compressed (images, video): minimal effect
3.4. Implement Rate Limiting
const rateLimit = require('express-rate-limit');
// General API rate limit
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per window
message: 'Too many requests from this IP, please try again later',
standardHeaders: true,
legacyHeaders: false,
});
// Stricter limit for expensive endpoints
const strictLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10,
message: 'Too many requests for this resource'
});
app.use('/api/', apiLimiter);
app.use('/api/search', strictLimiter);
app.use('/api/export', strictLimiter);
3.5. Optimize JSON Serialization
// BEFORE (default JSON.stringify)
app.get('/api/users', async (req, res) => {
const users = await User.findAll();
res.json(users); // Uses JSON.stringify
});
// AFTER (fast-json-stringify for known schemas)
const fastJson = require('fast-json-stringify');
const userSchema = fastJson({
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
email: { type: 'string' },
createdAt: { type: 'string', format: 'date-time' }
}
}
});
app.get('/api/users', async (req, res) => {
const users = await User.findAll();
res.set('Content-Type', 'application/json');
res.send(userSchema(users)); // 2-3x faster serialization
});
4. Algorithm Optimization
4.1. Replace Inefficient Algorithms
Example: Array Search Optimization
// BEFORE (O(n) lookup for each iteration = O(n²))
function enrichUsers(users, userData) {
return users.map(user => ({
...user,
data: userData.find(d => d.userId === user.id) // O(n) search
}));
}
// Time complexity: O(n²) for n users
// AFTER (O(n) with Map)
function enrichUsers(users, userData) {
const dataMap = new Map(
userData.map(d => [d.userId, d])
); // O(n) to build map
return users.map(user => ({
...user,
data: dataMap.get(user.id) // O(1) lookup
}));
}
// Time complexity: O(n)
// Performance improvement: 100x for 1000 users
Example: Sorting Optimization
// BEFORE (multiple array iterations)
function getTopUsers(users) {
return users
.filter(u => u.isActive) // O(n)
.map(u => ({ ...u, score: calculateScore(u) })) // O(n)
.sort((a, b) => b.score - a.score) // O(n log n)
.slice(0, 10); // O(1)
}
// Total: O(n log n)
// AFTER (single pass + partial sort)
function getTopUsers(users) {
const scored = [];
for (const user of users) {
if (!user.isActive) continue;
const score = calculateScore(user);
scored.push({ ...user, score });
// Keep only top 10 (partial sort)
if (scored.length > 10) {
scored.sort((a, b) => b.score - a.score);
scored.length = 10;
}
}
return scored.sort((a, b) => b.score - a.score);
}
// Total: O(n) average case
// Performance improvement: 10x for 10,000 users
4.2. Memoization for Expensive Computations
// Memoization decorator
function memoize(fn, keyFn = (...args) => JSON.stringify(args)) {
const cache = new Map();
return function(...args) {
const key = keyFn(...args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// BEFORE (recalculates every time)
function calculateUserScore(user) {
// Expensive calculation
let score = 0;
score += user.posts * 10;
score += user.comments * 5;
score += user.likes * 2;
score += complexAlgorithm(user.activity);
return score;
}
// AFTER (memoized)
const calculateUserScore = memoize(
(user) => {
let score = 0;
score += user.posts * 10;
score += user.comments * 5;
score += user.likes * 2;
score += complexAlgorithm(user.activity);
return score;
},
(user) => user.id // Cache key
);
// Subsequent calls with same user.id return cached result
5. Concurrency Optimization
5.1. Async/Await Parallelization
// BEFORE (sequential - slow)
async function getUserData(userId) {
const user = await User.findByPk(userId); // 50ms
const posts = await Post.findAll({ where: { userId } }); // 80ms
const comments = await Comment.findAll({ where: { userId } }); // 60ms
return { user, posts, comments };
}
// Total time: 50 + 80 + 60 = 190ms
// AFTER (parallel - fast)
async function getUserData(userId) {
const [user, posts, comments] = await Promise.all([
User.findByPk(userId), // 50ms
Post.findAll({ where: { userId } }), // 80ms
Comment.findAll({ where: { userId } }) // 60ms
]);
return { user, posts, comments };
}
// Total time: max(50, 80, 60) = 80ms
// Performance improvement: 2.4x faster
5.2. Worker Threads for CPU-Intensive Tasks
const { Worker } = require('worker_threads');
// cpu-intensive-worker.js
const { parentPort, workerData } = require('worker_threads');
function cpuIntensiveTask(data) {
// Complex computation
let result = 0;
for (let i = 0; i < data.iterations; i++) {
result += Math.sqrt(i) * Math.sin(i);
}
return result;
}
parentPort.postMessage(cpuIntensiveTask(workerData));
// Main application
function runWorker(workerData) {
return new Promise((resolve, reject) => {
const worker = new Worker('./cpu-intensive-worker.js', { workerData });
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`Worker stopped with exit code ${code}`));
}
});
});
}
// BEFORE (blocks event loop)
app.post('/api/process', async (req, res) => {
const result = cpuIntensiveTask(req.body); // Blocks for 500ms
res.json({ result });
});
// AFTER (offloaded to worker)
app.post('/api/process', async (req, res) => {
const result = await runWorker(req.body); // Non-blocking
res.json({ result });
});
// Main thread remains responsive
5.3. Request Batching and Debouncing
// Batch multiple requests into single database query
class BatchLoader {
constructor(loadFn, delay = 10) {
this.loadFn = loadFn;
this.delay = delay;
this.queue = [];
this.timer = null;
}
load(key) {
return new Promise((resolve, reject) => {
this.queue.push({ key, resolve, reject });
if (!this.timer) {
this.timer = setTimeout(() => this.flush(), this.delay);
}
});
}
async flush() {
const queue = this.queue;
this.queue = [];
this.timer = null;
try {
const keys = queue.map(item => item.key);
const results = await this.loadFn(keys);
queue.forEach((item, index) => {
item.resolve(results[index]);
});
} catch (error) {
queue.forEach(item => item.reject(error));
}
}
}
// Usage
const userLoader = new BatchLoader(async (userIds) => {
// Single query for all user IDs
const users = await User.findAll({
where: { id: { $in: userIds } }
});
// Return in same order as requested
return userIds.map(id => users.find(u => u.id === id));
});
// BEFORE (N separate queries)
app.get('/api/feed', async (req, res) => {
const posts = await Post.findAll({ limit: 50 });
for (const post of posts) {
post.author = await User.findByPk(post.userId); // N queries
}
res.json(posts);
});
// AFTER (batched into 1 query)
app.get('/api/feed', async (req, res) => {
const posts = await Post.findAll({ limit: 50 });
await Promise.all(
posts.map(async (post) => {
post.author = await userLoader.load(post.userId); // Batched
})
);
res.json(posts);
});
// Improvement: 50 queries → 2 queries (posts + batched users)
6. Response Streaming for Large Datasets
const { Transform } = require('stream');
// BEFORE (loads entire dataset into memory)
app.get('/api/export/users', async (req, res) => {
const users = await User.findAll(); // Loads all users into memory
res.json(users); // May cause OOM for large datasets
});
// AFTER (streams data)
app.get('/api/export/users', async (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.write('[');
let first = true;
const stream = User.findAll({ stream: true }); // Database stream
for await (const user of stream) {
if (!first) res.write(',');
res.write(JSON.stringify(user));
first = false;
}
res.write(']');
res.end();
});
// Memory usage: O(1) instead of O(n)
// Can handle millions of records
7. Optimize Middleware Stack
// BEFORE (all middleware runs for all routes)
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(session({ /* config */ }));
app.use(passport.initialize());
app.use(passport.session());
app.use(cors());
app.get('/api/public/health', (req, res) => {
res.json({ status: 'ok' });
// Still parsed body, cookies, session unnecessarily
});
// AFTER (selective middleware)
const publicRouter = express.Router();
publicRouter.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
const apiRouter = express.Router();
apiRouter.use(bodyParser.json());
apiRouter.use(authenticate);
apiRouter.get('/users', async (req, res) => { /* ... */ });
app.use('/api/public', publicRouter);
app.use('/api', apiRouter);
// Health check endpoint has minimal overhead
8. Database Connection Management
// BEFORE (creates new connection per request)
app.get('/api/users', async (req, res) => {
const client = await pool.connect(); // Slow
const result = await client.query('SELECT * FROM users');
client.release();
res.json(result.rows);
});
// AFTER (uses connection pool efficiently)
const { Pool } = require('pg');
const pool = new Pool({
max: 20,
min: 5,
idleTimeoutMillis: 30000
});
app.get('/api/users', async (req, res) => {
const result = await pool.query('SELECT * FROM users'); // Reuses connection
res.json(result.rows);
});
// Connection acquisition: 50ms → 0.5ms
Output Format
# Backend Optimization Report: [Context]
**Optimization Date**: [Date]
**Backend**: [Framework and version]
**Runtime**: [Node.js/Python/Go version]
**Load Profile**: [low/medium/high]
## Executive Summary
[2-3 paragraphs summarizing findings and optimizations]
## Baseline Metrics
### API Performance
| Endpoint | p50 | p95 | p99 | RPS | Error Rate |
|----------|-----|-----|-----|-----|------------|
| GET /api/users | 120ms | 450ms | 980ms | 45 | 0.5% |
| POST /api/posts | 230ms | 780ms | 1800ms | 20 | 1.2% |
| GET /api/feed | 850ms | 2100ms | 4500ms | 12 | 2.3% |
### Resource Utilization
- **CPU**: 68% average
- **Memory**: 1.2GB / 2GB (60%)
- **Event Loop Lag**: 45ms average
## Optimizations Implemented
### 1. Fixed N+1 Query Problem in /api/feed
**Before**:
```javascript
const posts = await Post.findAll();
for (const post of posts) {
post.author = await User.findByPk(post.userId); // N queries
}
// Result: 1 + 50 = 51 queries for 50 posts
After:
const posts = await Post.findAll({
include: [{ model: User, as: 'author' }]
});
// Result: 1 query with JOIN
Impact:
- Before: 850ms p50 response time
- After: 95ms p50 response time
- Improvement: 88.8% faster
2. Implemented Redis Caching
Implementation:
const cacheMiddleware = (key, ttl) => async (req, res, next) => {
const cached = await redis.get(key(req));
if (cached) return res.json(JSON.parse(cached));
const originalJson = res.json.bind(res);
res.json = async (data) => {
await redis.setex(key(req), ttl, JSON.stringify(data));
return originalJson(data);
};
next();
};
app.get('/api/users',
cacheMiddleware(req => `users:${req.query.page}`, 300),
handler
);
Impact:
- Cache Hit Rate: 82% (after 24 hours)
- Cached Response Time: 5ms
- Database Load Reduction: 82%
3. Parallelized Independent Queries
Before:
const user = await User.findByPk(userId); // 50ms
const posts = await Post.findAll({ where: { userId } }); // 80ms
const comments = await Comment.findAll({ where: { userId } }); // 60ms
// Total: 190ms
After:
const [user, posts, comments] = await Promise.all([
User.findByPk(userId),
Post.findAll({ where: { userId } }),
Comment.findAll({ where: { userId } })
]);
// Total: 80ms (max of parallel operations)
Impact: 57.9% faster (190ms → 80ms)
4. Added Response Compression
Implementation:
app.use(compression({ level: 6, threshold: 1024 }));
Impact:
- JSON Response Size: 450KB → 95KB (78.9% reduction)
- Network Transfer Time: 180ms → 38ms (on 20Mbps connection)
- Bandwidth Savings: 79%
5. Optimized Algorithm Complexity
Before (O(n²) lookup):
users.map(user => ({
...user,
data: userData.find(d => d.userId === user.id) // O(n) per iteration
}));
// Time: 2,400ms for 1,000 users
After (O(n) with Map):
const dataMap = new Map(userData.map(d => [d.userId, d]));
users.map(user => ({
...user,
data: dataMap.get(user.id) // O(1) lookup
}));
// Time: 12ms for 1,000 users
Impact: 99.5% faster (2,400ms → 12ms)
Results Summary
Overall API Performance
| Metric | Before | After | Improvement |
|---|---|---|---|
| Avg Response Time (p50) | 285ms | 65ms | 77.2% faster |
| p95 Response Time | 1,100ms | 180ms | 83.6% faster |
| p99 Response Time | 3,200ms | 450ms | 85.9% faster |
| Throughput | 85 RPS | 320 RPS | 276% increase |
| Error Rate | 1.5% | 0.1% | 93.3% reduction |
Endpoint-Specific Improvements
| Endpoint | Before (p50) | After (p50) | Improvement |
|---|---|---|---|
| GET /api/users | 120ms | 8ms | 93.3% |
| GET /api/feed | 850ms | 95ms | 88.8% |
| POST /api/posts | 230ms | 65ms | 71.7% |
Resource Utilization
| Metric | Before | After | Change |
|---|---|---|---|
| CPU Usage | 68% | 32% | -53% |
| Memory Usage | 60% | 45% | -25% |
| Event Loop Lag | 45ms | 8ms | -82.2% |
Load Testing Results
Before Optimization:
Requests: 5,000
Duration: 58.8s
RPS: 85
p95: 1,100ms
p99: 3,200ms
Errors: 75 (1.5%)
After Optimization:
Requests: 5,000
Duration: 15.6s
RPS: 320
p95: 180ms
p99: 450ms
Errors: 5 (0.1%)
Improvement: 276% more throughput, 83.6% faster p95
Trade-offs and Considerations
Caching Strategy:
- Benefit: 82% reduction in database load
- Trade-off: Cache invalidation complexity, eventual consistency
- Mitigation: TTL-based expiration (5 minutes) acceptable for this use case
Response Compression:
- Benefit: 79% bandwidth savings
- Trade-off: ~5ms CPU overhead per request
- Conclusion: Worth it for responses > 1KB
Algorithm Optimization:
- Benefit: 99.5% faster for large datasets
- Trade-off: Increased memory usage (Map storage)
- Conclusion: Negligible memory increase, massive performance gain
Monitoring Recommendations
Key Metrics to Track:
-
Response Times:
// Use middleware to track app.use((req, res, next) => { const start = Date.now(); res.on('finish', () => { const duration = Date.now() - start; metrics.histogram('response_time', duration, { endpoint: req.path, method: req.method, status: res.statusCode }); }); next(); }); -
Cache Hit Rates:
// Track Redis cache effectiveness const cacheStats = { hits: 0, misses: 0, hitRate: () => cacheStats.hits / (cacheStats.hits + cacheStats.misses) }; -
Event Loop Lag:
const { monitorEventLoopDelay } = require('perf_hooks'); const h = monitorEventLoopDelay({ resolution: 20 }); h.enable(); setInterval(() => { console.log('Event loop delay:', h.mean / 1000000, 'ms'); }, 60000); -
Memory Leaks:
// Track memory usage trends setInterval(() => { const usage = process.memoryUsage(); metrics.gauge('memory.heap_used', usage.heapUsed); metrics.gauge('memory.heap_total', usage.heapTotal); }, 60000);
Alerts to Configure
- Response time p95 > 500ms
- Error rate > 1%
- Cache hit rate < 70%
- Event loop lag > 50ms
- Memory usage > 80%
Next Steps
- Implement worker threads for CPU-intensive report generation
- Consider horizontal scaling with load balancer
- Evaluate GraphQL migration for flexible data fetching
- Monitor cache invalidation patterns for optimization
- Review remaining slow endpoints for optimization opportunities