একটি মেমরি লিক হলো একটি অনিচ্ছাকৃত, अनকন্ট্রোলড, এবং অনেন্ডিং মেমরি ইউজেজ বৃদ্ধি। এটা কতটুকু ছোট হোক না কেন, অবশেষে এটি আপনার প্রসেসকে মেমরি শূন্য করে ক্র্যাশ করাবে। এমনকি আপনি যদি পিরিয়ডিক্যালি আপনার অ্যাপ রিস্টার্ট করেন এই ক্র্যাশ এড়াতে (কোনো জাজমেন্ট নেই, আমিও করেছি!), আপনি এখনও মেমরি লিকের পারফরম্যান্স ইম্প্লিকেশন ভোগবেন।
এই পোস্টে, মেমরি লিক সম্পর্কে দু-পর্বের সিরিজের প্রথম পর্বে, আমরা শুরু করব রুবি কীভাবে মেমরি ম্যানেজ করে, গার্বেজ কালেকশন (GC) কীভাবে কাজ করে, এবং কীভাবে একটি লিক খুঁজে বের করতে হয়।
দ্বিতীয় পর্বে, আমরা লিক ট্র্যাক ডাউন করার গভীরে যাব।
চলুন শুরু করি!
রুবি মেমরি ম্যানেজমেন্ট
রুবি অবজেক্টগুলো হিপে সংরক্ষিত থাকে, এবং প্রতিটি অবজেক্ট হিপে একটি স্লট পূরণ করে।
Ruby 3.1-এর আগে, হিপের সকল স্লট একই সাইজের ছিল — একদম ৪০ বাইট। স্লটে ফিট হওয়ার জন্য বড় অবজেক্টগুলো হিপের বাইরে সংরক্ষিত হতো। প্রতিটি স্লটে অবজেক্ট কোথায় মুভ করা হয়েছে তার একটি রেফারেন্স रहता।
Ruby 3.1-এ, String অবজেক্টের জন্য ভেরিয়েবল উইডথ এলোকেশন মার্জ করা হয়েছে। শীঘ্রই, ভেরিয়েবল উইডথ এলোকেশন সকল অবজেক্ট টাইপের জন্য нор্ম হবে।
ভেরিয়েবল উইডথ এলোকেশন পারফরম্যান্স উন্নত করার লক্ষ্যে ক্যাশ লোকালিটি উন্নত করে — একটি অবজেক্টের সকল ইনফরমেশন এক জায়গায় থাকবে দুটি মেমরি লোকেশনের বদলে।
এটি মেমরি ম্যানেজমেন্টের কিছু অংশও সিম্পলিফাই করা উচিত। বর্তমানে, দুটি 'হিপ' আছে:
- রুবি হিপ (অথবা GC হিপ) যা ছোট রুবি অবজেক্ট সংরক্ষণ করে।
- C হিপ (অথবা malloc/transient হিপ) যা বড় অবজেক্ট সংরক্ষণ করে।
একবার ভেরিয়েবল উইডথ এলোকেশন нор্ম হলে,После হিপের প্রয়োজন নেই।
হিপ একটি দিয়ে সাইজে শুরু হয় (ডিফল্ট ১০,০০০ স্লট) এবং অবজেক্ট তৈরি হওয়ার সাথে সাথে ফ্রি স্লটে অ্যাসাইন হয়। যখন রুবি একটি অবজেক্ট তৈরি করার চেষ্টা করে এবং কোনো ফ্রি স্লট উপলব্ধ নেই, গার্বেজ কালেকশন (GC) ঘটে কিছু ফ্রি স্লট উপলব্ধ করতে।
যদি GC-এর পর পর্যাপ্ত ফ্রি স্লট না থাকে, হিপ এক্সপ্যান্ড হবে (এবারে এইরকম আরো)।
এখানে যেসব ফ্যাক্টর আপনি কন্ট্রোল করতে পারেন, তাদের পাশাপাশি এনভায়রনমেন্ট ভেরিয়েবল:
- হিপের ইনিশিয়াল সাইজ -
RUBY_GC_HEAP_INIT_SLOTS - GC হলে উপলব্ধ হওয়া উচিত ফ্রি স্লটের সংখ্যা -
RUBY_GC_HEAP_FREE_SLOTS - হিপ এক্সপ্যান্ড হবে কতটুকু -
RUBY_GC_HEAP_GROWTH_FACTOR
রুবিতে গার্বেজ কালেকশন
রুবিতে গার্বেজ কালেকশন 'stops the world' — GC হলে অন্য কোনো প্রসেস ঘটে না। রুবিতে গার্বেজ কালেকশন (2.1 সन्सকে) জেনারেশনালও, অর্থাৎ গার্বেজ কালেক্টরের দুটি মোড আছে:
- মাইনর GC - 'যুব' অবজেক্টে ইনস্পেক্ট করে (letzt তৈরি হওয়া অবজেক্ট)
- মেজর GC - 'পুরনো' অবজেক্টসহ 'যুব' অবজেক্ট ইনস্পেক্ট করে (সকল অবজেক্ট)
নোট: একটি 'পুরনো' অবজেক্ট ৩বার GC রান পেরিয়ে যেছে, মেজর বা মাইনর।
যখন হিপ ফুল হয়, মাইনর GC আহ্বান করা হয় প্রথমে। যদি সেটি পর্যাপ্ত স্লট ফ্রি করতে না পারে লিমিটের নিচে, মেজর GC আহ্বান করা হবে। শুধু তারপর, যদি এখনও পর্যাপ্ত ফ্রি স্লট না থাকে, হিপ এক্সপ্যান্ড হবে।
মেজর GC মাইনর GC চেয়ে ব্যয়বহুল কারণ এটি অধিক অবজেক্ট দেখে।
জেনারেশনাল GC কেন অধিক পারফরম্যান্ট তার থিওরি হলো অবজেক্ট সাধারণত দুটি ক্যাটেগরিতে পড়ে:
- অবজেক্ট যেগুলো এলোকেট হয় এবং দ্রুত স্কোপের বাইরে চলে যায়। একটি রেইলস অ্যাপে, একটি পেজ রেন্ডার করার জন্য DB থেকে ফেচ করা মডেল রিকোয়েস্ট শেষ হলে স্কোপের বাইরে চলে যায়।
- অবজেক্ট যেগুলো এলোকেট হয় এবং দীর্ঘক্ষণ রাখা থাকে। ক্লাস এবং ক্যাশ অ্যাপের লাইফটাইম 내내 ব্যবহারে থাকার সম্ভাবনা।
মেজর GC মাইনর GC-এর পরও চলে যদি পুরনো অবজেক্টের সংখ্যা একটি নির্দিষ্ট থ্রেশহোল্ডের ওপর থাকে, এমনকি যদি পর্যাপ্ত ফ্রি স্লট থাকে। এই লিমিট হিপের সাইজ বাড়ার সাথে সাথে বাড়ে এবং RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR এনভায়রনমেন্ট ভেরিয়েবল দিয়ে কন্ট্রোল করা যায়।
যখন আপনার একটি লিক থাকে, আপনি এমন অবজেক্ট তৈরি করেন যা ক্লিন আপ করা যায় না — আরও এবং আরও old অবজেক্ট। এর মানে মেজর (খরচবহুল) GC অনেক বেশি বার চলে যা উচিত নয়। যেহেতু GC চলার সময় অন্য কিছু চলে না, এটি সময় যা আপনি বর্জন করছেন।
রুবিতে মেমরি লেয়াউট এবং গার্বেজ কালেক্টর সম্পর্কে আরও পড়ার জন্য এই আর্টিকেলের শেষে কিছু লিংক রেখেছি।
রুবিতে মেমরি লিক কেমন দেখায়?
আপনি যেকোনো ইউনিক্স সিস্টেমে উপলব্ধ সিম্পল টুল ব্যবহার করে মেমরি লিক দেখতে পারেন। নিচের কোডটি উদাহরণ হিসেবে নিন।
এই কোডকে 'লিক' বলা একটু অনিচ্ছাকৃত — এটি শুধু লিক করে! — কিন্তু আমাদের উদ্দেশ্যে সার্ভ করে।
আমরা লিকটি খুব সহজে কমান্ড লাইন থেকে নজরদারি করতে পারি এই প্রোগ্রাম এক টার্মিনালে রান করে এবং watch ব্যবহার করে ps-এর মাধ্যমে সময়ের সাথে সাথে মেমরি বৃদ্ধি দেখে।
pgrep -f "ruby ./leaky.rb" আমাদের জন্য প্রসেস আইডি খুঁজে বের করে, যাতে আমরা ps আউটপুটকে শুধু আমাদের আগ্রহী প্রসেসে সীমাবদ্ধ করতে পারি। আপনি حدস করতে পারেন, এটি প্রসেসের জন্য grep-এর মতো।
watch টুল আমাদের একটি দেওয়া কমান্ডের আউটপুট পোল করতে দেয় এবং ইমপ্লেস আপডেট করে, আমাদের টার্মিনালের ভিতরে একটি লাইভ ড্যাশবোর্ড দিয়ে।
আপনি এমন আউটপুট পাবেন, যা প্রতি কয়েক সেকেন্ডে আপডেট হয়।
আপনি %MEM এবং RSS বৃদ্ধি পাবেন। এগুলো হলো:
%MEM- হোস্ট মেশিনের মেমরির শতাংশ হিসেবে প্রসেস যে পরিমাণ মেমরি ব্যবহার করে।RSS(resident set size) - প্রসেস যে পরিমাণ RAM বাইটে ব্যবহার করে।
এই বেসিক OS-ওনলি ইনফরমেশন লিক আছে কিনা স্পট করার জন্য যথেষ্ট — যদি মেমরি বাড়তে থাকে, মানে আপনার লিক আছে!
গার্বেজ কালেক্টর মডিউল দিয়ে রুবি লিক খুঁজুন
আমরা রুবি কোডের ভিতর থেকেও GC মডিউল দিয়ে লিক ডিটেক্ট করতে পারি।
GC.stat মেথড একটি হ্যাশ রিটার্ন করে অনেক উপকারী ইনফরমেশন সহ। এখানে, আমরা :heap_live_slots-এ আগ্রহী, যেটি হিপে ব্যবহৃত স্লটের সংখ্যা। সেটি :heap_free_slots-এর বিপরীত। লুপের শেষে, আমরা একটি মেজর GC ফোর্স করি এবং ব্যবহৃত স্লটের সংখ্যা প্রিন্ট করি, অর্থাৎ GC-এর পর বাকি থাকা অবজেক্টের সংখ্যা।
আমাদের ছোট প্রোগ্রাম রান করলে, আমরা এই বৃদ্ধি অনন্তকাল দেখি। আমাদের লিক আছে! আমরা GC.stat(:old_objects)-ও একই উদ্দেশ্যে ব্যবহার করতে পারতাম।
যদিও GC মডিউল ব্যবহার করা যায় যদি আমাদের লিক আছে কিনা তা দেখতে এবং (আপনি আপনার puts স্টেটমেন্টস সাথে স্মার্ট হন তবে) লিক কোথায় হচ্ছে তা অনুমান করতে, আমরা ObjectSpace মডিউল দিয়ে লিক হতে পারে এমন অবজেক্টের টাইপ দেখতে পারি।
ObjectSpace.count_objects মেথড একটি হ্যাশ রিটার্ন করে লাইভ অবজেক্টের কাউন্টসহ। T_STRING, উদাহরণস্বরূপ, মেমরিতে লাইভ স্ট্রিং-এর সংখ্যা। আমাদের রদ्दी লিকি প্রোগ্রামের জন্য, এই ভ্যালু প্রতিটি লুপে বৃদ্ধি পায়, GC-এর পরেও। আমরা দেখতে পাচ্ছি যে আমরা স্ট্রিং অবজেক্ট লিক করছি।
AppSignal দিয়ে প্রোডাকশনে অ্যাপ্লিকেশন পারফরম্যান্স মনিটরিং
ps এবং GC-এর সাথে খেলা টয় প্রজেক্টের জন্য একটি সেনসিবল রুট হতে পারে — সেগুলো ফান ও ইনফরমেটিভও! — আমি সুপারিশ করব না সেগুলো আপনার প্রোডাকশন অ্যাপের মেমরি লিক ডিটেকশন সমাধান হিসেবে।
এই জায়গায় আপনি একটি অ্যাপ্লিকেশন পারফরম্যান্স মনিটরিং (APM) টুল ব্যবহার করবেন। আপনি যদি খুব বড় কোম্পানি হন, আপনি নিজে এগুলো বানাতে পারেন। ছোট আউটফিটের জন্য, কিন্তু, একটি APM অফ-দ-শেলফ পিক করা পথ। আপনাকে মাসিক সাবস্ক্রিপশন পে করতে হবে, কিন্তু সেগুলো যে ইনফরমেশন দেয় সেটি তার চেয়ে অনেক বেশি মান দেয়।
মেমরি লিক ডিটেক্ট করতে, আপনি চাইবেন সার্ভার বা প্রসেস মেমরি ইউজ (কখনও কখনও RSS বলা হয়) গ্রাফস সময়ের সাথে সাথে। এখানে AppSignal-এর 'প্রসেস মেমরি ইউজেজ' ড্যাশবোর্ড থেকে একটি হেলথি অ্যাপের উদাহরণ স্ক্রিনশট দেয়া হলো ডিপ্লয় হওয়ার তुर পর:

