Skip to main content
Практика завдань

Як виявити та запобігти витокам пам'яті в Node.js?

Витоки пам'яті в Node.js

Витік пам'яті виникає, коли додаток виділяє пам'ять, але не звільняє її, коли вона більше не потрібна. З часом споживання пам'яті зростає, поки процес не зламається з помилкою недостатньо пам'яті.


Загальні причини витоків пам'яті

1. Глобальні змінні та кеші без обмежень

js
// ❌ Зростає безкінечно const cache = {}; app.get('/data/:id', (req, res) => { cache[req.params.id] = fetchExpensiveData(req.params.id); res.json(cache[req.params.id]); });

Виправлення: Використовуйте LRU кеш з максимальним розміром:

js
// ✅ Обмежений кеш const LRU = require('lru-cache'); const cache = new LRU({ max: 500, ttl: 1000 * 60 * 5 });

2. Не видалені обробники подій

js
// ❌ Новий обробник додається на кожен запит app.get('/stream', (req, res) => { process.on('data', handler); // Ніколи не видаляється! });

Виправлення: Завжди видаляйте обробники:

js
// ✅ Видалити при відключенні req.on('close', () => { process.removeListener('data', handler); });

3. Замикання, що утримують великі об'єкти

js
function processData() { const hugeArray = new Array(1e6).fill('data'); // ❌ Замикання утримує hugeArray, хоча потрібна лише довжина return function getLength() { return hugeArray.length; }; }

Виправлення: Витягніть лише те, що потрібно:

js
function processData() { const hugeArray = new Array(1e6).fill('data'); const length = hugeArray.length; // hugeArray тепер можна зібрати сміття return function getLength() { return length; }; }

4. Забуті таймери та інтервали

js
// ❌ Інтервал ніколи не очищається setInterval(() => { doSomething(); }, 1000);

Виправлення: Зберігайте та очищайте посилання:

js
const interval = setInterval(() => doSomething(), 1000); // Коли закінчено: clearInterval(interval);

5. Непосилені потоки

Не споживання читабельних потоків призводить до накопичення буферизованих даних у пам'яті.


Виявлення витоків пам'яті

Використання process.memoryUsage()

js
setInterval(() => { const mem = process.memoryUsage(); console.log({ rss: `${Math.round(mem.rss / 1024 / 1024)} MB`, heapUsed: `${Math.round(mem.heapUsed / 1024 / 1024)} MB`, heapTotal: `${Math.round(mem.heapTotal / 1024 / 1024)} MB`, external: `${Math.round(mem.external / 1024 / 1024)} MB` }); }, 5000);

Використання --inspect з Chrome DevTools

bash
node --inspect server.js

Відкрийте chrome://inspect → Зробіть снімки купи → Порівняйте їх з часом.

Використання clinic.js

bash
npx clinic doctor -- node server.js npx clinic heapprofiler -- node server.js

Використання --max-old-space-size

bash
# Обмежте купу до 512MB, щоб швидше виявляти витоки в розробці node --max-old-space-size=512 server.js

Контрольний список запобігання

ПрактикаОпис
Використовуйте обмежені кешіLRU з максимальним розміром та TTL
Видаляйте обробники подійЗавжди .removeListener() або .off()
Уникайте глобального стануОбмежте дані життєвим циклом запиту
Очищайте таймериclearInterval, clearTimeout
Споживайте потокиЗавжди підключайте або читайте потоки
Моніторте пам'ятьВідстежуйте process.memoryUsage() у виробництві
Використовуйте WeakMap/WeakRefДля кешів об'єктів, які не повинні заважати GC

Порада: У виробництві використовуйте інструменти моніторингу, такі як Prometheus + Grafana, Datadog або New Relic, щоб відстежувати тенденції пам'яті та налаштовувати сповіщення перед аваріями OOM.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?
Практика завдань