অবজেক্ট হাইড্রেশন (Object hydration) একটি সমাধান করা সমস্যা বলে মনে হয় যতক্ষণ না আপনি এটি পরিমাপ করেন। আপনি ডাটাবেস থেকে একটি রো (row) নিয়ে আসেন, অ্যারেটিকে একটি অবজেক্টে ম্যাপ করেন এবং কাজ চালিয়ে যান। আমাদের বেশিরভাগ মানুষ এটিকে সাধারণ পাইপলাইনের মতো মনে করে—যা দেখা যায় না, আকর্ষণহীন এবং যথেষ্ট দ্রুত বলে ধরে নেওয়া হয়। তারপর একদিন আপনি একটি ব্যাচ জব (batch job) বা কিউ ওয়ার্কার (queue worker) প্রোফাইল করতে গিয়ে লক্ষ্য করেন যে সিপিপিইউ (CPU) টাইমের একটি বড় অংশ ম্যাপারের পেছনে ব্যয় হচ্ছে। এই উপলব্ধিটিই HydraType তৈরির পথ দেখিয়েছে, যা একটি বিশেষ নিয়মের ওপর ভিত্তি করে তৈরি করা হয়েছে: একটি ক্লাস কখনোই এমন কোনো ফিচারের জন্য মূল্য দেবে না যা তার প্রপার্টিগুলো ব্যবহার করে না।

যদি একটি ফিল্ড একটি সাধারণ স্ট্রিং হয় যার কোনো ট্রান্সফরমেশনের প্রয়োজন নেই, তবে জেনারেটেড কোডটি এমন হওয়া উচিত যেন মনে হয় একজন মানুষ এটি হাতে লিখেছে। সরাসরি অ্যাসাইনমেন্ট (Direct assignment)। কোনো লুপ নেই, কোনো পাইপলাইন নেই, কোনো রিফ্লেকশন (reflection) নেই।

হাইড্রেশন আসলে কতটা খরচ করে

প্রথম দেখায় ['name' => 'Alice', 'age' => 30] কে একটি User অবজেক্টে রূপান্তর করা খুব সহজ মনে হতে পারে। সমস্যা শুরু হয় যখন আপনি নমনীয়তা (flexibility) চান। বেশিরভাগ সাধারণ উদ্দেশ্যে ব্যবহৃত হাইড্রেশন টুলগুলো রানটাইমে ক্লাসগুলো পরীক্ষা করার জন্য রিফ্লেকশনের ওপর নির্ভর করে। তারা মেটাডেটা ম্যাপ তৈরি করে, টাইপ কোয়ার্সন (type coercion) সম্পন্ন করে এবং নেস্টেড অবজেক্ট গ্রাফ সমাধান করে। তারা পাইপলাইনও খুব পছন্দ করে। প্রতিটি ভ্যালু মিউটেটর (mutator), অ্যাসারশন (assertion) এবং ট্রান্সফরমারের একটি সিকোয়েন্সের মধ্য দিয়ে যায়, এমনকি সেই সিকোয়েন্সটি খালি থাকলেও। লুপ স্ট্রাকচারটি নিজেই অতিরিক্ত ওভারহেড (overhead) তৈরি করে।

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

জিরো-ওভারহেড দর্শন

HydraType-এর মূল ধারণা হলো ফিচার আইসোলেশন (feature isolation)। টাইপ কনভার্সন, নেস্টেড অবজেক্ট হাইড্রেশন এবং অ্যাসারশন—সবই সমর্থিত, তবে এর কোনোটিই বাধ্যতামূলক নয়। যদি একটি প্রপার্টির জন্য অ্যারে থেকে অবজেক্টে সরাসরি কপি করা ছাড়া আর কিছুর প্রয়োজন না হয়, তবে জেনারেটেড রাইটারটি ঠিক সেটিই করবে। এখানে কোনো শেয়ারড পাইপলাইন নেই যা একটি সাধারণ স্কেলার ফিল্ডকে এমন ভ্যালিডেটরদের জন্য অপেক্ষা করতে বাধ্য করবে যা তার প্রয়োজন নেই।

এটি শুনতে যতটা সহজ মনে হয়, বাস্তবে তা অর্জন করা ততটা সহজ নয়। অনেক লাইব্রেরি প্রতিটি ফিল্ডের জন্য একটি মাত্র এক্সিকিউশন পাথ ব্যবহার করে কারণ এটি কোডবেসকে ছোট এবং সুসংগত রাখে। HydraType এর ঠিক উল্টো পদ্ধতি গ্রহণ করে। এটি প্রতিটি টার্গেট DTO-এর জন্য একটি অনন্য PHP ক্লাস তৈরি করে, যা শুধুমাত্র সেই নির্দিষ্ট ক্লাসের প্রয়োজনীয় অপারেশনগুলোই নির্গত করে। জটিলতা এখানে 'অপ্ট-ইন' (opt-in) হিসেবে থাকে, প্রতিটি প্রপার্টির ওপর বাধ্যতামূলক কর হিসেবে নয়।

কীভাবে এই গতি আসে

চারটি সুনির্দিষ্ট সিদ্ধান্ত HydraType-কে সাধারণ রিফ্লেকশন-ভিত্তিক টুল এবং অন্যান্য জেনারেটেড পদ্ধতি থেকে আলাদা করে।

১. কোড একবার জেনারেট করুন, চিরকাল চালান

HydraType আপনার টার্গেট ক্লাসটি মাত্র একবার পরীক্ষা করে, তারপর এর সঠিক আকৃতি অনুযায়ী একটি ডেডিকেটেড PHP ক্লাস লিখে ফেলে। যদি আপনার DTO-তে একটি পাবলিক স্ট্রিং $name, একটি ইন্ট $status, এবং একটি প্রাইভেট DateTime $createdAt থাকে, তবে জেনারেটেড রাইটার আগে থেকেই এই সব জানে। রানটাইমে কোনো বারবার রিফ্লেকশন, স্ট্রিং পার্সিং বা অলস মেটাডেটা বিল্ডিং (lazy metadata building) করার প্রয়োজন হয় না। একবার OPCache জেনারেটেড ফাইলটি কম্পাইল করে ফেললে, হাইড্রেশন প্রক্রিয়াটি হাতে লেখা PHP কোডের মতোই দ্রুত কাজ করে।

২. খালি পাইপলাইন দূর করা

বেশিরভাগ হাইড্রেশন টুল তাদের কাজ একটি রানটাইম পাইপলাইনের চারপাশে সাজায়। তারা কলঅ্যাবল (callables)—যেমন মিউটেটর, ভ্যালিডেটর, ট্রান্সফরমার—এর একটি অ্যারে বজায় রাখে এবং প্রতিটি ভ্যালুর ওপর দিয়ে লুপ চালায়। এমনকি সেই অ্যারেগুলো খালি থাকলেও foreach বা array_reduce তবুও কার্যকর হয়। HydraType এটি পুরোপুরি সরিয়ে ফেলে। কোড জেনারেশনের সময় এটি পরীক্ষা করে যে একটি প্রপার্টির আসলে কী প্রয়োজন। যদি অপারেশনের তালিকা খালি থাকে, তবে এটি সরাসরি একটি অ্যাসাইনমেন্ট নির্গত করে যেমন: $object->name = $data['name'];। রানটাইমে আর কোনো লুপ চলে না, কারণ জেনারেটর আগেই সেই সিদ্ধান্ত নিয়ে নিয়েছে।

৩. রিফ্লেকশন রাইট দূর করতে ক্লোজার স্কোপিং ব্যবহার করা

এক্সটারনাল ম্যাপারদের জন্য প্রাইভেট প্রপার্টিগুলো একটি চিরচেনা মাথাব্যথার কারণ। সাধারণ সমাধান হলো ReflectionProperty::setValue(), কিন্তু সরাসরি প্রপার্টি অ্যাক্সেসের তুলনায় এই মেথডটি অনেক বেশি সময় নেয়। HydraType Closure::bind ব্যবহার করে এই সমস্যা এড়িয়ে যায়। জেনারেটেড রাইটারে টার্গেট ক্লাসের স্কোপভুক্ত ক্লোজার (closures) থাকে, যা রিফ্লেকশন ছাড়াই সরাসরি প্রাইভেট এবং প্রোটেক্টেড প্রপার্টিগুলোতে অ্যাক্সেস করতে দেয়। এই বাইন্ডিংটি কনস্ট্রাকশনের সময় একবার ঘটে; এরপর ক্লোজারটি প্রায় নেটিভ স্পিডে চলে।

