اگر میلیونها فایل کوچک را برای آموزش مدلهای هوش مصنوعی مدیریت میکنید، بزرگترین مانع شما معمولاً پهنای باند داده نیست، بلکه تأخیر در پاسخگویی به متادیتا (Metadata Latency) است. JuiceFS Community Edition 1.4 دقیقاً همین نقطه ضعف را هدف گرفته تا زمان حذف و کپی مجموعهدادههای عظیم را به شدت کاهش دهد.
در سناریوهای دسترسی گسترده به فایلها، مانند آموزش مدلهای هوش مصنوعی و مدیریت مجموعهدادهها، متادیتا اغلب اولین گلوگاه عملکردی است؛ بهویژه زمانی که تعداد فایلها و میزان دسترسیهای همزمان (Concurrency) افزایش مییابد. چه در حال حذف میلیونها فایل کوچک باشید، چه در حال کپی مجموعهدادههای بزرگ یا پیمایش دایرکتوریها تحت فشار ترافیکی بالا، عملکرد متادیتا مستقیماً بر کارایی کل اپلیکیشن اثر میگذارد. در این محیطها، هر درخواست تکفایل معمولاً یک رفتوبرگشت شبکهای (Network Round Trip) مجزا ایجاد میکند. این امر منجر به ایجاد سربار عظیمی از فراخوانیهای سیستم (System Calls) و تعویضهای زمینه (Context Switches) میشود که در نهایت کل خط لوله آموزش مدل را کند میکند.
برای حل این مشکل، JuiceFS سه بهینهسازی اصلی در لایه متادیتا معرفی کرده است: حذف دستهای (Batch Unlink) برای پاکسازی فایلها در مقیاس بزرگ، کپی دستهای (Batch Clone) برای تکثیر متادیتا، و کشینگ سمت کلاینت Redis برای خواندن متادیتای «داغ» (Hot Metadata). این بهبودها باعث کاهش تعداد تایید تراکنشها (Transaction Commits)، کاهش رفتوبرگشتهای شبکهای و حذف جستوجوهای تکراری متادیتا میشوند. این رویکرد برای پژوهشگران هوش مصنوعی که بهطور مکرر مجموعهدادهها را میچرخانند یا نسخهبندی (Version-control) بزرگی روی پیکرههای متنی و تصویری انجام میدهند، حیاتی است.
گلوگاه حذف: Batch Unlink
حذف یک فایل در معماریهایی که متادیتا از داده تفکیک شده است، فرآیند پیچیدهای است و بسیار فراتر از حذف یک ورودی ساده در دایرکتوری است. سیستم باید مراحل زیر را طی کند:
- بهروزرسانی شمارنده ارجاعات اینود (Inode Reference Counts)
- بازپسگیری منابع اینود و فضای ذخیرهسازی
- پردازش ورودیهای سطل زباله (Trash Entries)
- بهروزرسانی آمارهای سهمیه (Quota Statistics)
این عملیاتها معمولاً باید در قالب یک تراکنش واحد تکمیل شوند. در حالت سنتی، استفاده از دستور rm -rf برای هر تکفایل یک تراکنش متادیتای مجزا را تحریک میکند. هر درخواست unlink از پروتکل FUSE عبور کرده، بین فضای هسته (Kernel) و فضای کاربر (User Space) سوئیچ میکند و یک تراکنش جداگانه را فعال میسازد. با رشد تعداد فایلها، سربار ناشی از فراخوانیهای سیستم و رفتوبرگشتهای شبکه بهسرعت انباشته میشود.
JuiceFS پیش از این تلاش کرده بود با دستور juicefs rmr این مشکل را حل کند. برخلاف rm -rf ، دستور rmr لایه FUSE را دور میزند و درخواستهای حذف را مستقیماً به کلاینت میفرستد. همچنین از حذف چندرشتهای (بهطور پیشفرض ۵۰ رشته) پشتیبانی میکند. با این حال، موتور متادیتا همچنان مجبور بود برای هر فایل یک تراکنش اجرا کند. به این معنا که حذف ۱۰۰,۰۰۰ فایل، همچنان به ۱۰۰,۰۰۰ تراکنش نیاز داشت.
قابلیت Batch Unlink این روند را تغییر میدهد. این سیستم چندین عملیات حذف در یک دایرکتوری واحد را در قالب یک تراکنش دستهای (Batch Transaction) ادغام میکند. فایلهای معمولی و لینکهای نمادین (Symbolic Links) را گروهبندی کرده و در یک فراخوانی واحد به موتور متادیتا ارسال میکند. این کار باعث تبدیل تعداد زیادی تراکنش کوچک به تعداد کمی تراکنش بزرگ میشود.
پیادهسازی Batch Unlink
JuiceFS یک رابط (Interface) برای حذف دستهای در لایه موتور متادیتا اضافه کرده است. هنگام پاکسازی بازگشتی (Recursive) یک دایرکتوری، سربار به دو روش کاهش مییابد:
۱. دایرکتوریهای مختلف بهصورت همزمان و با استفاده از حذف چندرشتهای مدیریت میشوند.
۲. در داخل هر دایرکتوری، فایلهای عادی و symlinkها در دستههایی گروهبندی شده و به تابع BatchUnlink ارسال میشوند.
نکته مهم این است که BatchUnlink بهطور مستقیم دایرکتوریها را حذف نمیکند. حذف دایرکتوریها از همان گردش کار بازگشتی استاندارد پیروی میکند: ابتدا محتویات زیر-دایرکتوری خالی میشود و سپس خودِ زیر-دایرکتوری حذف میگردد. این روش باعث حفظ معناشناسی صحیح حذف بازگشتی و جلوگیری از ریسکهای مربوط به سازگاری در ساختار درخت دایرکتوری میشود.

