Express.jsSeniorSystem design

Каковы лучшие практики производительности для продакшн Express-приложений?

Production-производительность Express достигается через NODE_ENV=production, gzip-сжатие, кеширование через Redis, кластеризацию всех CPU ядер, HTTP keep-alive, исключение синхронного кода в hot path и профилирование event loop.

Производительность Express в продакшне

1. NODE_ENV=production

Это не просто переменная — Express читает её для включения оптимизаций: кеш шаблонов, сокращённые stack traces, отключение дорогих debug-операций. Всегда устанавливать явно.

NODE_ENV=production node server.js

2. Сжатие ответов

import compression from 'compression';

// Только для текстовых ответов > 1KB
app.use(compression({
  threshold: 1024,
  filter: (req, res) => {
    if (req.headers['x-no-compression']) return false;
    return compression.filter(req, res);
  },
}));

3. Кластеризация

Один Node.js процесс использует одно ядро CPU. Для многоядерных серверов нужен cluster или PM2:

// cluster.js
import cluster from 'cluster';
import { cpus } from 'os';
import { createServer } from './server.js';

if (cluster.isPrimary) {
  const numCPUs = cpus().length;
  console.log(`Primary ${process.pid} running, forking ${numCPUs} workers`);
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  cluster.on('exit', (worker) => {
    console.log(`Worker ${worker.process.pid} died, restarting...`);
    cluster.fork();
  });
} else {
  createServer();
}
# ecosystem.config.yml (PM2)
apps:
  - name: api
    script: src/server.js
    instances: max
    exec_mode: cluster
    env_production:
      NODE_ENV: production

4. HTTP Keep-Alive и таймауты

const server = app.listen(PORT);

// Keep-alive для повторного использования соединений
server.keepAliveTimeout = 65_000; // > ALB idle timeout (60s)
server.headersTimeout = 66_000;   // > keepAliveTimeout

5. Кеширование через Redis

import { createClient } from 'redis';

const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();

const cacheMiddleware = (ttlSeconds) => async (req, res, next) => {
  const key = `cache:${req.originalUrl}`;
  const cached = await redis.get(key);
  if (cached) {
    res.setHeader('X-Cache', 'HIT');
    return res.json(JSON.parse(cached));
  }
  const originalJson = res.json.bind(res);
  res.json = async (data) => {
    await redis.setEx(key, ttlSeconds, JSON.stringify(data));
    res.setHeader('X-Cache', 'MISS');
    originalJson(data);
  };
  next();
};

app.get('/api/products', cacheMiddleware(300), productsController.list);

6. Оптимизация работы с БД

  • Connection pooling с правильным max (обычно 10–20 на процесс).
  • Индексы на колонки WHERE/JOIN — одна плохая запись может убить весь сервис.
  • Paginated queries вместо SELECT * для больших таблиц.
  • Prepared statements для повторяющихся запросов.

7. Мониторинг event loop lag

import { monitorEventLoopDelay } from 'perf_hooks';

const h = monitorEventLoopDelay({ resolution: 20 });
h.enable();

setInterval(() => {
  const lagMs = h.mean / 1e6;
  if (lagMs > 50) {
    logger.warn({ lagMs }, 'High event loop lag detected');
  }
  h.reset();
}, 10_000);

8. Статические файлы

Никогда не отдавать статику через Express в продакшне. Использовать nginx, CDN или отдельный S3/MinIO. Express добавляет overhead Node.js на каждый файл.

Подводные камни

  • Устанавливать compression() после роутов — middleware применяется только к запросам, зарегистрированным после него в цепочке.
  • Слишком маленький пул соединений: при 4 воркерах и max: 20 PostgreSQL получит 80 соединений — проверяйте max_connections на стороне БД.
  • Кешировать ответы с персонализированными данными (по user_id) без учёта ключа кеша — утечка данных между пользователями.
  • Забывать инвалидировать кеш при мутациях — клиенты видят устаревшие данные.
  • keepAliveTimeout меньше idle timeout load balancer (AWS ALB = 60s по умолчанию) — ALB разрывает соединения с ошибкой 502.
  • JSON.stringify на гигантских объектах в hot path блокирует event loop — профилировать с clinic.js.
  • Синхронный console.log в production — использовать pino с async transport.

Common mistakes

  • Дает общий ответ про Node.js и не называет конкретные API Express.js.
  • Не объясняет, где в lifecycle находится производительность Express в продакшене.
  • Не разделяет validation, authorization, business logic и persistence.
  • Игнорирует ошибки, лимиты входных данных, observability и тестирование.

What the interviewer is testing

  • Может объяснить производительность Express в продакшене на примере кода.
  • Называет ключевые API: NODE_ENV=production, compression, reverse proxy.
  • Использует точные API Express.js, а не вымышленные hooks/decorators/methods.
  • Видит production-риски: безопасность, отказоустойчивость, логирование и тесты.

Sources

Related topics