阿里云主机折上折
  • 微信号
Current Site:Index > Performance optimization and caching strategies

Performance optimization and caching strategies

Author:Chuan Chen 阅读数:23699人阅读 分类: Node.js

Performance Optimization and Caching Strategies

Express, as a popular Node.js framework, sees its performance optimization and caching strategies directly impact application response speed and user experience. Proper caching mechanisms can significantly reduce database queries and lower server load, while performance optimization ensures stable operation under high concurrency.

Understanding Express Middleware Performance

The execution order of Express middleware directly affects performance. Improper middleware ordering may lead to unnecessary computations. For example, static file middleware should be placed before route handling:

const express = require('express');
const app = express();

// Correct order: handle static resources first
app.use(express.static('public'));

// Then handle dynamic routes
app.get('/api/data', (req, res) => {
  // Data processing logic
});

Avoid synchronous operations in middleware, as they block the event loop. Use asynchronous methods instead:

// Avoid writing like this
app.use((req, res, next) => {
  const data = fs.readFileSync('large-file.json');
  next();
});

// Should write like this
app.use(async (req, res, next) => {
  const data = await fs.promises.readFile('large-file.json');
  next();
});

Route Optimization Techniques

Complex route structures may cause performance degradation. Use route parameters instead of multiple independent routes:

// Not recommended
app.get('/users/1', getUser1);
app.get('/users/2', getUser2);

// Recommended
app.get('/users/:id', getUser);

For frequently accessed routes, consider precompiling regular expressions:

const pattern = new RegExp('^/products/([0-9]+)$');
app.get(pattern, (req, res) => {
  const productId = req.params[0];
  // Processing logic
});

Cache Strategy Implementation

In-Memory Caching

Node.js process memory is suitable for short-term caching. Use objects or Maps for simple caching:

const cache = new Map();

app.get('/expensive-route', (req, res) => {
  const cacheKey = req.originalUrl;
  if (cache.has(cacheKey)) {
    return res.json(cache.get(cacheKey));
  }

  // Simulate expensive computation
  const result = computeExpensiveResult();
  cache.set(cacheKey, result);
  
  // Set cache expiration
  setTimeout(() => cache.delete(cacheKey), 60000);
  
  res.json(result);
});

Redis Caching

For distributed systems, Redis is a better choice:

const redis = require('redis');
const client = redis.createClient();

app.get('/api/products/:id', async (req, res) => {
  const { id } = req.params;
  const cacheKey = `product:${id}`;
  
  try {
    const cachedData = await client.get(cacheKey);
    if (cachedData) {
      return res.json(JSON.parse(cachedData));
    }

    const product = await db.products.findOne({ where: { id } });
    await client.setEx(cacheKey, 3600, JSON.stringify(product));
    res.json(product);
  } catch (error) {
    res.status(500).send('Server Error');
  }
});

Response Cache Header Settings

Properly setting HTTP cache headers can reduce duplicate requests:

app.get('/static-data', (req, res) => {
  const data = getStaticData();
  
  // Set strong caching
  res.set('Cache-Control', 'public, max-age=3600');
  
  // Or set negotiation caching
  res.set('ETag', generateETag(data));
  res.json(data);
});

Handling conditional requests:

app.get('/conditional', (req, res) => {
  const data = getData();
  const etag = generateETag(data);

  if (req.headers['if-none-match'] === etag) {
    return res.status(304).end();
  }

  res.set('ETag', etag);
  res.json(data);
});

Database Query Optimization

Reducing database query frequency is key to performance. Use batch queries instead of looping through single queries:

// Not recommended
app.get('/users', async (req, res) => {
  const userIds = [1, 2, 3];
  const users = [];
  for (const id of userIds) {
    users.push(await User.findById(id));
  }
  res.json(users);
});

// Recommended
app.get('/users', async (req, res) => {
  const userIds = [1, 2, 3];
  const users = await User.findAll({ where: { id: userIds } });
  res.json(users);
});

Use projection to query only necessary fields:

app.get('/users/light', async (req, res) => {
  // Only query name and email fields
  const users = await User.find({}, 'name email');
  res.json(users);
});

Cluster Mode and Process Management

Leverage multi-core CPUs to improve performance:

const cluster = require('cluster');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
} else {
  const app = express();
  // App configuration
  app.listen(3000);
}

Use process managers like PM2:

pm2 start app.js -i max

Compressing Response Data

Enable Gzip compression to reduce transfer size:

const compression = require('compression');
app.use(compression());

Disable compression for specific routes:

app.use(compression({
  filter: (req, res) => {
    if (req.headers['x-no-compression']) {
      return false;
    }
    return compression.filter(req, res);
  }
}));

Avoiding Memory Leaks

Common memory leak scenarios include storing request-related data in global variables:

// Bad example
const userSessions = {};