بهرهوری حاصل از این روش بسته به بکاند (Backend) متفاوت است، زیرا سیستم از استراتژیهای دستهبندی متفاوتی برای به حداقل رساندن تایید تراکنشها استفاده میکند:
- بکاندهای SQL (مانند MySQL، PostgreSQL و غیره): پیش از این، هر فایل به توالی مجزایی از دستورات INSERT، DELETE و UPDATE نیاز داشت. اکنون سیستم تمام رکوردهای لبه (Edge Records) برای ورودیهای هدف را در یک پرسوجوی دستهای دریافت کرده و ویژگیهای اینود مربوطه را در یک پرسوجوی دستهای قفلشده میگیرد. سپس حذف لبهها، بهروزرسانی وضعیت اینود (کاهش nlink یا علامتگذاری برای پاکسازی) و درج ورودیهای delfile را همگی در یک تراکنش واحد اجرا میکند.
- بکاند Redis: از خط لوله (Pipeline) و تراکنشهای
MULTI/EXECاستفاده میکند. تمام دستوراتHDEL(حذف dentry)،ZADD(صف برای پاکسازی)،SET(بهروزرسانی ویژگی اینود) وINCRBY(بهروزرسانی شمارنده) برای چندین فایل در یک خط لوله جمعآوری میشوند. برای جلوگیری از مسدود کردن حلقه رویداد تکرشتهای Redis برای مدت طولانی، اندازه هر دسته به ۲۵۰ ورودی محدود شده است. - بکاند TiKV: از قابلیت بومی نوشتن دستهای (Batch Write) در TiKV برای تجمیع چندین حذف در یک تراکنش واحد بهره میبرد. این امر اجازه میدهد ظرفیت نوشتن همزمان بکاند بهطور کامل مورد استفاده قرار گیرد.
در بنچمارکهای انجام شده روی یک دایرکتوری تخت (Flat) شامل ۱۰۰,۰۰۰ فایل با دستور juicefs rmr --threads 16 ، قابلیت Batch Unlink عملکرد را تا ۹۳ برابر نسبت به روشهای سنتی بهبود بخشید؛ بیشترین سود در TiKV و Redis مشاهده شد.
سرعت بخشیدن به نسخهبندی مجموعهداده: Batch Clone
در آموزش هوش مصنوعی، ایجاد Snapshot از مجموعهدادهها بسیار رایج است. دستور juicefs clone امکان کپی سریع را با استفاده مجدد از ارجاعات بلوکهای موجود (بهجای کپی واقعی دادهها) فراهم میکند. بلوکهای داده جدید تنها زمانی تخصیص مییابند که روی نسخه کپی شده نوشته شود، که این کار از سربار زمانی و فضای ذخیرهسازی کپی کامل جلوگیری میکند.
با این حال، کپی کردن یک دایرکتوری بزرگ همچنان از مشکل تراکنشهای «تکبهتک» رنج میبرد. قابلیت Batch Clone این مشکل را با خواندن ورودیهای دایرکتوری بهصورت جریانی (Stream) حل میکند. برای هر دسته، تمام ورودیهای غیر-دایرکتوری جمعآوری شده و در یک عملیات واحد کپی میشوند.
مکانیسم Batch Clone
یک جزئیات فنی کلیدی در اینجا، «پیشتخصیص اینود» (Inode Pre-allocation) است. پیش از ورود به تراکنش، سیستم از nextInode برای تخصیص پیشدستانه اینودهای هدف برای تمام ورودیهایی که قرار است کپی شوند، استفاده میکند. این کار باعث جلوگیری از تداخل قفلها (Lock Contention) میشود که در صورت درخواست مکرر اینود در داخل تراکنش رخ میداد.
پس از ورود به تراکنش، فرآیند مراحل زیر را طی میکند:
- پرسوجوی دستهای تمام ویژگیهای فایل منبع با استفاده از قفلهای ردیفی (Row Locks).
- ساخت تمام دادههای درج برای نودهای هدف، لبهها، چانکها، symlinkها و xattrها.
- درج تمام دادهها در یک دسته واحد.

