পারফরম্যান্স ও নিরাপত্তা FAQ: ধীর, আটকানো ও অবিশ্বস্ত ডেটা
সংক্ষেপে: কয়েক ডজন MB JSON ফাইল কেন খোলে না, গভীর ক্লোন কত দামি, __proto__ দূষণ কীভাবে ঠেকাবেন, আর XSS কোথা দিয়ে ঢোকে — লক্ষণ অনুযায়ী সাজানো দশটি প্রাথমিক উপসংহার।

মূল কথা: আগে লক্ষণ দেখে নির্ণয় করুন — পার্সিং, রেন্ডারিং নাকি মেমরি — তারপর সমাধানের ক্রম হেঁটে আসুন: ডেটার আয়তন কমান, গভীর ক্লোন বন্ধ করুন, পার্সিং Worker-এ সরান ও ভার্চুয়াল স্ক্রলিং যোগ করুন, আর ৫০ MB-এর উপরে সবকিছু স্ট্রিম করুন। অবিশ্বস্ত ইনপুটে __proto__, constructor ও prototype ফিল্টার করার আগে আকার ও নেস্টিং গভীরতা সীমিত করুন।
"JSON ধীর" দশবারের নয়বারই ভুল; আসলে যা ধীর তা সাধারণত রেন্ডারিং, গভীর ক্লোন, বা ভুল অপারেশন-ক্রম।
১. কেন ধীর
বড় JSON ফাইল পার্স করা ধীর। কীভাবে অপ্টিমাইজ করব?
আগে bottleneck লোকেট করুন, তারপর অপ্টিমাইজেশনের কথা বলুন। ধাপগুলো কয়েক মাত্রার ক্রমে আলাদা:
| ধাপ | ১০ MB ডেটার মোটামুটি সময় | কেন bottleneck |
|---|---|---|
| নেটওয়ার্ক ট্রান্সফার | ব্যান্ডউইথের উপর নির্ভর | অকম্প্রেসড JSON সাধারণত প্রায় ১/৫-এ কম্প্রেস হয় |
JSON.parse |
২০–৮০ ms | নেটিভ বাস্তবায়ন, ইতিমধ্যে দ্রুত |
| রিকার্সিভ ট্রাভার্সাল / গভীর ক্লোন | ২০০–২০০০ ms | বিশুদ্ধ JS নোড-ধরে-নোড প্রসেসিং |
| DOM রেন্ডারিং (ট্রি ভিউ) | সেকেন্ড, সহজেই জমে যায় | নোড-সংখ্যা বিস্ফোরিত হয় |
তাই অপ্টিমাইজেশনের ক্রম হওয়া উচিত:
- ডেটার আয়তন কমান: API পেজিনেট করুক, কেবল দরকারি ফিল্ড ফেরত দিক, gzip/brotli চালু করুন;
- গভীর ক্লোন এড়ান: পরের অংশ দেখুন; এটাই সবচেয়ে সাধারণ লুকানো খরচ;
- লেজি রেন্ডারিং: ট্রি ভিউতে একবারে কয়েক হাজার DOM নোড না বানিয়ে কেবল এক্সপ্যান্ড করা স্তর রেন্ডার করুন;
- স্ট্রিমিং: ডেটা ১০ MB ছাড়ালে সব একবারে পার্স করবেন না।
Bottleneck সত্যিই JSON.parse হলে মানে ডেটা ইতিমধ্যে কয়েক ডজন MB বা বেশি, আর সঠিক পদক্ষেপ স্ট্রিমিং পার্সার:
import { parser } from 'stream-json';
import { streamArray } from 'stream-json/streamers/StreamArray';
// Handle array elements one by one; memory usage is independent of the total size
const pipeline = fs.createReadStream('big.json')
.pipe(parser())
.pipe(streamArray());
pipeline.on('data', ({ value }) => {
processItem(value);
});
কয়েক ডজন MB JSON খোলার সঙ্গে সঙ্গে ব্রাউজার ক্র্যাশ করে। এখন কী?
আগে নিশ্চিত করুন এটি "ক্র্যাশ" নাকি "জমে যাওয়া"। দুটোর ব্যবস্থাপনা সম্পূর্ণ আলাদা:
- জমে যাওয়া: ট্যাব "Page unresponsive" দেখায় আর কয়েক ডজন সেকেন্ড পরে হয়তো সেরে যায়। কারণ মেইন থ্রেড পূর্ণ করা লং টাস্ক;
- ক্র্যাশ: ট্যাব "Aw, Snap" হয়ে যায়, সাধারণত মেমরি নিঃশেষ (OOM)।
যেভাবেই হোক, মেইন থ্রেডে প্রসেস করবেন না। পার্সিং Web Worker-এ সরান:
// worker.js
self.onmessage = (event) => {
const parsed = JSON.parse(event.data);
self.postMessage(parsed);
};
Worker "UI জমে না" সমাধান করে, মেমরি সিলিং নয়। একটি ব্রাউজার ট্যাবে সাধারণত ১–৪ GB পাওয়া যায়; ২০০ MB JSON অবজেক্টে পার্স করলে ১ GB বা বেশি নিতে পারে (প্রতিটি কি-ভ্যালু জোড়া অবজেক্ট ওভারহেড বহন করে), আর উপরে রেন্ডারিং যোগ করলে সহজেই সীমায় ঠেকে।
আসল সমাধান কখনো পুরোটা লোড না করা:
| ডেটার আকার | পদ্ধতি |
|---|---|
| < ৫ MB | সোজা JSON.parse দিয়ে পার্স করুন, রেন্ডারিং খেয়াল রাখুন |
| ৫–৫০ MB | Worker পার্সিং + ভার্চুয়াল স্ক্রলিং |
| > ৫০ MB | স্ট্রিমিং পার্সিং, কেবল দরকারি ফিল্ড রাখুন |
| > ৫০০ MB | ফরম্যাট বদলান (Parquet / SQLite / শার্ডেড JSON Lines) |
JSON.parse(JSON.stringify(obj)) কি গভীর ক্লোন?
এটি গভীর ক্লোন, কিন্তু কয়েক জায়গায় "নীরবে ডেটা বদলায়", তাই সাধারণ ক্লোনিং টুল হিসেবে উপযুক্ত নয়।
const source = {
date: new Date(),
fn: () => {},
undef: undefined,
nan: NaN,
map: new Map(),
nested: { a: 1 },
};
JSON.parse(JSON.stringify(source));
// {
// date: "2026-10-30T03:00:00.000Z", // Date becomes a string
// nan: null, // NaN becomes null
// nested: { a: 1 } // only this level is correct
// }
// fn and undef disappear, map becomes {}
সমস্যার তালিকা:
| টাইপ | ফল |
|---|---|
Date |
ISO স্ট্রিং হয়ে যায় |
NaN / Infinity |
null হয়ে যায় |
undefined / ফাংশন / Symbol |
অবজেক্টে উধাও, অ্যারেতে null হয় |
Map / Set |
{} হয়ে যায় |
BigInt |
TypeError throws |
| সার্কুলার রেফারেন্স | TypeError throws |
আধুনিক ব্রাউজারে structuredClone আছে, যা Date, Map, Set ও সার্কুলার রেফারেন্স সমর্থন করে, ফাংশন নয়:
const copy = structuredClone(source);
কেবল অগভীর কনফিগ মার্জ হলে spread অপারেটর সবচেয়ে সস্তা: { ...config, timeout: 3000 }।
ট্রি ভিউ এক্সপ্যান্ড করা এত আটকে যায় কেন?
কারণ প্রতিটি নোড একটি DOM এলিমেন্ট, আর নোড-সংখ্যা কয়েক হাজারে পৌঁছালে ব্রাউজারের লেআউট খরচ বিস্ফোরিত হয়।
১০ MB JSON-এ ৫০k–১০০k লিফ নোড থাকতে পারে। কেবল দৃশ্যমান এলাকা রেন্ডার করলেও একবার DOM গড়তে শত মিলিসেকেন্ড থেকে সেকেন্ড লাগে, আর পরের প্রতিটি স্ক্রলে রিফ্লো ট্রিগার হয়।
সঠিক পথ:
- ভার্চুয়াল স্ক্রলিং: কেবল ভিউপোর্টের কয়েক ডজন সারি রেন্ডার করুন আর স্ক্রলে DOM নোড পুনরায় ব্যবহার করুন;
- ডিফল্টে কোলাপ্স: কেবল প্রথম দুই স্তর এক্সপ্যান্ড করুন ও গভীর স্তর চাহিদামাফিক রেন্ডার করুন;
- রেন্ডার স্কেল সীমিত করুন: ১০k নোডের উপরে "ডেটা খুব বড়, লোকেট করতে সার্চ ব্যবহার করুন" দেখান, জোর করে রেন্ডার নয়।
এই সাইটের এডিটর বড় ফাইলে graceful degrade করে এজন্যই — প্রযুক্তিগতভাবে রেন্ডার করতে পারে না বলে নয়, বরং রেন্ডার করলে ব্যবহারযোগ্য হতো না বলে।
২. কেন আক্রমণের শিকার হয়
__proto__ ইনজেকশন কীভাবে কাজ করে?
প্রোটোটাইপ দূষণ। আক্রমণকারী __proto__ কি-যুক্ত JSON বানায়; আপনার কোড ফিল্টার না করে অবজেক্টে মার্জ করলে
এটি Object.prototype ওভাররাইট করে আর রানটাইমের প্রতিটি অবজেক্টকে প্রভাবিত করে।
const payload = JSON.parse('{"__proto__": {"isAdmin": true}}');
// Dangerous code: recursive merge without checking key names
function merge(target, source) {
for (const key in source) {
if (typeof source[key] === 'object') {
target[key] = target[key] || {};
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
merge({}, payload);
({}).isAdmin; // true — every object has been polluted
মূল তথ্য: JSON.parse নিজে প্রোটোটাইপ দূষিত করে না; {"__proto__": ...} সাধারণ নিজস্ব প্রপার্টি হিসেবে পার্স হয়।
বিপজ্জনক হলো পার্সের পরের মার্জ, অ্যাসাইনমেন্ট, Object.assign, lodash.merge ইত্যাদি।
তিন প্রতিরক্ষা:
- কি-নাম ফিল্টার: মার্জের সময়
__proto__,constructorওprototypeএড়িয়ে যান; Object.create(null)কন্টেইনার হিসেবে ব্যবহার করুন যাতে দূষণের প্রোটোটাইপ চেইন না থাকে;Mapদিয়ে অবিশ্বস্ত কি-ভ্যালু জোড়া রাখুন।
function safeMerge(target, source) {
for (const key of Object.keys(source)) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue;
}
const value = source[key];
if (value && typeof value === 'object' && !Array.isArray(value)) {
target[key] = safeMerge(target[key] ?? {}, value);
} else {
target[key] = value;
}
}
return target;
}
এছাড়া for...in উত্তরাধিকারসূত্রে পাওয়া প্রপার্টি হাঁটে; Object.keys() নিরাপদ।
অবিশ্বস্ত JSON পার্স করার সময় কী খেয়াল করব?
এই ক্রমে করুন — কম খরচ, বেশি লাভ:
- আকার ক্যাপ করুন: থ্রেশহোল্ডের উপরে কিছুই সঙ্গে সঙ্গে প্রত্যাখ্যান করুন, আগে মেমরিতে পড়বেন না;
- গভীরতা ক্যাপ করুন: কয়েকশো স্তর গভীর JSON রিকার্সিভ পার্সারের স্ট্যাক ওভারফ্লো করতে পারে;
- কি-সংখ্যা ক্যাপ করুন: হ্যাশ কলিশন আক্রমণ ও মেমরি amplification ঠেকায়;
- বিপজ্জনক কি ফিল্টার করুন:
__proto__,constructor,prototype; - কাঠামো যাচাই করুন: Schema-এর মধ্য দিয়ে চালিয়ে টাইপ ও বাধ্যতামূলক ফিল্ড নিশ্চিত করুন;
Content-Typeবিশ্বাস করবেন না: ঘোষিত টাইপ ও প্রকৃত কন্টেন্ট আলাদা হতে পারে।
গভীরতা চেক সস্তা; পার্সের আগে মোটামুটি রেগুলার ফিল্টার ব্যবহার করতে পারেন, বা পার্সের পর ট্রাভার্স করতে পারেন:
function measureDepth(value, depth = 0) {
if (value === null || typeof value !== 'object') {
return depth;
}
let maxDepth = depth;
for (const key of Object.keys(value)) {
maxDepth = Math.max(maxDepth, measureDepth(value[key], depth + 1));
}
return maxDepth;
}
কিন্তু এই কোড নিজেই রিকার্সিভ আর দূষিতভাবে গভীর ডেটায় তবু স্ট্যাক ওভারফ্লো করতে পারে। নিরাপদ পথ হলো পার্সিংয়ের সময়েই সীমা বলবৎ করা,
গভীরতা অপশন সমর্থনকারী পার্সার দিয়ে (কিছু লাইব্রেরি maxDepth প্যারামিটার দেয়)।
JSON দিয়ে XSS কীভাবে হয়?
JSON নিজে HTML নয়, চলে না। XSS ঘটে যেখানে "একটি JSON মান HTML-এ জোড়া লাগানো হয়"।
তিন সাধারণ পথ:
| পথ | উদাহরণ | প্রতিরক্ষা |
|---|---|---|
| সার্ভার-পক্ষ টেমপ্লেট জোড়া লাগানো | res.send('<div>' + data.name + '</div>') |
আউটপুটে HTML-escape |
ফ্রন্টএন্ড innerHTML |
el.innerHTML = data.html |
textContent বা sanitize ব্যবহার করুন |
| ইনলাইন স্ক্রিপ্ট এম্বেড | <script>var d = ${JSON.stringify(data)}</script> |
<, >, & এস্কেপ করুন |
তৃতীয়টি সবচেয়ে সূক্ষ্ম। JSON.stringify < এস্কেপ করে না, তাই </script><script>alert(1)</script> ধারণকারী ডেটা
স্ক্রিপ্ট ট্যাগ আগেই বন্ধ করতে পারে:
const data = { name: '</script><script>alert(1)</script>' };
// Output: <script>var d = {"name":"</script><script>alert(1)</script>"}</script>
সমাধান হলো বিপজ্জনক অক্ষরকে ইউনিকোড এস্কেপে বদলানো:
function safeJsonForHtml(data) {
return JSON.stringify(data)
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e')
.replace(/&/g, '\\u0026')
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029');
}
নেস্টিং গভীরতা সীমিত করা উচিত?
হ্যাঁ। এটি সেরা খরচ-লাভ অনুপাতের প্রতিরক্ষার একটি।
সীমিত না করার পরিণতি: রিকার্সিভ ফাংশন স্ট্যাক ওভারফ্লো করে, গভীরতা বেশি হলে কিছু বাস্তবায়নে JSON.parse ক্র্যাশ করে,
আর সিরিয়ালাইজেশনও একইভাবে স্ট্যাক উড়িয়ে দেয়। ১ KB [[[[...]]]] অরক্ষিত পার্সার নামাতে যথেষ্ট।
যুক্তিসঙ্গত থ্রেশহোল্ড ব্যবসার উপর নির্ভর করে:
| ডেটার ধরন | প্রস্তাবিত সীমা |
|---|---|
| সাধারণ ব্যবসায়িক অবজেক্ট | ১০–২০ স্তর |
| ট্রি কাঠামো (অর্গ চার্ট, ডিরেক্টরি) | ৫০–১০০ স্তর |
| সাধারণ API গেটওয়ে | ২০ স্তর |
JSON হাইজ্যাকিং কী?
একটি ঐতিহাসিক দুর্বলতা: প্রাথমিক ব্রাউজার ক্রস-অরিজিন <script src>-কে JSON লোড ও চালাতে দিত, আর আক্রমণকারীরা
Array কনস্ট্রাক্টর বা Object.prototype.__defineSetter__ পুনরায় সংজ্ঞায়িত করে ডেটা চুরি করত।
আধুনিক প্রতিরক্ষা বেশ সম্পূর্ণ:
- ব্রাউজার আর
__defineSetter__দিয়ে অ্যারে লিটেরাল হাইজ্যাক করতে দেয় না; - সঠিক
Content-Type: application/jsonস্ক্রিপ্ট এক্সিকিউশন ঠেকায়; X-Content-Type-Options: nosniffMIME sniffing ঠেকায়।
পুরোনো সিস্টেমে তবু অবশিষ্ট ঝুঁকি থাকে। ব্যবহারিক পরামর্শ: টপ লেভেলে খালি অ্যারে নয়, অবজেক্ট ব্যবহার করুন।
{"data": [...]}-এর মতো মোড়ক রেসপন্সকে বৈধ JavaScript স্টেটমেন্ট হওয়া থেকে রাখে ({ কোড ব্লক হিসেবে ধরা হয়),
সিনট্যাক্স স্তরেই হাইজ্যাকিং আটকায়।
JSON লগ করার ঝুঁকি কী?
তিনটি:
- সংবেদনশীল তথ্য ডিস্কে লেখা: ফোন নম্বর, পরিচয়পত্র নম্বর, টোকেন ও প্লেইনটেক্সট পাসওয়ার্ড লগিং সিস্টেমে পড়ে, যার সাধারণত শিথিল অনুমতি ও দীর্ঘ ধারণকাল;
- লগ ইনজেকশন: নিউলাইন ও জাল লগ প্রিফিক্সযুক্ত JSON মান একটি রেকর্ডকে কয়েকটিতে ভাগ করে অডিট ট্রেইল দূষিত করে;
- আকার বিস্ফোরণ: পুরো ১০ MB রেসপন্স লগে লিখলে লগিং সিস্টেম দ্রুত ভরে যায়।
পদ্ধতি:
// Redact and truncate before logging
function safeLog(data) {
const cloned = structuredClone(data);
for (const key of ['password', 'token', 'idCard', 'phone']) {
if (key in cloned) cloned[key] = '***';
}
const text = JSON.stringify(cloned);
return text.length > 2000 ? text.slice(0, 2000) + '...(truncated)' : text;
}
স্ট্রাকচার্ড লগিং সিস্টেম (যেমন pino) নিজেই নিউলাইন এস্কেপ করে, কিন্তু redaction ও truncation তবু আপনার কাজ।
৩. দ্রুত রেফারেন্স
| লক্ষণ | আগে কী দেখবেন | সমাধান |
|---|---|---|
| ধীর পার্সিং | ডেটার আয়তন, গভীর ক্লোন জড়িত কি না | চাঙ্কিং/স্ট্রিমিং, JSON.parse(JSON.stringify()) সরান |
| পেজ জমে যায় | মেইন থ্রেডে লং টাস্ক, রেন্ডার হওয়া নোড-সংখ্যা | Worker + ভার্চুয়াল স্ক্রলিং |
| ট্যাব ক্র্যাশ | মেমরি ব্যবহার | সব লোড করবেন না; স্ট্রিমিংয়ে যান |
| গভীর ক্লোন ডেটা হারায় | Date, NaN, Map |
structuredClone ব্যবহার করুন |
| অবজেক্টে হঠাৎ বাড়তি প্রপার্টি | প্রোটোটাইপ দূষণ | __proto__ ফিল্টার করুন, Map ব্যবহার করুন |
| স্ক্রিপ্ট পপ আপ | HTML-এ জোড়া লাগানো স্ট্রিং | < এস্কেপ করুন, textContent ব্যবহার করুন |
| স্ট্যাক ওভারফ্লো | নেস্টিং গভীরতা | ১০–২০ স্তরে সীমিত করুন |
| লগ ভরে যায় | পুরো রেসপন্স লগ হয় | truncate + redact |