DOM Bottleneck যা নিয়ে কেউ কথা বলে না

কল্পনা করুন একটি সাপোর্ট ড্যাশবোর্ড যা দশ হাজার লগ এন্ট্রি টানছে। অথবা একটি CRM যা একটি সিঙ্গেল স্ক্রোলেবল টেবিলে প্রতিটি কন্টাক্ট দেখানোর চেষ্টা করছে। React-এ এটি তৈরির কোডটি যথেষ্ট নিরীহ মনে হতে পারে। আপনি একটি অ্যারের ওপর ম্যাপ করেন, কিছু JSX রিটার্ন করেন এবং ফ্রেমওয়ার্ককে তার কাজ করতে দেন। ডেভেলপমেন্টে একশটি রো (row) নিয়ে সবকিছু ঠিকঠাক কাজ করে। তারপর যখন প্রোডাকশন ডেটা আসে, তখন পেজটি অত্যন্ত ধীরগতির হয়ে যায়।

ব্রাউজার অলসতা করছে না। এটি ঠিক সেটাই করছে যা আপনি তাকে করতে বলেছেন, আর এটাই সমস্যা। প্রতিটি রো একটি DOM নোড হয়ে ওঠে। প্রতিটি নোড স্টাইল করা হয়, লেআউট করা হয়, পেইন্ট করা হয় এবং মেমরিতে ট্র্যাক করা হয়। যখন আপনি স্ক্রল করেন, ব্রাউজার শুধুমাত্র আপনি যে অংশটি দেখছেন তা নয়, বরং পুরো ট্রি-এর পজিশন পুনরায় গণনা করে। ইভেন্ট লিসেনারগুলো জমে যায়। মেমরি হঠাৎ বেড়ে যায়। অবশেষে মেইন থ্রেডটি এতটাই জট পাকিয়ে যায় যে ইন্টারফেস ক্লিক, কি-স্ট্রোক বা এমনকি স্ক্রল করার ক্ষেত্রেও সাড়া দেওয়া বন্ধ করে দেয়। অ্যাপ্লিকেশনটি প্রযুক্তিগতভাবে ক্র্যাশ করেনি, কিন্তু এর সামনে বসে থাকা ব্যবহারকারীর কাছে অভিজ্ঞতাটি একইভাবে ভেঙে পড়েছে।

এটি ঘটে কারণ ব্রাউজার প্রতিটি এলিমেন্টকে একসাথে একটিভ মেমরিতে রাখার চেষ্টা করে। React আপনার UI-এর ভার্চুয়াল বর্ণনা তৈরিতে দক্ষ হতে পারে, কিন্তু একবার সেই বর্ণনাগুলো ডকুমেন্টে রিয়েল নোড হয়ে গেলে, সেগুলোর খরচ হাতে লেখা HTML-এর মতোই হয়। ফ্রেমওয়ার্কের ভেতরেই এর কোনো সহজ সমাধান বা 'এস্কেপ হ্যাচ' নেই। আপনি কীভাবে লিস্টটি DOM-এ প্রদান করছেন তার ক্ষেত্রে একটি কাঠামোগত পরিবর্তন প্রয়োজন।

ভার্চুয়ালাইজেশন (Virtualization) আসলে কী বোঝায়

ভার্চুয়ালাইজেশন হলো সেই কাঠামোগত পরিবর্তন। React-কে পুরো অ্যারে রেন্ডার করতে বলার পরিবর্তে, আপনি শুধুমাত্র সেই আইটেমগুলো রেন্ডার করেন যা ভিউপোর্টের (viewport) ভেতরে ধরে, সাথে উপরে এবং নিচে একটি ছোট বাফার। ব্যবহারকারী যখন স্ক্রল করেন, অ্যাপ্লিকেশনটি ভিউপোর্টের বাইরে চলে যাওয়া নোডগুলোকে বাদ দিয়ে দেয় এবং বিপরীত প্রান্ত থেকে আসা নতুন নোডগুলোকে তৈরি করে। ব্যবহারকারীর কাছে এটি এখনও একটি অবিচ্ছিন্ন লিস্টের মতো মনে হয় কারণ মোট স্ক্রোলেবল উচ্চতা বজায় রাখা হয়, যা সাধারণত একটি লম্বা কন্টেইনার এলিমেন্ট বা সতর্কতার সাথে গণনা করা একটি স্পেসারের মাধ্যমে করা হয়। দৃশ্যমান আইটেমগুলো আসলে ডেটাসেটের ওপর দিয়ে স্লাইড করা একটি উইন্ডোর মতো।