کپی دستهای (Batch Clone) مشابه حذف دستهای، از قابلیتهای بومی نوشتن دستهای هر بکاند استفاده میکند. بهبود عملکرد به مدل تراکنش، سربار ارتباطات شبکه و کارایی درج دستهای برای رکوردهای متادیتا بستگی دارد. نتایج روی یک دایرکتوری تخت با ۱۰۰,۰۰۰ فایل نشان میدهد:
- MySQL: تقریباً ۲۴ برابر سرعت بیشتر.
- Redis: تقریباً ۵ برابر سرعت بیشتر.
- TiKV: تقریباً ۲ برابر سرعت بیشتر.
حذف تأخیر شبکه: کشینگ سمت کلاینت Redis
حتی با وجود دستهبندی، جستوجوهای مکرر برای متادیتای «داغ» میتواند موتور Redis را تحت فشار قرار دهد. عملیات open("/mnt/jfs/dataset/images/cat.jpg") را در نظر بگیرید. سیستم فایل مجازی لینوکس (VFS) باید هر جزء را تفکیک کند: جستوجوی dataset ، سپس images و در نهایت cat.jpg.
اگر دایرکتوری images شامل صدها هزار فایل باشد و کارهای آموزشی دسترسی تصادفی (Random Access) داشته باشند، هر جستوجو نیاز به یک درخواست GET به Redis دارد. حتی اگر یک پرسوجو تنها چند ده میکروثانیه زمان ببرد، تأخیر شبکه هر جستوجو را به صدها میکروثانیه یا میلیثانیه میرساند. تحت فشار همزمانی بالا، این منجر به رفتوبرگشتهای شبکهای عظیم و افزایش شدید بهرهبرداری از CPU در Redis میشود.
نحوه عملکرد کشینگ سمت کلاینت Redis
JuiceFS قابلیت کشینگ سمت کلاینت Redis 6.0 را پیادهسازی کرده است که به کلاینتها اجازه میدهد کلیدهای داغ را بهصورت محلی کش کرده و در صورت تغییر، اعلانهای ابطال (Invalidation Notifications) را دریافت کنند. JuiceFS دو دسته از متادیتا را کش میکند:
- کش ویژگیهای اینود (Inode Attribute Cache): این کش با کلید شماره اینود کار میکند و دادههای کامل ویژگیها (نوع، اندازه، مجوزها، برچسبهای زمانی) را ذخیره میکند. این قابلیت از طریق مکانیسمهای Hook در لایه درایور Redis پیاده شده است. هنگام پرسوجو، ابتدا کش محلی بررسی میشود و در صورت یافتن (Hit)، پاسخ فوراً بازگردانده میشود. در صورت تغییر متادیتا، کش بهطور خودکار ابطال میگردد.
- کش ورودی دایرکتوری (Directory Entry Cache): این کش با کلید «اینود والد + جداکننده مسیر + نام فایل» کار میکند و نتایج جستوجوی دایرکتوری را ذخیره میکند. منطق جستوجو مستقیماً در مسیر جستوجوی دایرکتوری تعبیه شده است. وقتی ورودیهای یک دایرکتوری ابطال شوند، تمام ورودیهای کش مرتبط با آن دایرکتوری با استفاده از تطبیق پیشوندی (Prefix Matching) پاک میشوند.

