فشرده سازی هارددیسک برای افزایش کارایی


انتقال اطلاعات در سطح هارددیسک، کندترین نوع تبادل اطلاعات است. این کندی در در سیستم‌عاملی مثل گنو بسیار تاثیر گذار است، چون وابستگی به فایل‌ها در تمام سطوح دیده می‌شود و از طرف دیگر فایل‌سیستم‌های یونیکسی برای کاهش Internal Fragmentation تاثیر External Fragmentation را نادیده می‌گیرند، در حالی که این عامل مهمی در خواندن متوالی فایل‌ها ـ در مقابل خواندن یک فایل مجزا ـ است. با در نظر گرفتن این عوامل، یک راه حل برای بازیابی افت سرعت، استفاده از فشرده سازی برای انتقال اطلاعات در هارددیسک است. فایل سیستم Squash یک فایل سیستم فشرده است که به همراه یک سیستم فایل unionfs برای ساخت دیسک‌های زنده استفاده می‌شود. استفاده از فایل سیستم فشرده دو دست‌آورد منجر به افزایش سرعت دارد:

  1. سرعت خواندن اطلاعات از هارددیسک افزایش پیدا می‌کند؛ چرا که اطلاعات کمتری خوانده می‌شود.
  2. با از بین رفتن External Fragmentation ها، فاصله بین اطلاعات در هارددیسک کمتر شده و فایل‌های بیشتری در زمان قبلی خوانده می‌شوند.

البته کاهش حافظه مصرفی از هارددیسک برای من مهم نیست! (: مراحل کار بسیار ساده‌است، ولی دستورات زیر را چشم بسته اجرا نکنید.

تغییرات کرنل

فایل سیستم Squash رو به کرنل اضافه می‌کنیم. بهتر است به صورت ماژول اضافه نشود و در خود کرنل گنجانده شود. از یک طرف باید همیشه و خیلی زود load شود و همچنین برای فشرده سازی /lib کمتر با مشکل مواجه می‌شوید.

نصب SquashFS

بسته squashfs-tools در جنتو و آرچ موجود است. آن‌را در توزیع خود نصب کنید.

فشرده سازی پارتیشن

فضایی را برای هاست فایل سیستم‌های Squash اختصاص می‌دهیم. حجم مورد نیاز تا حد زیادی بستگی به ماهیت فایل‌های فایل سیستم مورد نظر و بلاک سایز آن دارد. معمولاً سایز پارتیشن فشرده حدود دو پنجم فایل سیستم اصلی است. ما دو برابر این میزان رو در نظر می‌گیریم تا در مرحله ۶ مشکلی نداشته باشیم. من به عنوان هاست از پارتیشن XFS با بلاک سایز ۴ کیلوبایت روی LVM استفاده کردم و از مسیر /sqsh برای قرار دادن ایمیج‌ها استفاده کردم. در این مسیر یک پوشه برای هر فایل سیستم می‌سازیم. فعلاً فقط پارتیشن /usr را فشرده می‌کنیم:

# cd /sqsh
# mdkir -p usr/{ro,rw}
# mksquashfs /usr /sqsh/usr/usr.sfs -b 65536

ساخت فایل سیستم Squash زمان‌بر است. با مشاهده mksquashfs -h چندین گزینه برای خاموش کردن ویژگی‌هایی می‌بینید که باعث انجام سریع‌تر می‌شوند. هدف ما استفاده از بلاک سایز بزرگی مثل ۶۴ کیلوبایت در مقابل بلاک سایزهای سیستم فایل‌های سنتی است که محدود به ۴ کیلوبایت هستند. بلاک سایز بزرگ‌تر برای فشرده سازی قسمت‌های بزرگ‌تر اطلاعات و در نتیجه درصد فشرده‌سازی بیشتر است.

در دستور بالا ممکن است مسیرهایی وجود داشته باشند که نیازی به فشرده سازی نداشته باشند. مثلاً /usr/portage/distfiles در جنتو یکی از این مسیرها است. این مسیر پیش‌فرض ذخیره شدن سورس کد برنامه ها است. ما نیازی به فشرده سازی این مسیر نداریم، چرا که خود فایل‌ها آرشیوهای فشرده شدن و فشرده‌سازی حجم زیادی از اطلاعات هم بسیار زمان‌بر و در این مورد بی‌فایده‌است. در این سیستم distfiles در یک پارتیشن دیگر روی lvm قرار دارد و کار با umount کردن انجام می‌شود. اگر شما چنین پوشه‌ای دارید که نمی‌خواهید فشرده کنید، می‌توانید آن رو از /usr خارج کنید و یک لینک به آن بزنید:

# mkdir /sqsh/not-sqshed/
# mv /usr/portage/distfiles /sqsh/not-sqshed/
# ln -s /sqsh/not-sqshed/distfiles /usr/portage/distfiles

شاید ترجیح دهید پوشه‌های /usr/local و /usr/src را فشرده نکنید، بهتر است چنین پوشه‌هایی که بیشتر تغییر می‌کنند، فشرده نشوند.

خواندن از پارتیشن فشرده

سیستم فایل Squash ساخته شد. این یک فایل فشرده شده است بنابراین اضافه کردن به یا پاک کردن اطلاعات از آن ممکن نیست. به همین خاطر در دیسک‌های زنده که فقط خواندنی هستند به وفور استفاده می‌شوند. به همین دلیل هم توصیه کردیم /usr/src و یا /usr/local را فشرده نکنیم. فایل‌های /usr کمتر از مسیرهای استاندارد دیگر گنو تغییر می‌کنند ولی حداقل با نصب و حذف برنامه‌ها، فایل‌های این مسیر را تغییر می‌دهیم.

یک راه حل این است که فایل‌هایی را که بعداً اضافه می‌شوند، جایی نگه داریم و بعد از مدتی که این فایل‌ها زیاد شدند -یا فایل‌های زیادی را از فایل سیستم Squash حذف کردیم- دوباره فایل سیستم Squash را بسازیم. برای این کار از aufs استفاده می‌کنیم. aufs این امکان را به ما می‌دهد که اطلاعات خواندنی و نوشتنی یک پارتیشن را در دو مسیر متفاوت قرار دهیم.

برای نصب aufs بسته aufsN در جنتو یا آرچ موجود است. N بسته به ورژن لینوکس ۲ یا ۳ است.

تغییرات /etc/fstab

فایل /etc/fstab را تغییر می‌دهیم:

/etc/fstab

/sqsh/usr/usr.sfs   /sqsh/usr/ro   squashfs   loop,ro   0 0
usr    /usr    aufs    udba=reval,br:/sqsh/usr/rw:/sqsh/usr/ro  0 0

مسلماً سطرهای مربوط به /usr - اگر از پارتیشن جدا برای /usr یا /usr/local استفاده می‌کردید- یا ترتیب آنها را هم تغییر می‌هدید.

تمام شد. با راه‌اندازی دوباره می‌توانید تفاوت سرعت را احساس کنید.

فشرده سازی مجدد

بعد از مدتی سرعت سیستم‌فایل مجددا کم می‌شود. این به خاطر تغییراتی در سیستم‌فایل است که هنوز فشرده نشدند. در این شرایط دوباره فایل سیستم Squash را باید بسازیم:

# mksquashfs /usr/ /sqsh/usr/usr_t.sfs -b 65536
# umount -l /usr
# umount -l /sqsh/usr/ro
# mv /sqsh/usr/usr{_t.sfs,.sfs}
# rm -rf /sqsh/usr/rw/*
# mount /sqsh/usr/ro
# mount /usr

دستورات فوق می‌توانند به صورت cron job اجرا شوند ولی بهتر است به صورت یک اسکریپت در /sqsh باشند و هر زمان خواستید اجرا کنید.

فشرده سازی سایر پارتیشن‌ها

همین کارو می‌توانید برای پوشه‌ها یا پارتیشن‌های دیگه انجام بدهید. مسیرهایی که اطلاعات‌شان دائم تغییر می‌کنند (مثلاً var یا home) مناسب فشرده‌سازی نیستند. می‌توانید محتویات پارتیشن قبلی یا پوشه‌ای که فایل سیستم Squash روی اون mount می‌شود را پاک کنید؛ ولی دقت کنید Dependecy های سیستم قبل از اینکه ماژول aufs فراهم شود. ماژول aufs به چهار فایل اجرایی نیاز دارد. اگر پارتیشن root را /dev/sda5 فرض کنیم:

# mkdir t
# mount /dev/sda5 t
# mkdir t/usr/bin/
# cp /usr/bin/{comm,diff,paste,tee} t/usr/bin/
# umount t
# rmdir t

بنابراین چهار فایل اجرایی در /usr/bin قرار دارند که برای بارگذاری aufs کافی هستند. سپس تصویر Squash روی /usr مانت می‌شود و دسترسی به سایر فایل‌های /usr/bin هم از این طریق صورت می‌گیرد.

اگر می‌خواهید پوشه /lib را هم فشرده کنید، از پاک کردن محتویات قبلی آن خودداری کنید یا حداقل مطمئن شوید کتاب‌خانه‌هایی که قبل از aufs بارگذاری می‌شوند، همچنان از مسیر قبلی قابل دسترس هستند.

مشاهده تغییرات

برای اندازه گیری تغییرات سرعت دستورات زیر را برای مسیری مورد نظر به صورت فشرده شده و فشرده نشده اجرا کنید. دستور اول لیست فایل‌ها را به صورت تصادفی می‌سازد و دستور دوم زمان کپی کردن فایل‌ها را نشان می‌دهد:

# find $PATH -type f -printf "%s %p\n" | sort -R | awk '{ printf $2; printf "\n" }' > /sort
# time cpio -o --quiet -H newc < /sort > /dev/null

برای نمونه من در حالت فشرده نشده:

# find /usr/portage/net-* -type f -printf "%s %p\n" | sort -R | awk '{ printf $2; printf "\n" }' > /sort
# time cpio -o --quiet -H newc < /sort > /dev/null
0.44s user 1.90s system 2% cpu 1:45.28 total

و در حالت فشرده شده:

0.15s user 7.23s system 87% cpu 8.389 total

تفاوت خیلی زیاد به علت ماهیت فایل‌هایی که کپی شده‌اند نیز است. این فایل‌ها، فایل‌های متنی کوچکی هستند که در صورت فشرده شدن، حجم‌شان بسیار تغییر می‌کند.

مطالعه بیشتر

نشانی اول منبع اصلی و حاوی یک سری بنچمارک است.

اسکریپت‌های من برای این کار

اسکریپت برای آپدیت و لود کردن ماژول‌ها:

/etc/init.d/squashfs

#!/sbin/runscript
     
    depend() {
            need localmount lvm
            before keymaps
            after lvm
    }
     
    start() {
            ebegin "Start aufs"
            mount /squashimg
           
            for dev in $IMGS; do
                    mount "/squashimg${dev}/ro"
                    mount "$dev"
                    eend $? "failed to mount $dev"
            done
     
            mount /usr/portage/distfiles
            eend $? "failed to mount /usr/portage/distfiles"
    }
     
    stop() {
            ebegin "Unmount aufs file systems"
            umount /usr/portage/distfiles
     
            for dev in $IMGS; do
                    umount -l "$dev"
                    umount -r "/squashimg${dev}/ro"
     
                    size=`du -s --block-size=1m "/squashimg${dev}/rw/" | sed -e 's/\s.*//'`
                    if [[ $size -gt 20 ]]; then
                            eerror "Reith: $dev has ${size}MB data uncompressed. execute \`update.sh $dev'"
                    fi
            done
            umount -r /squashimg
            eend $?
    }

یک اسکریپت برای دوباره فشرده کردن پارتیشن‌ها:

update.sh

#!/bin/sh
cd /squashimg
if ! [[ $# -eq 1 ]]; then
        echo "need exactly one argument, name of sub directory in /squashimg"
        exit 1
fi
if [ ! -e /squashimg/$1 ]; then
        echo "$1 is not a valid name"
        exit 2;
fi
if [[ $1 == "usr" ]]; then
       echo "excluding distfiles"
        mksquashfs /$1 $1/$1_t.sfs -b 65536 -e /usr/portage/distfiles/*
else
       mksquashfs /$1 $1/$1_t.sfs -b 65536
fi
umount -l /$1
umount -l $1/ro
mv $1/$1_t.sfs $1/$1.sfs
rm -rf $1/rw/*
mount $1/ro
mount /$1
[[ $1 == "usr" ]] && mount /usr/portage/distfiles