এজেন্টের জন্য Chrome DevTools-এর মাধ্যমে Google Chat কীভাবে ৩০টির বেশি মেমরি সংক্রান্ত সমস্যার সমাধান করেছে

Vatan Aggarwal
Vatan Aggarwal Software Engineer

প্রকাশিত: ২৯ অক্টোবর, ২০২৬

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

এই সমস্যার সমাধান করতে, Google Chat টিম মেমরি সংক্রান্ত সমস্যা খুঁজে বের করে তা সমাধান করার জন্য একটি উদ্যোগ শুরু করেছে। তারা এজেন্টের জন্য Chrome DevTools ব্যবহার করেছে, যার মধ্যে DevTools MCP সার্ভার অন্তর্ভুক্ত আছে, যাতে একটি কঠিন ম্যানুয়াল প্রসেসকে অটোমেটেড ওয়ার্কফ্লোতে পরিণত করা যায়।

চ্যালেঞ্জ: ৫০০ এমবি খড়ের গাদায় সূচ খোঁজা

একটি লিক হওয়া অবজেক্ট কেন মেমরিতে রাখা হয়েছে তা সঠিকভাবে শনাক্ত করার জন্য অত্যন্ত ছোট করা কোডের মাধ্যমে রিটেনিং পাথ ট্রেস করতে হবে। সম্প্রতি Chrome DevTools মেমরি প্যানেলে (যেমন, উন্নত এজ ফিল্টারিং এবং অবজেক্ট কোয়েরি) করা উন্নতির ফলে এটি সহজ হলেও, ৫০০ এমবি স্ন্যাপশট জুড়ে ম্যানুয়ালি এটি করা এখনও ধীরগতির এবং মানসিকভাবে ক্লান্তিকর হতে পারে।

আর লিক সারানোর জন্য দুই ধরনের জ্ঞান থাকা প্রয়োজন:

  • V8 জাভাস্ক্রিপ্ট ইঞ্জিন কীভাবে কম্পাইল করা বাইটকোড ম্যানেজ করে তা আপনাকে বুঝতে হবে।
  • এছাড়াও, আপনাকে অ্যাপ্লিকেশনের নির্দিষ্ট স্টেট ম্যানেজমেন্ট ও কম্পোনেন্ট লাইফসাইকেল সম্পর্কে বুঝতে হবে।

এই দুটি ভিন্ন ধরনের জ্ঞান ম্যানুয়ালি একত্রিত করতে সাধারণত কয়েক দিন ধরে তদন্ত করতে হয় এবং প্রতিটি লিকের জন্য একাধিক ডেভেলপারের সাথে সহযোগিতা করতে হয়।

সমাধান: এজেন্টের সাহায্যে হিপ নেভিগেট করা

টিম বুঝতে পেরেছে যে লার্জ ল্যাঙ্গুয়েজ মডেল স্ট্রাকচার্ড ডেটার উপর যুক্তি প্রয়োগ করার ক্ষেত্রে খুব ভালো হলেও, টোকেন খরচ যুক্তিসঙ্গত রেখে বিশাল ফাইল প্রসেস করার জন্য তাদের সঠিক টুল প্রয়োজন। মেমরি টুল চালু থাকা (--memoryDebugging=true) ওপেন-সোর্স Chrome DevTools MCP সার্ভারের সাথে Google Antigravity কানেক্ট করে, তারা এজেন্টকে V8 হিপে সরাসরি প্রোগ্রাম্যাটিক অ্যাক্সেস দিয়েছে।

Chrome DevTools মেমরি প্যানেলে রিটেনার ট্রি ম্যানুয়ালি বড় করার পরিবর্তে, এজেন্ট সরাসরি স্ন্যাপশট কোয়েরি করেছে। প্রসেস শুরু করার জন্য টিম একটি সহজ প্রম্পট ব্যবহার করেছে:

Discover memory leaks in the heap snapshot chat.heapsnapshot using Chrome DevTools memory tools.

