মেমরি লিক সম্পর্কে এই দুই-ভাগ সিরিজের প্রথম পার্টে, আমরা দেখেছিলাম Ruby কীভাবে মেমরি ম্যানেজ করে এবং গারবেজ কালেকশন (GC) কীভাবে কাজ করে।
আপনি হয়তো শক্তিশালী মেশিন বেশি মেমরির সাথে অফার্ড করতে পারেন, এবং আপনার অ্যাপ যথেষ্ট রিস্টার্ট হয় যাতে ইউজাররা নোটিস না করে, কিন্তু মেমরি ইউজেজ मैटर করে।
অ্যালোকেশন এবং গারবেজ কালেকশন ফ্রি নয়। আপনার লিক থাকলে, আপনি গারবেজ কালেকশনের ওপর বেশি আরো বেশি সময় ব্যয় করবেন তার বদলে যা করতে আপনি অ্যাপ বিল্ড করেছেন।
এই পোস্টে, আমরা আরও গভীরে যাবো আপনি কী টুলস ব্যবহার করতে পারেন মেমরি লিক আবিষ্কার এবং ডায়গনোস করতে।
চলুন जারি রাখি!
Ruby-তে লিক খুঁজে বের করা
একটি লিক ডিটেক্ট করা সহজ। আপনি GC, ObjectSpace, এবং আপনার APM টুলের RSS গ্রাফ ব্যবহার করে আপনার মেমরি ইউজেজ বাড়তে দেখতে পারেন। কিন্তু শুধু জানা যে আপনার লিক আছে তা ফিক্স করার জন্য যথেষ্ট নয়। আপনাকে জানতে হবে সেটি কোথা থেকে আসছে। রrawing 넘برز সেটি বলতে পারবে না।
শুক্রgués, Ruby ইকোসিস্টেমে কিছু চমৎকার টুল আছে যেগুলো সেই নাম্বারগুলোতে কনটেক্সট যোগ করে। দুটি হচ্ছে memory-profiler এবং derailed_benchmarks।
Ruby-তে memory_profiler
memory_profiler gem একটি খুব সিম্পল API এবং একটি ডিটেইলড (य थोড়া ওভারওয়েলমিং) alokেটেড এবং রিটেইনড মেমরি রিপোর্ট অফার করে — যা ইনক্লুড করে alokেটেড অবজেক্টের ক্লাস, তাদের সাইজ, এবং সেগুলো কোথায় alokেট হয়েছিল। এটি আমাদের লিকি প্রোগ্রামে যোগ করা স্ট্রেইটফরওয়ার্ড।
একটি রিপোর্ট আউটপুট করে যেটি এইরকম দেখে।
এখানে অনেক ইনফরমেশন আছে, কিন্তু সাধারণত allocated objects by location এবং retained objects by location সেকশনগুলো লিক খুঁজতে সবচেয়ে উপযোগী। এগুলো হলো ফাইল লোকেশন যেগুলো অবজেক্ট alokেট করে, alokেটেড অবজেক্টের সংখ্যা অনুযায়ী অর্ডারড।
allocatedঅবজেক্ট হচ্ছেreportব্লকের ভিতরে alokেটেড (ক্রিয়েটেড) সকল অবজেক্ট।retainedঅবজেক্ট হচ্ছে অবজেক্ট যেগুলোreportব্লকের শেষে গারবেজ কলেক্টেড হয়নি। আমরা ব্লকের শেষে আগে একবার GC রান ফোর্স করেছি যাতে লিক করা অবজেক্টগুলো আরও ক্লিয়ারলি দেখতে পাই।
retained অবজেক্ট কাউন্টে আস্থা রাখার সময় সতর্ক থাকুন। এগুলো fortement নির্ভর করে যে লিকিং কোডের কতটুকু অংশ report ব্লকের ভিতরে।
উদাহরণস্বরূপ, যদি আমরা an_array এর ডিক্লেরেশন report ব্লকের ভিতরে সরিয়ে দিই, আমরা ভুলে যেতে পারি যে কোড লিকি নয়।
ফলস্বরূপ রিপোর্টের শীর্ষে অনেক রিটেইনড অবজেক্ট রিপোর্ট করবে না (শুধু রিপোর্টটি নিজেই)।
Ruby-তে derailed_benchmarks
derailed_benchmarks gem সকল ধরনের পারফরম্যান্স কাজের জন্য খুব উপকারী টুলসের একটি সুট, প্রায়মেরিলি Rails অ্যাপস টার্গেটেড। লিক খুঁজতে, আমরা চাই perf:mem_over_time, perf:objects, এবং perf:heap_diff।
এই টাস্কগুলো একটি রানিং অ্যাপে curl রিকোয়েস্ট পাঠিয়ে কাজ করে, তাই আমরা এগুলো আমাদের ছোট লিকি প্রোগ্রামে যোগ করতে পারি না। বদলে, আমাদের একটি ছোট Rails অ্যাপ সেট আপ করতে হবে যেটিতে একটি এন্ডপয়েন্ট থাকে যা প্রতিটি রিকোয়েস্টে মেমরি লিক করে, তারপর সেটিতে derailed_benchmarks ইনস্টল করতে হবে।
এখন আপনি bin/rails s দিয়ে অ্যাপ বুট করতে পারবেন। আপনি একটি এন্ডপয়েন্ট curl করতে পারবেন যা প্রতিটি রিকোয়েস্টে মেমরি লিক করে।
এখন আমরা derailed_benchmarks ব্যবহার করে আমাদের লিক একশনে দেখতে পারি।
perf:mem_over_time
এটি আমাদের সময়ের সাথে সাথে মেমরি ইউজ দেখাবে (সিমিলারলি যেভাবে আমরা watch এবং ps দিয়ে আমাদের লিকি স্ক্রিপ্টের মেমরি গ্রোথ দেখেছিলাম)।
Derailed প্রোডাকশন মোডে অ্যাপ বুট করবে, বারবার একটি এন্ডপয়েন্ট হিট করবে (/ ডিফল্ট), এবং মেমরি ইউজেজ রিপোর্ট করবে। যদি এটা কখনো বাড়তে বন্ধ না করে, আমাদের লিক আছে!
নোট: Derailed টেস্ট করার জন্য Rails অ্যাপকে প্রোডাকশন মোডে বুট করে। ডিফল্টভাবে, এটি require rails/all ও ফার্স্ট করে। যেহেতু এই অ্যাপে ডাটাবেস নেই, আমাদের এই বিহেভিয়র ওভাররাইড করতে হবে DERAILED_SKIP_ACTIVE_RECORD=true দিয়ে।
আমরা এই বেনচмар্ক বিভিন্ন এন্ডপয়েন্টের বিপরীতে রান করতে পারি যাতে দেখতে পাই কোনটি/গুলো (যদি থাকে) লিক করে।
perf:objects
perf:objects টাস্ক memory_profiler ব্যবহার করে পর্দার পেছনে, তাই প্রোডিউসড রিপোর্ট znajomi লাগবে।
এই রিপোর্ট আপনার লিক করা মেমরি কোথায় alokেট হচ্ছে সেটি ন্যারো ডাউন করতে সাহায্য করতে পারে। আমাদের উদাহরণে, রিপোর্টের শেষ সেকশন — Retained String Report — আমাদের সমস্যাটি একদম স্পষ্ট করে বলে দেয়।
আমরা LeaksController লাইন ৩ থেকে "ABC" কন্টেইন করে ১০,০০০ স্ট্রিং লিক করেছি। একটি নন-ট্রিভিয়াল অ্যাপে, এই রিপোর্ট তাৎপর্যপূর্ণভাবে বড় হতো এবং রিটেইনড স্ট্রিং থাকতো যেগুলো আপনি রিটেইন করতে চান — কোয়েরি ক্যাশ, ইত্যাদি — কিন্তু এই এবং অন্যান্য 'by location' সেকশনগুলো আপনার লিক ন্যারো ডাউন করতে সাহায্য করবে।
perf:heap_diff
perf:heap_diff বেনচмар্ক সাহায্য করতে পারে যদি perf:objects রিপোর্ট লিক কোথা থেকে আসছে সেটি দেখতে খুব জটিল হয়।
নামের সাজেশন অনুযায়ী, perf:heap_diff তিনটি হিপ ডাম্প তৈরি করে এবং তাদের মধ্যে পার্থক্য ক্যালকুলেট করে। এটি একটি রিপোর্ট তৈরি করে যেটি ডাম্পের মধ্যে রিটেইনড অবজেক্টের টাইপ এবং সেগুলো alokেট করা লোকেশন ইনক্লুড করে।
আপনি Tracking a Ruby memory leak in 2021 পড়ে আরও ভালো বুঝতে পারেন কী হচ্ছে।
রিপোর্ট আমাদের লিকি বেবি অ্যাপের জন্য সঠিক লোকেশন পয়েন্ট করে। ডিফেরেন্সের শীর্ষে, আমরা দেখি ৯৯৯৯৯১ রিটেইনড স্ট্রিং অবজেক্ট alokেটেড LeaksController লাইন ৩ থেকে।
রিয়েল Ruby এবং Rails অ্যাপে লিক
আশা করি, আমরা যা উদাহরণ ব্যবহার করেছি সেগুলো কখনো রিয়েল-লাইফ অ্যাপে ব্যবহার হয়নি — আমি আশা করি কেউ ইচ্ছাক্রমে মেমরি লিক করতে চায় না!
নন-ট্রিভিয়াল অ্যাপে, মেমরি লিক ট্র্যাক ডাউন করা অনেক কঠিন হতে পারে। রিটেইনড অবজেক্ট সবসময় খারাপ নয় — গারবেজ কলেক্টেড আইটেমসহ একটি ক্যাশ খুব উপকারী হবে না।
সব লিকের মধ্যে একটি সাধারণ জিনিস আছে। কোথাও একটি রুট-লেভেল অবজেক্ট (একটি ক্লাস/গ্লোবাল, ইত্যাদি) একটি অবজেক্টের রেফারেন্স ধরে রাখে।
একটি সাধারণ উদাহরণ হলো লিমিট বা ইভিকশন পলিসি বিনা একটি ক্যাশ। ডেফিনিশন অনুযায়ী, এটি মেমরি লিক করবে কারণ ক্যাশে রাখা প্রতিটি অবজেক্ট সেখানে সবসময় থাকবে। সময়ের সাথে সাথে, এই ক্যাশ অ্যাপের মেমরির বেশি আরো বেশি দখল করবে, যার ছোট এবং ছোট পার্সেন্টেজ আসলে ইউজ হচ্ছে।
নিচের কোড বিবেচনা করুন যা একটি গেমের হাই স্কোর ফেচ করে। এটি কিছুটা সিমিলার যা আমি الماضيةয় দেখেছি। এটি একটি একسپেনসিভ রিকোয়েস্ট, এবং ক্যাশ সহজে বাস্ট করা যায় যখন এটা চেঞ্জ হয়, তাই আমরা ক্যাশ করতে চাই।
@scores হ্যাশ সম্পূর্ণ অনুচেকড। এটি প্রতিটি ইউজারের প্রতিটি হাই স্কোর ধরে রাখতে বড় হবে — আপনার অনেক ইউজার বা স্কোর থাকলে আইডিয়াল নয়।
একটি Rails অ্যাপে, আমরা সম্ভবত Rails.cache ব্যবহার করব একটি সেনসিবল এক্সপায়রির সাথে (Redis-এ মেমরি লিকও মেমরি লিক!)।
নন-Rails অ্যাপে, আমরা হ্যাশ সাইজ লিমিট করতে চাই, পুরনো বা লিস্ট রিসেন্টলি ইউজড আইটেমস ইভিক্ট করে। LruRedux একটি ভালো ইমপ্লিমেন্টেশন।
এই লিকের একটি আরও সূক্ষ্ম ভার্সন হলো লিমিটেড ক্যাশ, কিন্তু যার কিজ আर्बিট্রারি সাইজের। যদি কিজ বড় হয়, ক্যাশও বড় হবে। সাধারণত আপনি এটিকে হিট করবেন না। কিন্তু, আপনি যদি অবজেক্ট JSON হিসেবে সিরিয়ালাইজ করে সেটি কি হিসেবে ব্যবহার করেন, ডাবল চেক করুন যে আপনি যেসব জিনিস ব্যবহারের সাথে বড় হয় সেগুলো সিরিয়ালাইজ করছেন না — যেন একজন ইউজারের রিড মেসেজের লিস্ট।
সার্কুলার রেফারেন্স
সার্কুলার রেফারেন্স কান গারবেজ কলেক্টেড হয়। Ruby-তে গারবেজ কালেকশন "Mark and Sweep" অ্যালগরিদম ব্যবহার করে। ভেরিয়েবল উইডথ অ্যালোকেশন ইন্ট্রোডিউস করার সময়, Peter Zhu এবং Matt Valentine-House এই অ্যালগরিদম কীভাবে কাজ করে সেটি চমৎকার ব্যাখ্যা করেছিলেন।
অসেনশিয়ালি, দুটি ফেজ আছে: মার্কিং এবং সুইপিং।
-
মার্কিং ফেজে, গারবেজ কলেক্টর রুট অবজেক্টগুলোতে (ক্লাস, গ্লোবাল, ইত্যাদি) শুরু করে, সেগুলো মার্ক করে, এবং তারপর তাদের রেফারেন্সড অবজেক্টগুলোতে তাকায়।
তারপর সে সকল রেফারেন্সড অবজেক্ট মার্ক করে। যেসব রেফারেন্সড অবজেক্ট ইতিমধ্যে মার্কড, সেগুলো আবার দেখা হয় না। এটি চলতে থাকে যতক্ষণ না আর কোনো অবজেক্ট থাকে না দেখার জন্য — অর্থাৎ সকল রেফারেন্সড অবজেক্ট মার্কড হয়ে গেছে।
-
গারবেজ কলেক্টর তারপর সুইপিং ফেজে যায়। যেকোনো অবজেক্ট যেটি মার্কড নয়, সেটি ক্লিন আপ হয়।
অতএব, লাইভ রেফারেন্সدار অবজেক্টও ক্লিন আপ হয়তে পারে। যতক্ষণ না একটি রুট অবজেক্ট ইভেনচুয়ালি একটি অবজেক্টকে রেফারেন্স করে, সেটি কলেক্টেড হবে। এইভাবে, সার্কুলার রেফারেন্সવાલা অবজেক্টের ক্লাস্টারও গারবেজ কলেক্টেড হতে পারে।
অ্যাপ্লিকেশন পারফরম্যান্স মনিটরিং: ইভেন্ট টাইমলাইন এবং alokেটেড অবজেক্টস গ্রাফ
এই সিরিজের প্রথম পার্টে বলা হয়েছে, যেকোনো প্রোডাকশন-লেভেল অ্যাপকে কিছু ফর্মের অ্যাপ্লিকেশন পারফরম্যান্স মনিটরিং (APM) ব্যবহার করা উচিত।
অনেক অপশন আছে, নিজের বানানো পর্যন্ত (শুধু বড় টিমের জন্য রিকমেন্ডেড)। একটি APM থেকে আপনার পাওয়া উচিত একটি কী ফিচার হচ্ছে একটি অ্যাকশন (বা ব্যাকগ্রাউন্ড জব) কত alokেশন করে সেটি দেখার ক্ষমতা। ভালো APM টুলস এটি ব্রেক ডাউন করবে, জানাবে alokেশনগুলো কোথা থেকে আসছে — কন্ট্রোলার, ভিউ, ইত্যাদি।
এটিকে প্রায়শই 'ইভেন্ট টাইমলাইন' বলা হয়। বোনাস পয়েন্ট যদি আপনার APM আপনাকে কাস্টম কোড লিখতে দেয় যা টাইমলাইন আরও ব্রেক ডাউন করে।
একটি Rails কন্ট্রোলারের জন্য নিচের কোড বিবেচনা করুন।
যখন APM দ্বারা রিপোর্ট করা হয়, 'ইভেন্ট টাইমলাইন' কিছু এইরকম দেখতে পারে AppSignal-এর স্ক্রিনশট থেকে।

এটি ইনস্ট্রুমেন্ট করা যাতে আমরা দেখতে পাই কোডের কোন অংশ alokেশন করে টাইমলাইনে। রিয়েল অ্যাপে, এটি কোড থেকে কম Ова svatো 😅
এখানে একটি ইনস্ট্রুমেন্টেড ইভেন্ট টাইমলাইন উদাহরণ, আবার AppSignal থেকে:

কোথায় ইনস্ট্রুমেন্ট করবেন সেটি বুঝতে কঠিন হতে পারে। আপনার অ্যাপ্লিকেশনের কোড বাস্তবে বুঝার কোনো অপশন নেই, কিন্তু কিছু সিগন্যাল আছে যেগুলো 'smells' হিসেবে কাজ করতে পারে।
আপনার APM যদি GC রান বা alokেশন ওভার টাইম সারফেস করে, আপনি স্পাইক খুঁজতে পারেন যাতে বুঝতে পারেন সেগুলো নির্দিষ্ট এন্ডপয়েন্ট হিট বা নির্দিষ্ট রানিং ব্যাকগ্রাউন্ড জবের সাথে মেলে কিনা। এখানে AppSignal-এর Ruby VM Magic Dashboard থেকে আরেকটি উদাহরণ:

এইভাবে alokেশন দেখে, আমরা আমাদের সার্চ ন্যারো ডাউন করতে পারি মেমরি সমস্যা তদন্ত করার সময়। এটি memory_profiler এবং derailed_benchmarks কেমন দক্ষতার সাথে ব্যবহার করতে পারি সেটি অনেক সহজ করে দেয়।
AppSignal-এর Ruby gem-এর লেটেস্ট অ্যাডিশনসমূহ পড়ুন, যেন alokেশন এবং GC স্ট্যাটস ট্র্যাকিং।
উপসংহার
এই পোস্টে, আমরা কিছু টুলস গভীরে গিয়েছি যা মেমরি লিক খুঁজে বের করতে এবং ফিক্স করতে সাহায্য করতে পারে, যার মধ্যে memory_profiler, derailed_benchmarks, perf:mem_over_time, perf:objects, perf:heap_diff, AppSignal-এ ইভেন্ট টাইমলাইন এবং alokেটেড অবজেক্টস গ্রাফ।
আশা করি এই পোস্ট, পার্ট وان সহ, আপনার Ruby অ্যাপের মেমরি লিক ডায়গনোস ও সর্ট আউট করার জন্য উপযোগী হবে।
আমরা যে টুলস ব্যবহার করেছি সেগুলো সম্পর্কে আরও পড়ুন:
memory_profilerderailed_benchmarks- লিকি Rails অ্যাপ
অতিরিক্ত ডিটেইলড পড়াশোনা:
GCমডিউল ডকুমেন্টেশনObjectSpaceমডিউল ডকুমেন্টেশন- Garbage Collection Deep Dive
- Variable Width Allocation
শুভ কোডিং!
ন.ব. আপনি যদি Ruby Magic পোস্টগুলো প্রকাশ होते সাথে সাথে পড়তে চান, আমাদের Ruby Magic নিউজলেটার সাবস্ক্রাইব করুন এবং একটাও পোস্ট মিস করবেন না!