Введение
Производительность Node.js-приложений — это не только про быстрый код, но и про умение эффективно использовать ресурсы, предотвращать узкие места и масштабироваться под нагрузкой. Даже самая совершенная архитектура может столкнуться с проблемами при неправильной работе с памятью, блокирующих операциях или неоптимальных запросах к базе данных. В этом посте мы разберем конкретные техники и паттерны, которые помогут выжать максимум из вашего Node.js-приложения — от потоковой обработки данных и кэширования до кластеризации и продвинутого мониторинга. Все примеры готовы к использованию в реальных проектах.
Оптимизация работы с памятью
Использование потоков для обработки больших данных:
const fs = require('fs'); const { Transform } = require('stream'); // Создаем поток для обработки больших файлов const processLargeFile = () => { const readStream = fs.createReadStream('large-data.csv'); const writeStream = fs.createWriteStream('processed-data.csv'); const transformStream = new Transform({ transform(chunk, encoding, callback) { // Обрабатываем данные по частям const processed = chunk.toString().toUpperCase(); this.push(processed); callback(); } }); readStream .pipe(transformStream) .pipe(writeStream) .on('finish', () => { console.log('Файл обработан потоково'); }); };
Кэширование для снижения нагрузки
Использование Redis для кэширования:
const redis = require('redis'); const client = redis.createClient(); async function getCachedData(key, fetchFunction, ttl = 3600) { try { // Пытаемся получить данные из кэша const cached = await client.get(key); if (cached) { console.log('Данные из кэша'); return JSON.parse(cached); } // Если нет в кэше, получаем новые данные console.log('Запрос к базе данных'); const data = await fetchFunction(); // Сохраняем в кэш await client.setEx(key, ttl, JSON.stringify(data)); return data; } catch (error) { console.error('Ошибка кэширования:', error); return await fetchFunction(); } } // Использование app.get('/api/products', async (req, res) => { const products = await getCachedData( 'all_products', () => db.query('SELECT * FROM products') ); res.json(products); });
Кластеризация для использования всех ядер CPU
const cluster = require('cluster'); const os = require('os'); const express = require('express'); if (cluster.isMaster) { const numCPUs = os.cpus().length; console.log(`Запускаем ${numCPUs} воркеров`); for (let i = 0; i < numCPUs; i++) { cluster.fork(); } cluster.on('exit', (worker) => { console.log(`Воркер ${worker.process.pid} умер, запускаем новый`); cluster.fork(); }); } else { const app = express(); const PORT = 3000; app.get('/heavy-computation', async (req, res) => { // Нагрузка распределяется между воркерами const result = await performComputation(); res.json({ result, processedBy: process.pid }); }); app.listen(PORT, () => { console.log(`Сервер ${process.pid} на порту ${PORT}`); }); }
Оптимизация асинхронных операций
Батчинг запросов к базе данных:
class QueryBatcher { constructor(batchSize = 100, delay = 50) { this.batch = []; this.batchSize = batchSize; this.delay = delay; this.timeout = null; } add(query) { return new Promise((resolve, reject) => { this.batch.push({ query, resolve, reject }); if (this.batch.length >= this.batchSize) { this.executeBatch(); } else if (!this.timeout) { this.timeout = setTimeout(() => { this.executeBatch(); }, this.delay); } }); } async executeBatch() { if (this.timeout) { clearTimeout(this.timeout); this.timeout = null; } if (this.batch.length === 0) return; const currentBatch = [...this.batch]; this.batch = []; try { const queries = currentBatch.map(item => item.query); const results = await db.batchExecute(queries); currentBatch.forEach((item, index) => { item.resolve(results[index]); }); } catch (error) { currentBatch.forEach(item => { item.reject(error); }); } } } // Использование const userBatcher = new QueryBatcher(); async function getUser(id) { return userBatcher.add( `SELECT * FROM users WHERE id = ${id}` ); }
Мониторинг и профилирование
Быстрая настройка мониторинга:
const v8 = require('v8'); const { performance, PerformanceObserver } = require('perf_hooks'); // Наблюдатель за производительностью const obs = new PerformanceObserver((items) => { items.getEntries().forEach((entry) => { console.log(`${entry.name}: ${entry.duration}ms`); }); }); obs.observe({ entryTypes: ['measure'] }); // Мониторинг памяти setInterval(() => { const memoryUsage = process.memoryUsage(); const heapStats = v8.getHeapStatistics(); console.log('--- Мониторинг ---'); console.log(`RSS: ${(memoryUsage.rss / 1024 / 1024).toFixed(2)} MB`); console.log(`Heap Used: ${(memoryUsage.heapUsed / 1024 / 1024).toFixed(2)} MB`); console.log(`Heap Total: ${(memoryUsage.heapTotal / 1024 / 1024).toFixed(2)} MB`); console.log(`Heap Limit: ${(heapStats.heap_size_limit / 1024 / 1024).toFixed(2)} MB`); }, 60000); // Каждую минуту
Оптимизация зависимостей
Анализ пакетов:
// package.json { "scripts": { "analyze": "npx node-prune && npx depcheck", "bundle": "npx webpack --mode production", "profile": "node --prof app.js" } }
Заключение
Оптимизация производительности в Node.js — это постоянный процесс, а не разовое действие. Ключевой принцип: измеряй, анализируй, внедряй, снова измеряй. Начните с мониторинга основных метрик (память, CPU, время ответа), выявите реальные узкие места, а затем применяйте targeted-оптимизации — будь то внедрение кэширования, переход на потоковую обработку или настройка кластеризации.