এটি একটি প্রজেক্টর গেটের মধ্য দিয়ে চলে যাওয়া ফিল্মস্ট্রিপের মতো চিন্তা করুন। দর্শক মসৃণ মোশন দেখতে পান, কিন্তু যন্ত্রটি শুধুমাত্র বর্তমান অবস্থানে থাকা ফ্রেমটিকে আলোকিত করে। বাকি রিলটি ফিড এবং টেক-আপ স্পুলে থাকে, আলোর পথে নয়। ভার্চুয়ালাইজড লিস্টগুলো একইভাবে কাজ করে। ডেটাসেট হলো রিল, আর ভিউপোর্ট হলো গেট।

এটি প্রথাগত অর্থে লেজি লোডিং (lazy loading) নয়। লেজি লোডিং ব্যবহারকারী কাছাকাছি স্ক্রল না করা পর্যন্ত ডেটা ফেচ করা স্থগিত রাখে। ভার্চুয়ালাইজেশন ধরে নেয় যে আপনার কাছে ইতিমধ্যে ডেটা আছে, কিন্তু আপনি কোন অংশগুলো রিয়েল DOM এলিমেন্টে রূপান্তরিত হবে সে বিষয়ে নির্বাচনী ভূমিকা পালন করেন। এই দুটি কৌশল একসাথে কাজ করতে পারে, তবে তারা ভিন্ন ভিন্ন সমস্যার সমাধান করে।

কেন এই পার্থক্যটি তাৎক্ষণিক অনুভূত হয়

এর সুবিধাগুলো চারটি ক্ষেত্রে দেখা যায়, যা মূলত একই মূল বিষয়ের সাথে যুক্ত: আপনি এমন কিছুর জন্য খরচ করা বন্ধ করেন যা ব্যবহারকারী দেখতে পান না।

দ্রুততর প্রাথমিক লোড টাইম। যখন ব্রাউজার পেজটি খোলে, এটি পনের হাজার রো-এর পরিবর্তে হয়তো মাত্র পনেরটি রো পেইন্ট করে। প্রথম অর্থবহ পেইন্ট (meaningful paint) দ্রুত আসে। টাইম-টু-ইন্টারঅ্যাক্টিভ (time-to-interactive) কমে যায় কারণ জাভাস্ক্রিপ্ট ইঞ্জিন নোড তৈরি এবং সেগুলো ডকুমেন্টে যুক্ত করতে কম সময় ব্যয় করে।

কম মেমরি ব্যবহার। একটি DOM নোড একটি ব্যয়বহুল অবজেক্ট। প্রতিটি নোড স্টাইল রুলস, লেআউট মেট্রিক্স এবং ইভেন্ট বাইন্ডিংয়ের রেফারেন্স বহন করে। সক্রিয় নোড সংখ্যা কমিয়ে কয়েক ডজন করে ফেললে মেমরি ফুটপ্রিন্ট নাটকীয়ভাবে কমে যায়। লো-এন্ড ডিভাইস বা দীর্ঘ সেশনের ক্ষেত্রে, এটি একা একটি ট্যাবকে অপারেটিং সিস্টেম দ্বারা বন্ধ হওয়া থেকে রক্ষা করতে পারে।

মসৃণ স্ক্রলিং পারফরম্যান্স। ট্রি-তে নোড সংখ্যা কম থাকায় স্ক্রল ইভেন্টের সময় ব্রাউজার লেআউট এবং পেইন্ট ফেজে কম সময় ব্যয় করে। কম্পোজিটর থ্রেড (compositor thread) লুকানো কন্টেন্টের জ্যামিতি বারবার গণনা না করেই মুভমেন্ট সামলাতে পারে। এর ফলে স্ক্রলিং মনিটরের রিফ্রেশ রেটের কাছাকাছি থাকে।

স্থিতিশীল ফ্রেম রেট। যেহেতু মেইন থ্রেড আর লেআউট কাজের চাপে ডুবে থাকে না, তাই অন্যান্য কাজের জন্য পর্যাপ্ত জায়গা থাকে। অ্যানিমেশনগুলো সাবলীল থাকে। নেটওয়ার্ক রেসপন্স প্রসেস করা যায়। নতুন ডেটা আসার সময় UI ফ্রিজ হয় না কারণ রেন্ডার পাথ আর কোনো বাধা (bottleneck) হয়ে থাকে না।

সঠিকভাবে ইমপ্লিমেন্টেশন করা