মেমরি ডিবাগিং স্কিল ব্যবহার করে, এজেন্ট নির্দিষ্ট MCP টুল ব্যবহার করে একটি স্ট্রাকচার্ড ওয়ার্কফ্লো এক্সিকিউট করেছে:

  1. স্ন্যাপশট ক্যাপচার বা লোড করা: take_heapsnapshot-এর মতো টুল ব্যবহার করে অথবা আগে থেকে থাকা ফাইল পড়ে।
  2. হিপ স্ট্রাকচার বিশ্লেষণ করুন: এটি get_heapsnapshot_details এবং compare_heapsnapshots ব্যবহার করে সাধারণ লিক সোর্স স্ক্যান করেছে।
  3. get_heapsnapshot_retaining_paths ব্যবহার করে ডমিনেটর ট্রি থেকে গার্বেজ কালেকশন রুটে ফিরে যাওয়ার জন্য রিটেনিং পাথ ট্রেস করা: এটি ব্যবহার করা হয়।
  4. সমাধান জেনারেট করা: এটি অত্যন্ত নির্দিষ্ট প্যাচ আউটপুট করতে অ্যাপ্লিকেশনের সোর্স কোডের সাথে রিটেনিং পাথ ক্রস-রেফারেন্স করেছে।

টেকনিক্যাল ইনসাইট: তিনটি সাধারণ মেমরি লিক প্যাটার্ন

এজেন্ট ৩০টির বেশি আলাদা আলাদা লিকেজ শনাক্ত করেছে। এজেন্ট নিম্নলিখিত তিনটি সবচেয়ে প্রভাবশালী প্যাটার্ন খুঁজে পেয়েছে, যা সবচেয়ে বেশি মেমরি সেভিংস এবং আধুনিক ওয়েব অ্যাপ্লিকেশনে সবচেয়ে সাধারণ আর্কিটেকচারাল অ্যান্টি-প্যাটার্নকে তুলে ধরে।

স্ট্যাটিক এরর স্ট্যাক ট্রেস ট্র্যাপ

এজেন্ট শনাক্ত করেছে যে সবচেয়ে বড় লিকেজটি আগে থেকে বরাদ্দ করা এরর অবজেক্টের সাথে যুক্ত ছিল, যা স্থায়ীভাবে প্রায় ২০ এমবি মেমরি ধরে রেখেছিল। যেমন, একটি স্ট্যাটিক এরর কনস্ট্যান্ট (EMPTY_KEY_ERROR) নিজেই ১৪.১ এমবি ডেটা ধরে রেখেছে।

সমস্যা: V8-এর মতো আধুনিক জাভাস্ক্রিপ্ট ইঞ্জিনে, Error অবজেক্ট তৈরি করলে, ডিবাগিংয়ে সাহায্য করার জন্য বর্তমান কল স্ট্যাকের স্ন্যাপশট ক্যাপচার করা হয়। এই নির্দিষ্ট মেকানিজমের কারণে বিশাল রেখে দেওয়া সাইজ তৈরি হয়েছে:

  • সমস্যাটি ছিল একটি গ্লোবাল কনস্ট্যান্ট: এটি প্রাথমিক মডিউল মূল্যায়নের সময় ইনস্ট্যান্টিয়েট করা হয়েছিল।
  • JavaScript ইঞ্জিন স্ট্যাক ক্যাপচার করেছে: সেই সময় স্ট্যাকে অ্যাক্টিভ ফাংশন ছিল অ্যাপ্লিকেশনের মূল ইনিশিয়ালাইজেশন র‍্যাপার (একটি ইমিডিয়েটলি ইনভোকড ফাংশন এক্সপ্রেশন বা IIFE)।
  • অ্যারে মেমরি ট্র্যাপ করেছে: যেহেতু অ্যাপ্লিকেশনটি কখনই ত্রুটিটি গার্বেজ কালেক্ট করেনি, তাই এর ইন্টার্নাল স্ট্যাক ট্রেস অ্যারে প্রধান বান্ডেলের ইনিশিয়ালাইজেশন ফাংশনের একটি স্থায়ী রেফারেন্স ধরে রেখেছে।
// Ineffective pattern: Instantiating Error objects at module evaluation time
export const EMPTY_KEY_ERROR = new Error("Key cannot be empty");

export function validateKey(key: string) {
  if (!key) {
    throw EMPTY_KEY_ERROR;
  }
}

সমাধান: টিম কোড পরিবর্তন করেছে যাতে সমস্যা দেখা দিলে ডায়নামিক উপায়ে সমস্যা তৈরি করা যায়। If a reusable static error is strictly necessary, explicitly clearing the stack (EMPTY_KEY_ERROR.stack = '';) immediately frees the captured V8 internal frames.

// Recommended: Construct errors dynamically
export function validateKey(key: string) {
  if (!key) {
    throw new Error("Key cannot be empty");
  }
}