এবং এখানে একটি আনহেলথি অ্যাপ ডিপ্লয়ের পর:

AppSignal রুবি VM স্ট্যাটস যেমন GC এবং হিপ স্লটও সারফেস করবে, যা আপনাকে মেমরি লিকের জন্য একটি আরও স্পষ্ট সিগন্যাল দিতে পারে। যদি লাইভ স্লটের সংখ্যা বেড়তে থাকে, আপনার লিক আছে!

AppSignal for Ruby সম্পর্কে আরও পড়ুন।
র্যাপ আপ এবং আরও পড়ুন
এই পোস্টে, আমরা রুবির মেমরি ম্যানেজমেন্ট এবং গার্বেজ কালেক্টরের একটি দ্রুত ট্যুর নিলাম। তারপর আমরা ইউনিক্স টুলস এবং রুবির GC মডিউল ব্যবহার করে মেমরি লিক ডায়াগনোস করলাম।
পরবর্তীবার, আমরা দেখব কীভাবে memory_profiler এবং derailed_benchmarks ব্যবহার করে লিক খুঁজে বের করা ও ফিক্স করা যায়।
মধ্যে মধে, আপনি আমাদের ব্যবহৃত টুলস সম্পর্কে আরও পড়তে পারেন:
watchpspgrep
অতিরিক্ত আরও পড়ুন:
GCমডিউল ডকুমেন্টেশনObjectSpaceমডিউল ডকুমেন্টেশন- Garbage Collection Deep Dive
- Variable Width Allocation
হ্যাপি কোডিং, এবং পরবর্তিবার দেখা হবে!
P.S. আপনি যদি Ruby Magic পোস্টগুলো প্রেস থেকে বের হওয়ার সাথে সাথে পড়তে চান, আমাদের Ruby Magic নিউজলেটার সাবস্ক্রাইব করুন এবং কখনো একটি পোস্ট মিস করবেন না!