React ইকোসিস্টেমে, react-window এবং আরও ভারী react-virtualized-এর মতো লাইব্রেরিগুলো এই প্যাটার্নের জন্য প্রয়োজনীয় ব্যবস্থা প্রদান করে। মূল ধারণাটি একই: আপনি একটি আইটেম রেন্ডারার সংজ্ঞায়িত করেন, মোট আইটেম সংখ্যা প্রদান করেন এবং লাইব্রেরিটি উইন্ডোইং গণনার কাজ পরিচালনা করে। কিন্তু বিস্তারিত বিষয়গুলো অনেক সময় মানুষকে বিভ্রান্ত করে।

প্রথমত, কন্টেইনারের একটি নির্দিষ্ট উচ্চতা থাকা প্রয়োজন। যদি লিস্টটি এমন একটি প্যারেন্টের ভেতরে থাকে যা তার চিলড্রেনদের জায়গা করার জন্য প্রসারিত হয়, তবে ভার্চুয়ালাইজেশন কোন আইটেমগুলো দৃশ্যমান তা গণনা করতে পারবে না কারণ সেখানে কোনো ভিউপোর্ট বাউন্ডারি থাকে না। আপনাকে লিস্টটিকে একটি নির্দিষ্ট উচ্চতা অথবা পরিচিত সীমাবদ্ধতা সম্পন্ন একটি ফ্লেক্স কন্টেইনারের মধ্যে সীমাবদ্ধ রাখতে হবে।

দ্বিতীয়ত, আইটেম সাইজিং অত্যন্ত গুরুত্বপূর্ণ। স্থির উচ্চতার রো (row) হলো সবচেয়ে সহজ ক্ষেত্র। লাইব্রেরিটি রো-এর উচ্চতাকে ইনডেক্স দিয়ে গুণ করে এবং প্রতিটি এলিমেন্ট ঠিক কোথায় পজিশন করতে হবে তা নির্ভুলভাবে জানে। পরিবর্তনশীল উচ্চতার কন্টেন্ট, যেমন এমবেডেড ইমেজসহ চ্যাট মেসেজ বা কমেন্ট থ্রেড, লাইব্রেরিটিকে মাউন্ট করার পর পরিমাপ করতে এবং তাৎক্ষণিকভাবে সমন্বয় করতে বাধ্য করে। এই পরিমাপের ধাপটি যদি অনেক দেরিতে ঘটে, তবে তা স্ক্রল জিটার (scroll jitter) তৈরি করতে পারে। আপনার ডেটা যদি অনুমতি দেয়, তবে অভিন্ন উচ্চতা বা ন্যূনতম উচ্চতা নিশ্চিত করুন। যদি না পারে, তবে একটি ভ্যারিয়েবল-হাইট ভার্চুয়ালাইজার ব্যবহার করুন এবং অতিরিক্ত জটিলতা মেনে নিন।

তৃতীয়ত, ওভারস্ক্যানিং (overscanning) আপনার বন্ধু। স্ক্রিনে ঠিক যতটুকু জায়গা আছে শুধুমাত্র ততটুকুই রেন্ডার করলে ব্যবহারকারী দ্রুত স্ক্রল করার সময় খালি সাদা অংশ দেখা দিতে পারে। বেশিরভাগ লাইব্রেরি আপনাকে স্ক্রিনের উপরে এবং নিচে আরও কিছু অতিরিক্ত আইটেম রেন্ডার করার সুযোগ দেয়। DOM-কে আবার ভারাক্রান্ত না করেই সংযোগস্থলগুলো লুকিয়ে ফেলার জন্য সাধারণত দুই বা তিনটি রো-এর ওভারস্ক্যানিং যথেষ্ট।

চতুর্থত, key প্রপটি অবহেলা করবেন না। একটি ভার্চুয়ালাইজড লিস্টে, স্ক্রল করার সময় আইটেমগুলো DOM নোড পুনরায় ব্যবহার করে। স্টেবল কী (stable keys) রিকনসিলিয়েশনের (reconciliation) সময় React-কে ভুল অনুমান করা এবং রো কম্পোনেন্টের ভেতরের স্টেট (state) ধ্বংস করা থেকে রক্ষা করে। যদি আপনার লিস্টের রো-তে ইনপুট, টগল বা সম্প্রসারণযোগ্য সেকশন থাকে, তবে ভুল কী (key) ব্যবহারের ফলে UI স্টেট এমনভাবে ক্ষতিগ্রস্ত হতে পারে যা দেখে মনে হতে পারে আপনার ডেটা লেয়ারে বাগ আছে, কিন্তু আসলে সেগুলো রেন্ডারিংয়ের ভুল।