سازگاری و مدل BCAST
کشینگ سمت کلاینت در سناریوهای چند-کلاینتی (Multi-mount) یک چالش سازگاری ایجاد میکند. اگر یک کلاینت فایلی را ایجاد، حذف یا تغییر نام دهد، سایر کلاینتها ممکن است متادیتای قدیمی (Stale) را ببینند. برای حل این مشکل، JuiceFS مدل «ردیابی و ابطال پخش گسترده» یا BCAST را معرفی کرده است.
پس از اتصال به Redis، هر کلاینت پیشوندهای کلید متادیتایی را که میخواهد ردیابی کند، اعلام میکند. هرگاه آن کلیدها تغییر کنند، Redis اعلانهای ابطال را برای کلاینتهای مربوطه میفرستد. کلاینت پس از دریافت اعلان، کش ویژگی اینود یا ورودی دایرکتوری مربوطه را پاک میکند تا دسترسیهای بعدی دادههای تازه را دریافت کنند.

علاوه بر این، JuiceFS در هنگام مقداردهی اولیه کلاینت، متادیتای دایرکتوری ریشه (Root) نقطه اتصال را «گرم» (Warm-up) میکند. از آنجایی که این فایلها بیشترین دسترسی را دارند، این گرمکردن باعث بهبود چشمگیر عملکرد کلی میشود.
کاربردها و محدودیتها
کشینگ سمت کلاینت Redis برای سناریوهایی که خواندن بسیار زیاد و نوشتن کم است (Read-heavy, Write-light) ایدهآل است؛ مانند آموزش هوش مصنوعی که مجموعهدادهها معمولاً فقط-خواندنی (Read-only) هستند. این قابلیت بهویژه در استقرار بین مناطق دسترسی (Cross-AZ) که تأخیر شبکه بالاست، مفید است.
نیازمندیها و تنظیمات کلیدی عبارتند از:
- نسخه: Redis نسخه ۶.۰ یا بالاتر مورد نیاز است.
- انقضا: زمان انقضای پیشفرض کش ۱ دقیقه است. این یک شبکه ایمنی برای مواردی است که اعلانهای ابطال به دلیل اختلالات شبکه نمیرسند.
- سازگاری: برای حجم کارهایی با نیاز به سازگاری سختگیرانه، کاربران میتوانند زمان انقضا را کوتاهتر کرده یا کشینگ سمت کلاینت را بهطور کامل غیرفعال کنند.
تحلیل تحریریه
این بهروزرسانی نشاندهنده تغییری در نحوه برخورد سیستمهای فایل توزیعشده با «مشکل فایلهای کوچک» در هوش مصنوعی است. با انتقال منطق از عملیات تکفایل به تراکنشهای دستهای، JuiceFS اذعان میکند که در عصر AI، موتور متادیتا گلوگاه واقعی است، نه I/O دیسک.
برای متخصصان، این بدان معناست که انتخاب بکاند متادیتا اکنون اهمیت بیشتری دارد. جهش ۹۳ برابری در سرعت حذف برای برخی بکاندها نشان میدهد که سربار هرگز سختافزار نبوده، بلکه مدل تراکنش بوده است. معرفی BCAST برای کشینگ Redis نیز JuiceFS را به عملکرد سیستمهای فایل محلی نزدیکتر میکند در حالی که مقیاسپذیری ابری را حفظ میکند.
خلاصه و گامهای بعدی
این سه بهینهسازی مسیرهای مختلفی را هدف قرار دادهاند: Batch Unlink و Batch Clone عملیاتهای مستقل در یک دایرکتوری را در تراکنشهای دستهای ادغام میکنند، در حالی که کشینگ سمت کلاینت Redis تأخیر خواندن را از سطح شبکه به سطح حافظه میآورد.
کاربران باید توجه کنند که BatchUnlink و BatchClone رابطهای داخلی هستند و هنگام استفاده از juicefs rmr و juicefs clone بهطور خودکار اعمال میشوند. چون این عملیاتها فایلهای معمولی را در یک دایرکتوری ادغام کرده و زیر-دایرکتوریها را بهصورت بازگشتی از طریق Goroutineهای همزمان مدیریت میکنند، هرچه دایرکتوری بزرگتر باشد، سود حاصل از آن بیشتر است.
تمام این بهینهسازیها در JuiceFS Community Edition 1.4 در دسترس هستند. کلاینت خود را ارتقا دهید تا این بهبودهای عملکردی را دریافت کنید. برای سوالات بیشتر، به بحثهای JuiceFS در GitHub یا جامعه Discord بپیوندید.




گفتگو