৪. রাইটার পুনরায় ব্যবহার করা

কিছু লাইব্রেরি প্রতিটি অবজেক্ট হাইড্রেশন করার জন্য একটি নতুন স্ট্র্যাটেজি বা রিফ্লেকশন গ্রাফ তৈরি করে। HydraType তার রাইটার একবার তৈরি করে এবং তারপর হাজার হাজার অবজেক্টের ক্ষেত্রে সেই একই ইনস্ট্যান্স পুনরায় ব্যবহার করে। প্রতি অবজেক্টের খরচ কমে শুধুমাত্র প্রপার্টি ভ্যালু সেট করা এবং ফলাফল রিটার্ন করার ন্যূনতম পর্যায়ে নেমে আসে।

সংখ্যাতাত্ত্বিক তথ্য

PHP 8.2-এ, পার্থক্যটি অত্যন্ত স্পষ্ট:

  • HydraType: 267.9 ns
  • Ocramius GeneratedHydrator: 307.9 ns
  • Symfony PropertyNormalizer: 8,499.0 ns
  • Valinor: 10,755.2 ns

Symfony PropertyNormalizer এবং Valinor শক্তিশালী টুল, কিন্তু এগুলো হলো জেনারেলিস্ট। PropertyNormalizer হলো একটি সিরিয়ালাইজার কম্পোনেন্টের অংশ যা ফরম্যাট ডিটেকশন, নরমালাইজার এবং ডিপ অবজেক্ট গ্রাফ হ্যান্ডেল করে। Valinor টাইপ ট্রি এবং কোয়ার্সন (coercion) এর ক্ষেত্রে অত্যন্ত কঠোর। এই শক্তির একটি মূল্য রয়েছে। এই পরীক্ষায়, তারা HydraType-এর তুলনায় প্রায় ৩০ থেকে ৪০ গুণ ধীরগতির ছিল। এমনকি Ocramius GeneratedHydrator, যা ইতিমধ্যে কোড জেনারেশন ব্যবহার করে, পাইপলাইন এবং প্রপার্টি অ্যাক্সেস সংক্রান্ত আর্কিটেকচারাল পার্থক্যের কারণে কিছুটা পিছিয়ে রয়েছে।

প্রেক্ষাপট গুরুত্বপূর্ণ। একটি মাত্র ডাটাবেস কুয়েরি বা একটি HTTP রাউন্ডট্রিপ এই সমস্ত সংখ্যাকে তুচ্ছ করে দেয়। আপনি যদি একটি কুয়েরির পরে মাত্র তিনটি অবজেক্ট হাইড্রেট করেন, তবে ন্যানোসেকেন্ডের পেছনে ছোটা শক্তির অপচয় মাত্র। যখন আপনি স্কেল করবেন, তখন এই ব্যবধানটি অর্থবহ হয়ে উঠবে। পঞ্চাশ হাজার রেকর্ড হাইড্রেট করা একটি ব্যাচ প্রসেস, অথবা ক্যাশ অ্যারে থেকে অবজেক্ট পুনর্গঠন করা একটি কিউ কনজিউমার (queue consumer), ২৬৭ ন্যানোসেকেন্ড এবং দশ মাইক্রোসেকেন্ডের মধ্যে পার্থক্যটি অনুভব করতে পারবে। ফিল্ড এবং রো-এর সংখ্যা গুণ করলে দেখা যাবে যে, কোনো বিজনেস লজিক পরিবর্তন না করেই দ্রুতগতির ম্যাপার একটি কাজের সময় কয়েক সেকেন্ড কমিয়ে দিতে পারে।

আসল শিক্ষা

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

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