একটি সূক্ষ্ম ফাঁদ হলো ব্রাউজারের 'find-in-page' ফিচার। যেহেতু লুকানো আইটেমগুলো DOM-এ থাকে না, তাই ব্রাউজারের সার্চ বক্স সেগুলো খুঁজে পাবে না। আপনার ব্যবহারকারীরা যদি একটি বড় লিস্টের ভেতরে টেক্সট খোঁজার জন্য Ctrl+F-এর ওপর নির্ভর করেন, তবে আপনাকে একটি কাস্টম সার্চ তৈরি করতে হবে যা ডকুমেন্টের পরিবর্তে ডেটাসেটের ওপর ভিত্তি করে কাজ করবে। লিস্টের সিম্যান্টিকস (semantics) যদি সাবধানে হ্যান্ডেল না করা হয়, তবে স্ক্রিন রিডারগুলোও কনটেক্সট হারিয়ে ফেলতে পারে; তাই অ্যাসিস্টিভ টেকনোলজি দিয়ে পরীক্ষা করুন এবং ডায়নামিক লোডিংয়ের জন্য লাইভ রিজন অ্যানাউন্সমেন্ট (live region announcements) যোগ করার কথা বিবেচনা করুন।

কখন এটি এড়িয়ে চলবেন

ভার্চুয়ালাইজেশন বিনামূল্যে পাওয়া যায় না। এটি ডিপেন্ডেন্সি ওয়েট, কোঅর্ডিনেট ম্যাথ এবং কনস্ট্রেইন্ট ওভারহেড বাড়িয়ে দেয়। যদি আপনার লিস্টে মাত্র পঞ্চাশ বা একশটি আইটেম থাকে, তবে ব্রাউজার কোনো সাহায্য ছাড়াই তা সামলাতে পারবে। পুরো লিস্টটি রেন্ডার করুন এবং এগিয়ে যান। যদি আপনার লিস্টের আইটেমগুলো ব্যক্তিগতভাবে অত্যন্ত জটিল হয়, তবে একই কথা প্রযোজ্য। ভার্চুয়ালাইজেশন আপনাকে হাজার হাজার নোড থেকে বাঁচায়, কিন্তু একটি নোডের ভেতরে থাকা বিশাল চার্ট বা ভিডিও এলিমেন্ট থেকে আপনাকে বাঁচাতে পারে না। আগে আইটেমের অতিরিক্ত ভার (bloat) কমানোর দিকে নজর দিন।

এছাড়া লিস্টটি যদি স্ক্রল না হয়, তবে ভার্চুয়ালাইজেশন এড়িয়ে চলুন। আপনি যদি নেক্সট এবং প্রিভিয়াস বাটন দিয়ে পেজিনেশন করেন এবং প্রতি পেজে মাত্র বিশটি আইটেম দেখান, তবে সেখানে 'উইন্ডো' করার মতো কিছু নেই। এই কৌশলটি তখনই কার্যকর হয় যখন ব্যবহারকারী একটি বড় অবিচ্ছিন্ন অনুক্রমের মধ্য দিয়ে স্ক্রল করার আশা করেন।

আসল শিক্ষা

ভার্চুয়ালাইজেশন কোনো লাইব্রেরি নির্বাচন নয়, বরং এটি একটি মানসিকতা। এটি আপনাকে স্বীকার করতে বাধ্য করে যে DOM একটি সীমিত সম্পদ, কোনো অসীম ক্যানভাস নয়। এটি যোগ করার আগে, Chrome DevTools খুলুন, একটি পারফরম্যান্স প্রোফাইল রেকর্ড করুন এবং নিশ্চিত হোন যে লেআউট বা পেইন্ট টাইমই আসলে মূল সমস্যা কি না। একবার যখন আপনি জানবেন যে DOM-ই বটলনেক (bottleneck), তখন সীমাবদ্ধতাগুলো মেনে চলার সিদ্ধান্ত নিন। আপনার উচ্চতা স্থির করুন, কী (key)-গুলোর দিকে নজর দিন, পরিমিতভাবে ওভারস্ক্যান করুন এবং আপনার অ্যাক্সেসিবিলিটি পরীক্ষা করুন। সঠিকভাবে করা হলে, একটি ভার্চুয়ালাইজড লিস্ট একটি অব্যবহারযোগ্য ডেটা দেয়ালকে এমন কিছুতে রূপান্তরিত করে যা একটি নেটিভ স্ক্রল ভিউয়ের মতো হালকা মনে হয়। ব্রাউজার আর লড়াই করে না, আপনার ব্যবহারকারীরা আর অপেক্ষা করে না এবং অ্যাপটি অবশেষে ঠিক সেই দ্রুত ইন্টারফেসের মতো আচরণ করে যা আপনি তৈরি করতে চেয়েছিলেন।