🌐 Detecting your location…

Node.js में ‘जावास्क्रिप्ट हीप मेमोरी से बाहर’ त्रुटि को कैसे ठीक करें

⏱️3 min read  ·  543 words

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memoryइसका मतलब है कि V8 अपनी ढेर छत से टकराया। इसके पीछे दो बहुत अलग-अलग स्थितियाँ हैं: एक वैध कार्यभार जिसके लिए अधिक मेमोरी की आवश्यकता होती है, और एक रिसाव या एक एल्गोरिथ्म जो उससे कहीं अधिक लोड हो रहा है। सीमा बढ़ाने से पहला ठीक हो जाता है और दूसरा छिप जाता है।

पहला: यह कौन सी स्थिति है?

कुछ भी बदलने से पहले इसका उत्तर दें.

लक्षण संभावित कारण
किसी निर्माण या बड़े बैच के कार्य के दौरान विफल वैध रूप से अधिक ढेर की आवश्यकता है
घंटों या दिनों के अपटाइम के बाद सर्वर क्रैश हो जाता है मेमोरी लीक
बड़े इनपुट पर विफल, छोटे पर ठीक स्ट्रीमिंग के बजाय सब कुछ मेमोरी में लोड हो रहा है
किसी भी आकार पर तुरंत क्रैश हो जाता है असीमित पुनरावृत्ति या अनंत संचय

एक लंबे समय से चलने वाला सर्वर जो लगातार बढ़ता है उसमें रिसाव होता है। सीमा बढ़ाने से केवल दुर्घटना टलती है।

त्वरित समाधान: ढेर सीमा बढ़ाएँ

बिल्ड और बैच कार्यों के लिए उपयुक्त जिन्हें वास्तव में मेमोरी की आवश्यकता होती है।

# Per invocation, in megabytes
node --max-old-space-size=4096 script.js

# For anything Node spawns, including build tools
export NODE_OPTIONS="--max-old-space-size=4096"
npm run build
{
  "scripts": {
    "build": "NODE_OPTIONS=--max-old-space-size=4096 next build"
  }
}

इसे वास्तव में उपलब्ध मेमोरी से ऊपर सेट न करें। यदि कंटेनर में 2GB है और आप 4GB ढेर की अनुमति देते हैं, तो कर्नेल का OOM किलर SIGKILL के साथ प्रक्रिया को समाप्त कर देता है – और आपको कोई जावास्क्रिप्ट त्रुटि नहीं मिलती है, बस 137 का एक निकास कोड मिलता है, जिसका निदान करना बहुत कठिन है।

# Exit code 137 = 128 + 9 (SIGKILL) — killed by the OS, not by V8
docker inspect <container> --format='{{.State.ExitCode}}'
dmesg | grep -i "killed process"

कारण 1: संपूर्ण फ़ाइलों को मेमोरी में पढ़ना

डेटा-प्रोसेसिंग कोड में सबसे आम कारण।

// ❌ A 2GB file needs 2GB+ of heap, plus overhead for the string
import fs from 'node:fs/promises';
const content = await fs.readFile('huge.csv', 'utf8');
const lines = content.split('\n');       // now a second copy exists
// ✅ Constant memory regardless of file size
import fs from 'node:fs';
import readline from 'node:readline';

const stream = fs.createReadStream('huge.csv', { encoding: 'utf8' });
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });

for await (const line of rl) {
  await processLine(line);
}

यही बात HTTP प्रतिक्रियाओं और डेटाबेस परिणामों पर भी लागू होती है। उन्हें स्ट्रीम करें; उन्हें जमा न करें.

// ❌ Buffers the entire response
const res = await fetch(url);
const buffer = await res.arrayBuffer();

// ✅ Pipes straight to disk
import { pipeline } from 'node:stream/promises';
import { Writable } from 'node:stream';

await pipeline(res.body, fs.createWriteStream('output.bin'));

कारण 2: असीमित क्वेरी परिणाम

// ❌ Ten million rows, all resident at once
const users = await db.query('SELECT * FROM users');
for (const user of users) await sendEmail(user);
// ✅ Keyset-paginated batches — constant memory
let lastId = 0;
const BATCH = 1000;

while (true) {
  const rows = await db.query(
    'SELECT * FROM users WHERE id > $1 ORDER BY id LIMIT $2',
    [lastId, BATCH]
  );
  if (rows.length === 0) break;

  for (const user of rows) await sendEmail(user);
  lastId = rows[rows.length - 1].id;
}

इससे भी बेहतर, एक डेटाबेस कर्सर का उपयोग करें जहां आपका ड्राइवर एक का समर्थन करता है – यह दोनों तरफ पूर्ण परिणाम सेट को पकड़े बिना पंक्तियों को स्ट्रीम करता है।

कारण 3: लंबे समय से चल रहे सर्वर में वास्तविक लीक

उनमें से अधिकांश के लिए चार पैटर्न जिम्मेदार हैं।

एक असीमित कैश. एक सादा वस्तु या मानचित्र जो केवल बढ़ता ही रहता है।