// Or clear the stack property if a static instance is necessary:
export const REUSABLE_ERROR = new Error("Static error");
REUSABLE_ERROR.stack = ""; // Releases the captured initialization frame

গ্লোবাল উইন্ডো ইভেন্ট লিসনার স্কেল করা

এজেন্ট visibilitychange ইভেন্ট লিসনারের সাথে সম্পর্কিত মেমরি স্কেলিং সমস্যা খুঁজে পেয়েছে। যদিও এটি কোনও কঠোর "লিক" নয় (কারণ কম্পোনেন্ট আনমাউন্ট করার সময় সেগুলি শেষ পর্যন্ত পরিষ্কার হয়ে যায়), এটি মেমরির ব্যবহার বাড়িয়ে দেয় কারণ স্ক্রিনে প্রতিটি মেসেজের জন্য একজন শ্রোতা ছিল। স্ন্যাপশট থেকে ১,১৪২টি বিল্ট-ইন ইভেন্ট লিসনারের কথা জানা গেছে, যার মধ্যে ১২৮টি অ্যাক্টিভ লিসনার সরাসরি গ্লোবাল উইন্ডো অবজেক্টের সাথে অ্যাটাচ করা আছে।

সমস্যা: জাভাস্ক্রিপ্টে, ইভেন্ট লিসনার কলব্যাক হল ক্লোজার। একটি ক্লোজার তার এনক্লোজিং লেক্সিক্যাল স্কোপের রেফারেন্স ধরে রাখে যাতে এটি যে ভেরিয়েবলগুলি প্রয়োজন সেগুলি অ্যাক্সেসযোগ্য থাকে। এজেন্ট এই শ্রোতাদের মধ্যে ১২৬ জনকে একটি একক কম্পোনেন্টে ট্র্যাক করেছে: RelativeTimeController। Chat UI-তে প্রতিটি টাইমস্ট্যাম্প (যেমন, "৫ মিনিট আগে") নিজস্ব কন্ট্রোলার ম্যানেজ করে, যা সময় আপডেট করার জন্য প্রাইভেট ভিজিবিলিটি লিসেনার রেজিস্টার করে। কারণ প্রতিটি শ্রোতা উইন্ডো রুটে তার কন্ট্রোলার ইনস্ট্যান্স অ্যাঙ্কর করেছে, অ্যাক্টিভ ক্লোজারের একটি ঘন ওয়েব দৃশ্যমান চ্যাট থ্রেডের লাইফটাইমের জন্য উল্লেখযোগ্য মেমরি টাই আপ করেছে।

// Ineffective pattern: Attaching global window listeners in individual UI controllers
export class RelativeTimeController {
  constructor() {
    this.visibilityListener = () => this.updateTime();
    window.addEventListener("visibilitychange", this.visibilityListener);
  }

  // If dispose() fails to run or unregister, the closure pins the controller
}

সমাধান: টিম গ্লোবাল উইন্ডো ইভেন্ট সেন্ট্রালাইজ করেছে। শেয়ার করা TimerService এখন উইন্ডোতে ঠিক একটি ভিজিবিলিটি লিসেনার রেজিস্টার করে এবং সব অ্যাক্টিভ টাইমস্ট্যাম্প কন্ট্রোলারে আপডেট ব্রডকাস্ট করে। এর ফলে ১২৬ জন শ্রোতা থেকে ঝটপট কমে গিয়ে মাত্র ১ জন শ্রোতা হয়ে যায়।

// Recommended: Centralized event coordination using a shared service
export class VisibilityService {
  private listeners = new Set<() => void>();

  constructor() {
    // Single global listener throttled for the entire application
    window.addEventListener("visibilitychange", () => this.notifyAll());
  }

  subscribe(callback: () => void): () => void {
    this.listeners.add(callback);
    return () => this.listeners.delete(callback); // Unsubscribe handle
  }
}

থার্ড-পার্টি SDK এবং এম্বেড করা iframe রিটেনার

আলাদা করা DOM নোড হল এমন এলিমেন্ট যা ডকুমেন্ট ট্রি থেকে সরিয়ে দেওয়া হয়েছে কিন্তু এখনও JavaScript ভেরিয়েবল দ্বারা রেফার করা হয়। আলাদা করা DOM ট্রির কোনও একটি এলিমেন্ট যদি রুট ভেরিয়েবলের মাধ্যমে রেখে দেওয়া হয়, তাহলে আলাদা করা সম্পূর্ণ ট্রি পুনরুদ্ধার করা যাবে না।

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

সমাধান: সব এম্বেড করা iframe ও থার্ড-পার্টি উইজেট যেন কম্পোনেন্ট ডিসপোজাল লাইফসাইকেলের সাথে যুক্ত থাকে তা নিশ্চিত করুন। প্লেয়ারে .destroy() মেথড স্পষ্টভাবে কল করলে গ্লোবাল SDK রেজিস্ট্রি থেকে এন্ট্রি মুছে যায় এবং DOM-কে গার্বেজ কালেক্ট করার অনুমতি দেয়।

// Recommended: Explicit teardown of embedded frames and SDK instances
export class MediaPlayerController {
  private player: ThirdPartyPlayer | null = null;

  dispose(): void {
    if (this.player) {
      this.player.destroy(); // Clears entry from global SDK registry
      this.player = null;
    }
  }
}

অতিরিক্ত প্যাটার্ন

আগে উল্লেখ করা তিনটি প্যাটার্ন সেভ করা র মেগাবাইটের ক্ষেত্রে সবচেয়ে প্রভাবশালী হলেও, এজেন্ট আরও ২৭টির বেশি লিক শনাক্ত করেছে এবং সমাধান করেছে। এর মধ্যে এগুলি অন্তর্ভুক্ত:

  • ইমেজ অ্যারে টেলিমেট্রি: যেখানে বিচ্ছিন্ন DOM ট্রিগুলি কাস্টম ডায়াগনস্টিক অ্যারে দ্বারা জীবিত রাখা হয়েছিল যা অ্যাবাভ-দ্য-ফোল্ড ইমেজ মেট্রিক্স ট্র্যাক করে
  • ফ্রেমওয়ার্ক কম্পোনেন্ট লাইফসাইকেল সংক্রান্ত সমস্যা: যেখানে ডকুমেন্টের বডিতে হোস্ট করা কাস্টম ড্রপ-ডাউন মেনু তাদের আসল পেরেন্টকে আনমাউন্ট করা বাইপাস করে
  • ইমপ্লিসিট আর্গুমেন্ট অবজেক্ট ক্লোজার ট্র্যাপ: যেখানে ইনার ক্লোজার অনিচ্ছাকৃতভাবে আউটার ফাংশনের সম্পূর্ণ আর্গুমেন্ট অ্যারে ক্যাপচার করে

প্রভাব: ওয়ার্কফ্লো হিসেবে প্রোঅ্যাক্টিভ মেমরি ম্যানেজমেন্ট

এজেন্টের জন্য Chrome DevTools-এর মাধ্যমে কোডিং এজেন্টকে সজ্জিত করে, Google Chat টিম একটি অত্যন্ত বিশেষায়িত কাজ অটোমেট করেছে। জটিল মেমরি লিক ট্রেস করার সময় ম্যানুয়াল হিপ ট্রাভার্সাল করতে যেখানে কয়েক দিন সময় লাগত, সেখানে অটোমেটেড বিশ্লেষণের মাধ্যমে কয়েক মিনিটেই তা করা সম্ভব।

P2ER-এর "Mandatory Empirical Verification" প্রয়োগ করে UI-এর স্থায়িত্ব বজায় রাখার মতোই, টিমগুলি এই মেমরি অডিটিং পদ্ধতিকে একটি প্রোঅ্যাক্টিভ গার্ডরেল হিসেবে গ্রহণ করতে পারে। ডেভেলপাররা প্রায়শই মেমরি সংক্রান্ত সমস্যাকে উপেক্ষা করেন, তারা মনে করেন যে এই সমস্যা শুধুমাত্র খুব বড় বা জটিল অ্যাপ্লিকেশনেই হয়। তবে, এই কেস স্টাডি থেকে দেখা যায় যে স্ট্যান্ডার্ড ইভেন্ট লিসেনার বা স্ট্যাটিক এররের মতো সাধারণ প্যাটার্ন নীরবে জমা হতে পারে। আপনার দৈনিক ডেভেলপমেন্ট সাইকেলে এজেন্টের জন্য Chrome DevTools-এর মতো টুল ইন্টিগ্রেট করলে, AI-কে একটি সাধারণ কোড জেনারেটর থেকে একটি নির্ভরযোগ্য কোয়ালিটি গেটে রূপান্তরিত করে, যা প্রোডাকশনে পৌঁছানোর আগেই সাইলেন্ট রিগ্রেশন শনাক্ত করে।