949 lines
23 KiB
Markdown
949 lines
23 KiB
Markdown
# 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`, or `all`
|
|
- `endpoints` (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:
|
|
```bash
|
|
# 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**:
|
|
```bash
|
|
# 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**:
|
|
```bash
|
|
# 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**:
|
|
```javascript
|
|
// 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**:
|
|
```javascript
|
|
// 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)**:
|
|
```javascript
|
|
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)**:
|
|
```javascript
|
|
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)**:
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
// 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**
|
|
|
|
```javascript
|
|
// 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**
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```markdown
|
|
# 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**:
|
|
```javascript
|
|
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**:
|
|
```javascript
|
|
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**:
|
|
```javascript
|
|
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**:
|
|
```javascript
|
|
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**:
|
|
```javascript
|
|
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)**:
|
|
```javascript
|
|
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)**:
|
|
```javascript
|
|
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**:
|
|
|
|
1. **Response Times**:
|
|
```javascript
|
|
// 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();
|
|
});
|
|
```
|
|
|
|
2. **Cache Hit Rates**:
|
|
```javascript
|
|
// Track Redis cache effectiveness
|
|
const cacheStats = {
|
|
hits: 0,
|
|
misses: 0,
|
|
hitRate: () => cacheStats.hits / (cacheStats.hits + cacheStats.misses)
|
|
};
|
|
```
|
|
|
|
3. **Event Loop Lag**:
|
|
```javascript
|
|
const { monitorEventLoopDelay } = require('perf_hooks');
|
|
const h = monitorEventLoopDelay({ resolution: 20 });
|
|
h.enable();
|
|
|
|
setInterval(() => {
|
|
console.log('Event loop delay:', h.mean / 1000000, 'ms');
|
|
}, 60000);
|
|
```
|
|
|
|
4. **Memory Leaks**:
|
|
```javascript
|
|
// 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
|
|
|
|
1. **Implement** worker threads for CPU-intensive report generation
|
|
2. **Consider** horizontal scaling with load balancer
|
|
3. **Evaluate** GraphQL migration for flexible data fetching
|
|
4. **Monitor** cache invalidation patterns for optimization
|
|
5. **Review** remaining slow endpoints for optimization opportunities
|