// ❌ Grows forever
const cache = new Map();
function get(key) {
  if (!cache.has(key)) cache.set(key, expensive(key));
  return cache.get(key);
}
// ✅ Bounded with eviction
import { LRUCache } from 'lru-cache';
const cache = new LRUCache({ max: 5000, ttl: 1000 * 60 * 10 });

श्रोता जिन्हें कभी हटाया नहीं जाता.

// ❌ Adds a listener on every request
app.get('/data', (req, res) => {
  emitter.on('update', () => res.write('...'));
});

// ✅ Remove it when the request ends
app.get('/data', (req, res) => {
  const onUpdate = () => res.write('...');
  emitter.on('update', onUpdate);
  res.on('close', () => emitter.off('update', onUpdate));
});

नोड इस बारे में चेतावनी देता है –MaxListenersExceededWarning यह एक रिसाव सूचक है, दबाने के लिए शोर नहीं।

टाइमर जो कभी साफ़ नहीं होते. हरsetInterval इसे बंद करके और इसमें संदर्भित हर चीज़ को हमेशा के लिए जीवित रखता है।

बड़ी वस्तुओं को कैप्चर करने वाला क्लोजर। एक कॉलबैक जो किसी विशाल ऑब्जेक्ट के एक फ़ील्ड को संदर्भित करता है, संपूर्ण ऑब्जेक्ट को पहुंच योग्य रखता है।

हीप स्नैपशॉट्स के साथ लीक ढूँढना

अनुमान लगाना धीमा है. स्नैपशॉट लें और तुलना करें.

# Start with the inspector attached
node --inspect server.js
# Open chrome://inspect in Chrome, click "inspect", go to the Memory tab

प्रक्रिया: एक स्नैपशॉट लें, कुछ देर के लिए संदिग्ध कार्यभार चलाएं, दूसरा स्नैपशॉट लें, फिर तुलनात्मक दृश्य में “डेल्टा” के आधार पर क्रमबद्ध करें। जो वस्तुएँ स्नैपशॉट के बीच बढ़ती हैं और कभी सिकुड़ती नहीं हैं, वे आपकी लीक हैं, और रिटेनर ट्री बिल्कुल वही दिखाता है जो उन्हें जीवित रखता है।

आप प्रक्रिया के अंदर से स्नैपशॉट भी ट्रिगर कर सकते हैं, जो उत्पादन में उपयोगी है।

import v8 from 'node:v8';
import fs from 'node:fs';

process.on('SIGUSR2', () => {
  const file = `/tmp/heap-${Date.now()}.heapsnapshot`;
  fs.writeFileSync(file, v8.getHeapSnapshot());
  console.log('Heap snapshot written to', file);
});
// Then: kill -SIGUSR2 <pid>

रिसाव की तलाश करने से पहले इसकी पुष्टि करने के लिए लॉग हीप का लगातार उपयोग करें।

setInterval(() => {
  const m = process.memoryUsage();
  console.log({
    rss:       `${(m.rss / 1e6).toFixed(0)}MB`,
    heapUsed:  `${(m.heapUsed / 1e6).toFixed(0)}MB`,
    heapTotal: `${(m.heapTotal / 1e6).toFixed(0)}MB`,
    external:  `${(m.external / 1e6).toFixed(0)}MB`,
  });
}, 30_000);

सॉटूथ पैटर्न स्वस्थ है – कचरा संग्रहण स्मृति को पुनः प्राप्त करता है। एक सीढ़ी जो केवल ऊपर उठती है वह एक रिसाव है।

कारण 4: मेमोरी ख़त्म होने लगती है

बड़े टाइपस्क्रिप्ट, वेबपैक, या नेक्स्ट.जेएस बिल्ड मेमोरी-भूख वाले होते हैं और मामूली सीमा वाले सीआई कंटेनरों में अक्सर विफल हो जाते हैं।

# Give the build more headroom
NODE_OPTIONS=--max-old-space-size=6144 npm run build

# TypeScript: incremental builds reuse previous work
tsc --incremental --noEmit

# Split very large builds into projects
tsc --build tsconfig.json

सीआई में, सुनिश्चित करें कि धावक के पास वास्तव में वह मेमोरी है जिसकी आप अनुमति दे रहे हैं। 4GB रनर पर 6GB हीप सीमा सहायक त्रुटि के बजाय OOM किल उत्पन्न करती है।

डॉकर और कंटेनर सीमाएँ

प्रत्येक कॉन्फ़िगरेशन में नोड स्वचालित रूप से अपने ढेर को कंटेनर सीमा तक आकार नहीं देता है, इसलिए दोनों को स्पष्ट रूप से सेट करें।

services:
  api:
    image: my-api
    environment:
      # Keep the heap below the container limit, leaving room for
      # native allocations, buffers, and the runtime itself.
      NODE_OPTIONS: "--max-old-space-size=1536"
    deploy:
      resources:
        limits:
          memory: 2G