app.get('/leak', (req, res) => {
  userSessions[req.ip] = Date.now();
  res.send('Data stored');
});

The correct approach is to use WeakMap or set expiration:

const sessionCache = new Map();

setInterval(() => {
  const now = Date.now();
  for (const [key, value] of sessionCache) {
    if (now - value.timestamp > 3600000) {
      sessionCache.delete(key);
    }
  }
}, 60000);

Real-time Monitoring and Performance Analysis

Use performance monitoring tools:

const promBundle = require('express-prom-bundle');
const metricsMiddleware = promBundle({
  includeMethod: true,
  includePath: true
});

app.use(metricsMiddleware);

Analyze slow requests:

app.use((req, res, next) => {
  const start = process.hrtime();
  
  res.on('finish', () => {
    const duration = process.hrtime(start);
    const milliseconds = duration[0] * 1000 + duration[1] / 1e6;
    if (milliseconds > 500) {
      console.warn(`Slow request: ${req.method} ${req.url} took ${milliseconds}ms`);
    }
  });
  
  next();
});

Frontend Resource Optimization

Although primarily a backend topic, Express also affects frontend resource delivery:

app.use('/static', express.static('public', {
  maxAge: '1y',
  immutable: true,
  setHeaders: (res, path) => {
    if (path.endsWith('.js')) {
      res.set('Content-Encoding', 'br');
    }
  }
}));

Enable HTTP/2 push:

const spdy = require('spdy');
const express = require('express');
const app = express();

app.get('/', (req, res) => {
  const stream = res.push('/main.css', {
    request: { accept: 'text/css' },
    response: { 'content-type': 'text/css' }
  });
  stream.end('body { color: red; }');
  
  res.send('<link rel="stylesheet" href="/main.css">');
});

spdy.createServer(options, app).listen(443);

Load Testing and Benchmarking

Use autocannon for stress testing:

const autocannon = require('autocannon');

autocannon({
  url: 'http://localhost:3000',
  connections: 100,
  duration: 10,
  requests: [
    { method: 'GET', path: '/api/data' }
  ]
}, console.log);

Compare performance before and after optimization:

# Before optimization
Requests: 2000, mean latency: 450ms

# After optimization
Requests: 2000, mean latency: 120ms

Micro-Optimization Techniques

Small tricks can also bring improvements:

// Use res.send() instead of res.json() when data is already a string
app.get('/fast', (req, res) => {
  const data = JSON.stringify({ fast: true });
  res.send(data); // Faster than res.json()
});

// Pre-set common headers
app.use((req, res, next) => {
  res.set('X-Powered-By', 'Express');
  next();
});

Avoid using console.log in hot paths:

// Disable in production
if (process.env.NODE_ENV === 'production') {
  console.log = () => {};
}

Cache Invalidation Strategies

Common cache invalidation patterns:

// Time-based invalidation
function getWithTTL(key, ttl, fetchFn) {
  const cached = cache.get(key);
  if (cached && Date.now() - cached.timestamp < ttl) {
    return cached.value;
  }
  const freshData = fetchFn();
  cache.set(key, { value: freshData, timestamp: Date.now() });
  return freshData;
}

// Event-driven invalidation
eventBus.on('data-updated', (key) => {
  cache.delete(key);
});

Advanced Caching Patterns

Implement read/write separation caching:

async function readThroughCache(key, fetchFn) {
  let value = await cache.get(key);
  if (!value) {
    value = await fetchFn();
    await cache.set(key, value);
  }
  return value;
}

async function writeBehindCache(key, value, persistFn) {
  await cache.set(key, value);
  // Asynchronous persistence
  setImmediate(() => persistFn(key, value));
}

Practical Caching Strategies

E-commerce product page example:

const productCache = new Map();

app.get('/products/:id', async (req, res) => {
  const { id } = req.params;
  
  // Check in-memory cache
  if (productCache.has(id)) {
    return res.json(productCache.get(id));
  }
  
  // Check Redis cache
  const redisData = await client.get(`product:${id}`);
  if (redisData) {
    const product = JSON.parse(redisData);
    productCache.set(id, product); // Populate in-memory cache
    return res.json(product);
  }
  
  // Query database
  const product = await db.products.findOne({ where: { id } });
  if (!product) {
    return res.status(404).send('Not found');
  }
  
  // Set caches
  productCache.set(id, product);
  await client.setEx(`product:${id}`, 3600, JSON.stringify(product));
  
  res.json(product);
});

Performance Optimization Checklist

  1. Enable Gzip compression
  2. Set appropriate cache headers
  3. Implement server-side caching
  4. Optimize database queries
  5. Avoid blocking operations
  6. Use cluster mode
  7. Monitor performance metrics
  8. Conduct regular load testing
  9. Implement progressive cache invalidation
  10. Keep middleware lean and efficient

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.