আমি এটি লিখছি কারণ আপনি আমার মতো করে না মাথা বাদাম দেবেন যখন আমি মেমরি স্পাইক শোষণ করতে স্ব্যাপ চালানোর সিদ্ধান্ত নিয়েছিলাম। আমার একটি cgroup ছিল যাতে দুটি প্রক্রিয়া ছিল: একটি Go প্রক্রিয়া যা io.ReadAll কল করে এবং তারপর proto.Unmarshal করে, একটি blob তৈরি করে এবং তারপর একটি গ্রাফ স্ট্রাক্টার তৈরি করে (যা Go-এর আবেদনকারী দ্বারা স্ক্যান হিসাবে চিহ্নিত)। অন্য প্রক্রিয়াটি একটি HTTP সার্ভার যা বেশিরভাগ সময় নীরব থাকে। প্রতিবার যখন কালেক্টর চালায়, তখন এটি স্ক্যান স্প্যান পড়ে, পয়েন্টার দিয়ে পয়েন্টার, এবং সিদ্ধান্ত নেয় কী করতে হয়। তাই আমি ভাবলাম: ঠিক আছে, মেমরি চাপের নিচে, কার্নেল পৃষ্ঠাগুলিকে স্ব্যাপ ডিভাইসে রিমুভ করবে, কিন্তু যেহেতু রিমুভটি প্রতি cgroup এবং প্রতি প্রক্রিয়া নয়, তাই উভয় প্রক্রিয়ার পৃষ্ঠাগুলিও রিমুভ করা হবে — তাই এটি কার্নেল এবং গ্যারবেজ কালেক্টরের মধ্যে স্ব্যাপ-ইন এবং স্ব্যাপ-আউট-এর একটি বেদনাদায়ক নাচে রূপান্তরিত হওয়ার সম্ভাবনা খুবই কম। আমি ভুল ছিলাম। এটি পরীক্ষা করার সময় আমি এমন একটি সমস্যার সম্মুখীন হয়েছিলাম যা আমাকে ক্ষতিগ্রস্ত করতে পারত: Go-এর গ্যারবেজ কালেক্টর তার মেটাডেটা (হিপের বাইরে, একটি অঞ্চলে যা মুক্ত করা হয়নি) স্টপ-দ্য-ওয়ার্ল্ড বিশ্রামে পড়ে, এবং সেই মেটাডেটা স্ব্যাপে থাকতে পারে। আমি MGLRU সক্ষম করে কার্নেল 6.8 ব্যবহার করে Hetzner-এ একটি সিমুলেশন চালিয়েছিলাম। মাঝারি বিশ্রামটি ছিল প্রায় 51 মাইক্রোসেকেন্ড। NVMe-এ মেটাডেটা থাকলে, সবচেয়ে খারাপ বিশ্রামটি 40 মিলিসেকেন্ড ছিল। এই 40 মিলিসেকেন্ডটি কোথায় গেছে তা জানতে আমি একটি ছোট bpf স্ক্রিপ্ট লিখেছিলাম যা স্টপ-দ্য-ওয়ার্ল্ড অবসান পর্যন্ত পৃষ্ঠা ফল্টের সংখ্যা গণনা করে। এটিই ছিল সবচেয়ে খারাপ: 39902 মাইক্রোসেকেন্ড, এর মধ্যে 228টি ফল্ট, 39013 মাই্রোসেকেন্ড ফল্টে। সেই 40 মিলিসেকেন্ডের 39 মিলিসেকেন্ডটি 228টি পৃষ্ঠা ফল্টে ব্যয় করা হয়েছিল। সেই ফল্টগুলি GC-এর বইয়ের মধ্যে ঘটেছিল। এটি একটি সম্ভাব্য ব্যর্থতা মোড। Go-এর GC-এর দুটি পয়েন্টে পৃবী বন্ধ করতে হয়: যখন এটি সোয়াপ শেষ করে, এবং যখন এটি মার্ক শেষ করে। আমরা 30 মিনিটে 312টি এমন বিশ্রাম পেয়েছিলাম। তাই এখানে কেন এটি ঘটে: রানটাইম এই পৃষ্ঠাগুলিকে বরাদ্দ করে। এগুলি মুক্ত করা হয়নি, কিন্তু পুনরায় ব্যবহার করা হয়। এই পৃষ্ঠাগুলি GC চক্রে পড়ে। কারণ কার্নেল পৃষ্ঠাগুলিকে বয়স অনুসারে রিমুভ করে, তাই এটি সবচেয়ে কম আগে অ্যাক্সেস করা পৃষ্ঠাগুলিকে স্ব্যাপে পাঠায়। GC চালু হয়, পৃবী বন্ধ করে, এই পৃষ্ঠাগুলিকে পড়ার চেষ্টা করে, কিন্তু এখন আমাদের একটি প্রধান পৃষ্ঠা ফল্ট আছে। কার্নেলকে PTE পড়তে হবে, তারপর do_swap_page কল করতে হবে, একটি নতুন ফ্রেম খুঁজে বের করতে হবে, এটিকে cgroup-এ চার্জ করতে হবে, পৃষ্ঠাগুলি পড়তে হবে, bio জমা দিতে হবে, ডিস্ক অপেক্ষা করতে হবে এবং তাদের মেমরিতে ফেরত দিতে হবে — শুধুমাত্র সংক্ষেপ করার জন্য। এই 40 মিলিসেকেন্ডটি প্রথমে অপরাধী মনে হয়। কিন্তু আমরা স্টপ-দ্য-ওয়ার্ল্ড বিশ্রামের কথা বলছি। এই 40 মিলিসেকেন্ডটি মানে আপনি যে কোনও জিনিস থেমে গেছে — Go-এর শব্দভাণ্ডারে, প্রতিটি P বন্ধ করা হয়েছে, তাই উদাহরণস্বরূপ, যদি একটি goroutine I/O অপেক্ষা করছিল, তবে সেই বিশ্রামের সময় I/O ফিরে আসতে পারে এবং কাউন তা হ্যান্ডেল করতে পারে না। 40 মিলিসেকেন্ড মাঝারি বিশ্রামের 800 গুণ। এটি পরীক্ষার সময় প্রতি মেমরি স্পাইকের সাথে দুটো বা তিন বার ঘটে। এটা বেশি।
উৎস: Hacker News · সারাংশ: HeadlinesBriefing