ढेर सीमा और कंटेनर सीमा के बीच का अंतर मायने रखता है। बफ़र्स, मूल मॉड्यूल और रनटाइम सभी जावास्क्रिप्ट ढेर के बाहर आवंटित होते हैं, और--max-old-space-size उनका हिसाब नहीं रखता.

निदान अनुक्रम

  1. निर्धारित करें कि यह V8 त्रुटि है या OS किल – निकास कोड 137 का अर्थ है कि कर्नेल ने ऐसा किया है।
  2. लॉगprocess.memoryUsage() अधिक समय तक। नीरस रूप से बढ़ने का मतलब है रिसाव।
  3. यदि यह केवल बड़े इनपुट पर विफल रहता है, तो संपूर्ण-फ़ाइल या संपूर्ण-परिणाम-सेट लोडिंग देखें।
  4. यदि यह एक लंबे समय तक चलने वाला सर्वर है, तो दो हीप स्नैपशॉट लें और डेल्टा की तुलना करें।
  5. बढ़ती हुई वस्तुओं के लिए रिटेनर ट्री की जाँच करें ताकि पता चल सके कि उन्हें क्या पकड़ता है।
  6. एक बार जब आप यह स्थापित कर लें कि उपयोग वैध है तो ही ढेर सीमा बढ़ाएँ।

अक्सर पूछे जाने वाले प्रश्न

प्रश्न: डिफ़ॉल्ट ढेर सीमा क्या है?
उत्तर: यह नोड संस्करण और उपलब्ध सिस्टम मेमोरी पर निर्भर करता है, और आधुनिक संस्करण इसे पुराने संस्करणों की तुलना में अधिक समझदारी से आकार देते हैं।node -e "console.log(v8.getHeapStatistics().heap_size_limit / 1e6)".

से अपना जांचें प्रश्न: क्या अधिकतम-पुराने-स्थान-आकार को बढ़ाना सुरक्षित है?
उत्तर: वास्तविक मेमोरी आवश्यकताओं वाले बिल्ड और बैच कार्यों के लिए, हाँ – बशर्ते मशीन में मेमोरी हो। लीक हो रहे सर्वर के लिए यह क्रैश को स्थगित कर देता है और निदान करना कठिन बना देता है।

प्रश्न: मेरा ऐप एग्जिट कोड 137 के साथ क्रैश क्यों हो जाता है और कोई त्रुटि नहीं होती?
उ: कर्नेल के OOM किलर ने SIGKILL भेजा, जिसे पकड़ा नहीं जा सकता। आपकी हीप सीमा कंटेनर सीमा से अधिक है, या हीप के बाहर कुछ मेमोरी का उपभोग कर रहा है।

प्रश्न: क्या मैं कचरा संग्रहण के लिए बाध्य कर सकता हूँ?
ए:--expose-gcके साथ आप कॉल कर सकते हैंglobal.gc(), जो यह परीक्षण करने के लिए उपयोगी है कि मेमोरी वास्तव में बरकरार है या नहीं। यह कोई समाधान नहीं है – यदि स्मृति पुनः प्राप्त नहीं हुई है, तब भी कुछ इसका संदर्भ देता है।

प्रश्न: क्या वर्कर थ्रेड मदद करते हैं?
उ: प्रत्येक श्रमिक को अपना स्वयं का ढेर मिलता है, इसलिए उनमें काम को विभाजित करने से कुल क्षमता बढ़ जाती है। यह रिसाव को ठीक नहीं करता है; यह इसे वितरित करता है.

निष्कर्ष

विफलता को वर्गीकृत करके प्रारंभ करें:किसी बिल्ड पर V8 हीप त्रुटि का आमतौर पर मतलब होता है कि कार्य को वास्तव में अधिक मेमोरी की आवश्यकता होती है, जबकि लंबे समय तक चलने वाले सर्वर में लगातार वृद्धि का मतलब रिसाव होता है। सब कुछ एक साथ लोड करने के बजाय स्ट्रीम फ़ाइलें और पेजिनेट क्वेरी परिणाम, प्रत्येक कैश को आकार और टीटीएल सीमाओं के साथ बांधें, श्रोताओं को हटा दें और जब उनका दायरा समाप्त हो जाए तो टाइमर साफ़ करें, और वास्तव में क्या बरकरार रखा जा रहा है यह जानने के लिए हीप स्नैपशॉट तुलना का उपयोग करें। उठाएँ--max-old-space-size केवल यह पुष्टि करने के बाद कि उपयोग वैध है, और इसे हमेशा कंटेनर की मेमोरी सीमा से नीचे आराम से रखें।

MD Rafikul Islam

Written by

MD Rafikul Islam is a software developer and the editor of TechPulse. He writes about developer tooling, hardware, and the practical decisions that come up in day-to-day engineering work — which laptop to buy, which framework to commit to, why a build broke at 2am. He tests the tools he writes about and says plainly when something is not worth the money. Corrections and corrections requests are welcome at rony.yf25@gmail.com.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা