وقتی یه کوئری فقطخواندنی ترابایتها روی دیسک مینویسه
خلاصهٔ کاملتر
نویسنده سالها فکر میکرد کندی گاهبهگاه کوئریهای تحلیلی روی MonetDB بهخاطر اینه که داده تو رم جا نمیشه و دیتابیس داره random I/O میکنه. ولی وقتی بالاخره عددها رو نگاه کرد، دید یه کوئری فقطخواندنی داره ترابایتها داده روی دیسک مینویسه.
تست ساده بود: یه جدول با یه ستون bigint، حدود ۱.۶ میلیارد ردیف و ۱۱۲ میلیون مقدار یکتا. کوئری count(distinct i) روی یه دسکتاپ با ۶۴ گیگ رم و NVMe، یک ساعت و ۱۳ دقیقه طول کشید. CPU فقط حدود ۱۰ درصد کار میکرد، ولی دیسک ۱۰۰ درصد مشغول نوشتن با سرعت ~۱.۹ GB/s بود. تست مقیاس هم نشون داد تا ۱۰۰ میلیون ردیف همهچی خطیه و بعدش یهو سقوط میکنه، یعنی مشکل الگوریتم نیست، یه مرز منابعه.
ریشهی ماجرا اینه که MonetDB ساختارهای موقت بزرگ مثل hash table رو، بعد از یه آستانه، توی یه transient farm میذاره. یعنی یه پوشه از فایلها که با mmap به حافظه وصل میشن و مدیریت حافظهشون با page cache کرنله. مشکل اینه که کرنل صفحههای dirty (یعنی صفحههایی که تو رم عوض شدن ولی هنوز رو دیسک نرفتن) رو بعد از vm.dirty_expire_centisecs، که پیشفرضش ۳۰ ثانیهست، روی دیسک مینویسه، حتی اگه کلی رم خالی باشه. سقفهای dirty_background_ratio و dirty_ratio هم حدود ۱۰ و ۲۰ درصد رم هستن، یعنی روی ۶۴ گیگ فقط ~۶ و ~۱۲ گیگ.
اولین آزمایش انتقال transient farm به tmpfs (یه فایلسیستم که کلاً تو رمه) بود. نتیجه: CPU صد درصد، I/O صفر، و زمان ۱ دقیقه و ۳۷ ثانیه. ولی نویسنده میگه tmpfs برای محیط چندمستأجری خوب نیست. اندازهش ثابته، رم رو رزرو میکنه و اگه داده ازش بزنه بیرون، بهجای کوئری کند، OOM kill میگیری.
راهحل پیشنهادی نویسنده اینه که scratch روی دیسک بمونه ولی سیاست writeback کرنل عوض بشه، با فایلی مثل این:
# /etc/sysctl.d/90-monetdb-writeback.conf
vm.dirty_background_bytes = 17179869184 # 16 GiB: start background flush late
vm.dirty_bytes = 34359738368 # 32 GiB: tolerate the working set dirty
vm.dirty_expire_centisecs = 21474836 # ~2.5 days: don't time-out dirty pages
vm.dirty_writeback_centisecs = 60000 # 10 min: flusher rarely wakesاینجوری صفحههای موقت تا آخر کوئری تو رم میمونن و وقتی فایلها پاک میشن، بدون هیچ نوشتنی آزاد میشن. نتیجه ~۱ دقیقه و ۴۰ ثانیهست، تقریباً مثل tmpfs. نویسنده تأکید میکنه این کار فقط به این دلیل امنه که MonetDB لاگش رو خودش fsync میکنه. اگه نرمافزار دیگهای رو همون ماشین به flusher کرنل تکیه داره، این تنظیم میتونه دوامش رو ضعیف کنه. این عددها هم مخصوص همون محیطن و نباید کورکورانه کپی بشن.
نکات کلیدی:
- زمان کوئری با تنظیمات پیشفرض ~۱ ساعت و ۱۳ دقیقه، با tmpfs ~۱ دقیقه و ۳۷ ثانیه، و با writeback تنظیمشده ~۱ دقیقه و ۴۰ ثانیه بود.
- کرنل لینوکس صفحههای dirty قدیمیتر از ۳۰ ثانیه رو بدون توجه به رم خالی روی دیسک مینویسه.
- رم خالی یعنی کرنل میتونه داده رو نگه داره، نه اینکه حتماً نگه میداره.
- نویسنده توصیه میکنه از *_bytes بهجای *_ratio استفاده بشه تا با اضافه شدن رم، سقف بیصدا عوض نشه.
- این تنظیم فقط وقتی امنه که دیتابیس خودش دوام داده رو با fsync لاگ تضمین کنه.




