🌐 Detecting your location…

Node.js-এ ‘জাভাস্ক্রিপ্ট হিপ অফ মেমরি’ ত্রুটি কীভাবে ঠিক করবেন

⏱️3 min read  ·  548 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);

একটি sawtooth প্যাটার্ন স্বাস্থ্যকর — আবর্জনা সংগ্রহ স্মৃতি পুনরুদ্ধার করা। একটি সিঁড়ি যা কেবল উঠে যায় সেটি একটি ফুটো।

কারণ 4: মেমরি ফুরিয়ে যাওয়া তৈরি করে

বড় TypeScript, ওয়েবপ্যাক, বা Next.js বিল্ডগুলি মেমরি-ক্ষুধার্ত এবং ঘন ঘন সিআই কন্টেইনারগুলিতে পরিমিত সীমার সাথে ব্যর্থ হয়।

# 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

CI-এ, নিশ্চিত করুন যে রানার আসলে আপনার যে মেমরির অনুমতি দিচ্ছেন তা আছে। একটি 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 হিপ ত্রুটির মানে সাধারণত কাজের জন্য প্রকৃতপক্ষে আরও মেমরির প্রয়োজন হয়, যখন একটি দীর্ঘ-চলমান সার্ভারে স্থির বৃদ্ধি মানে ফুটো। একবারে সবকিছু লোড করার পরিবর্তে স্ট্রীম ফাইল এবং পেজিনেট ক্যোয়ারী ফলাফল, প্রতিটি ক্যাশে আকার এবং TTL সীমার সাথে আবদ্ধ করুন, শ্রোতাদের সরিয়ে দিন এবং তাদের সুযোগ শেষ হলে টাইমার পরিষ্কার করুন এবং আসলে কী রাখা হচ্ছে তা খুঁজে পেতে হিপ স্ন্যাপশট তুলনা ব্যবহার করুন। বাড়াতে--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🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা