← Назад к вопросам

Как решить проблему утечки памяти на проекте?

3.0 Senior🔥 151 комментариев
#Node.js и JavaScript#Кэширование и производительность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как решить проблему утечки памяти на проекте?

Утечка памяти в Node.js — серьёзная проблема, которая приводит к снижению производительности и падению приложения. За 10+ лет работы я сталкивался с этим множество раз и разработал систематический подход к диагностике и решению.

Где возникают утечки памяти в Node.js

  1. Глобальные переменные — переменные, остающиеся в памяти при каждом запросе
  2. Незакрытые соединения — к БД, очередям, вебсокетам
  3. Циклические ссылки — объекты, ссылающиеся друг на друга
  4. Event listeners — события, забытые в памяти
  5. Большие объекты в кеше — без механизма очистки
  6. Таймеры и интервалы — setInterval не очищен
  7. Замыкания — функции удерживают ссылки на большие объекты

Этап 1: Диагностика

Мониторинг через Node.js процесс:

const os = require('os');
const v8 = require('v8');

// Проверить использование памяти
function logMemoryUsage() {
  const used = process.memoryUsage();
  console.log('Memory Usage:');
  console.log(`  RSS: ${Math.round(used.rss / 1024 / 1024)} MB`);
  console.log(`  Heap Total: ${Math.round(used.heapTotal / 1024 / 1024)} MB`);
  console.log(`  Heap Used: ${Math.round(used.heapUsed / 1024 / 1024)} MB`);
  console.log(`  External: ${Math.round(used.external / 1024 / 1024)} MB`);
}

// Проверять каждые 10 секунд
setInterval(logMemoryUsage, 10000);

Профилирование с помощью Clinic.js:

npm install -g clinic

# Запустить профилирование памяти
clinic doctor -- node app.js

# Детальный анализ
clinic flame -- node app.js

Chrome DevTools для анализа heap:

# Запустить с инспектором
node --inspect app.js

# Открыть chrome://inspect в Chrome

Этап 2: Поиск утечки

Heap snapshots для сравнения:

const heapdump = require('heapdump');

// Создать снимок памяти
setInterval(() => {
  heapdump.writeSnapshot('./heaps/snapshot-${Date.now()}.heapsnapshot');
}, 60000);

// Сравнить два снимка через DevTools

Анализ объектов в памяти:

const v8 = require('v8');

function analyzeHeap() {
  const heap = v8.getHeapStatistics();
  const space = v8.getHeapSpaceStatistics();
  
  console.log('Heap Statistics:');
  space.forEach(s => {
    console.log(`  ${s.space_name}: ${Math.round(s.space_used_size / 1024 / 1024)} MB`);
  });
}

setInterval(analyzeHeap, 30000);

Этап 3: Устранение утечек

Проблема 1: Забытые event listeners

// Плохо: listener остаётся в памяти
emitter.on('data', (data) => {
  console.log(data);
});

// Хорошо: удалить listener
const handler = (data) => console.log(data);
emitter.on('data', handler);

// При завершении
emitter.off('data', handler);

// Или использовать once
emitter.once('data', (data) => {
  console.log(data);
});

Проблема 2: Незакрытые соединения

// Плохо: соединение никогда не закрывается
app.post('/data', async (req, res) => {
  const db = await pool.connect();
  // Забыли сделать release
  const result = await db.query('SELECT * FROM users');
  res.json(result);
});

// Хорошо: явно закрыть соединение
app.post('/data', async (req, res) => {
  const db = await pool.connect();
  try {
    const result = await db.query('SELECT * FROM users');
    res.json(result);
  } finally {
    db.release();
  }
});

// Или использовать async pool
app.post('/data', async (req, res) => {
  const result = await pool.query('SELECT * FROM users');
  res.json(result);
});

Проблема 3: Бесконечное кеширование

// Плохо: кеш растёт бесконечно
const cache = {};

app.get('/user/:id', (req, res) => {
  if (!cache[req.params.id]) {
    cache[req.params.id] = getUserFromDB(req.params.id);
  }
  res.json(cache[req.params.id]);
});

// Хорошо: использовать LRU кеш
import LRU from 'lru-cache';

const cache = new LRU({
  max: 500, // Максимум 500 элементов
  ttl: 1000 * 60 * 5 // TTL 5 минут
});

app.get('/user/:id', (req, res) => {
  let user = cache.get(req.params.id);
  if (!user) {
    user = getUserFromDB(req.params.id);
    cache.set(req.params.id, user);
  }
  res.json(user);
});

Проблема 4: Замыкания с большими объектами

// Плохо: замыкание удерживает весь объект config
const config = { /* большой объект */ };

function createHandler() {
  return function(req, res) {
    // Handler удерживает ссылку на весь config
    console.log(config.someProperty);
  };
}

// Хорошо: достать только нужное
const config = { /* большой объект */ };
const neededValue = config.someProperty;

function createHandler() {
  return function(req, res) {
    // Handler удерживает только строку
    console.log(neededValue);
  };
}

Проблема 5: Таймеры без очистки

// Плохо: интервал никогда не очищается
setInterval(() => {
  processData();
}, 1000);

// Хорошо: хранить и очищать при необходимости
let interval = setInterval(() => {
  processData();
}, 1000);

process.on('SIGTERM', () => {
  clearInterval(interval);
  process.exit(0);
});

Инструменты мониторинга

Node.js встроенные инструменты:

# Запустить с флагом сборки мусора
node --expose-gc app.js

# Получить информацию о GC
node --trace-gc app.js

# Профилирование CPU и памяти
node --prof app.js
node --prof-process isolate-*.log > processed.txt

PM2 для мониторинга:

pm2 install pm2-auto-pull
pm2 monit

# Настроить лимиты памяти
pm2 start app.js --max-memory-restart 300M

APM инструменты:

  • New Relic — полный мониторинг приложения
  • Datadog — анализ памяти и производительности
  • Sentry — отслеживание ошибок
  • Clinic.js — бесплатный инструмент для диагностики

Best Practices

  • Регулярно тестируй приложение на утечки памяти под нагрузкой
  • Используй инструменты профилирования при разработке
  • Явно закрывай соединения и ресурсы
  • Удаляй event listeners при их больше не нужности
  • Используй LRU кеш вместо бесконечного хранилища
  • Избегай глобальных переменных для хранения данных запроса
  • Установи лимиты памяти через PM2 или Docker
  • Мониторь production регулярно на утечки

Систематический подход к диагностике и устранению утечек памяти — критический навык для разработчика Node.js приложений.