یک ایمپورتِ پویایِ (dynamic) یک تک آیکون باعث از کار افتادن شدید سرور توسعه در یک ماشین ۱۶ گیگابایتی Windows Subsystem for Linux 2 (WSL2) شد و فرآیند vmmemWSL را مجبور کرد تا تمام حافظه موجود را بلعیده و کل پنجره لینوکس را فریز کند. این کرش در حین اجرای یک پروژه Next.js 16 که از Turbopack استفاده میکند رخ داد، و این اتفاق با وجود داشتن یک فایل .wslconfig که استفاده از RAM ماشین مجازی را محدود میکرد، افتاد.
چرا یک ایمپورت کوچک میتواند به یک هیولا تبدیل شود
توسعهدهنده سعی داشت آیکونها را در زمان اجرا (runtime) با یک نقطه ورود پویا (dynamic entry point) حل (resolve) کند. این ایمپورت، یک پکیج آیکون را فراخوانی کرد که شامل تقریباً ۹,۰۰۰ ماژول است. Turbopack، باندلر مبتنی بر Rust که قدرت سرور توسعه Next.js 16 را تأمین میکند، برای هر پکیجی که با آن در تماس است، یک نقشه کامل از ماژولها (module map) میسازد. در حالت توسعه (dev mode)، این نقشه در حافظه قرار دارد و با هر تغییر در فایلها بهروز میشود. بارگذاری کل پکیج آیکون، Turbopack را مجبور کرد تا مقدار زیادی RAM اختصاص دهد که به سرعت به سقف تعیینشده در فایل .wslconfig رسید. به محض رسیدن به این محدودیت، ماشین مجازی WSL از پاسخگویی باز ایستاد؛ کلید Ctrl + C هیچ کاری انجام نداد و تنها راه خروج، خاموش کردن اجباری سیستمعامل میزبان ویندوز بود.
یک بیلدِ تولید (production build) از همان کد با موفقیت انجام شد، زیرا باندلر گراف را تنها یک بار کامپایل کرده، داراییها (assets) را منتشر میکند و سپس خارج میشود. اما سرور توسعه، گراف را در حافظه نگه میدارد تا قابلیت hot-reloading را فراهم کند. بنابراین، یک بیلد موفق در حالت تولید، تضمین نمیکند که محیط توسعه بتواند از همان الگوی ایمپورت جان سالم به در ببرد.
پیامدهای گستردهتر
توسعهدهندگانی که در ویندوز روی زنجیرههای ابزار (toolchains) مبتنی بر لینوکس کار میکنند، برای جداسازی استفاده از منابع به WSL2 متکی هستند. وقتی یک ایمپورت واحد حافظه ماشین مجازی را تمام میکند، کل سیستم میزبان میتواند کند یا غیرقابل پاسخگویی شود و بر سایر کانتینرها یا برنامههای روی همان ماشین تأثیر بگذارد. این حادثه همچنین نشاندهنده عدم تطابق بین فلگهای معمول تنظیم حافظه Node.js و معماری Turbopack است: Turbopack در Rust اجرا میشود، نه در V8؛ بنابراین افزایش فلگ --max-old-space-size در Node هیچ کمکی به مهار مصرف RAM آن نمیکند.
واقعاً چه مشکلی پیش آمد
- نقطه ورود پویا (Dynamic entry point): دستور import از باندلر خواست تا با کل پکیج آیکون به عنوان یک ماژول واحد که به صورت تنبل (lazy) بارگذاری میشود، برخورد کند. Turbopack در پاسخ، شروع به تجزیه (parsing) بیدرنگ (eager) تمام فایلها برای ساخت نقشه ماژول کرد.
- گراف سرور توسعه: برخلاف یک کامپایلِ تولیدِ یکباره، سرور توسعه گراف کامل وابستگیها را در RAM نگه میدارد تا بازخورد آنی از تغییرات فایلها ارائه دهد.
- محدودیت حافظه: فایل .wslconfig استفاده از ماشین مجازی را به بخشی از RAM میزبان محدود کرده بود. وقتی تقاضای Turbopack از آن سقف فراتر رفت، ماشین مجازی فریز شد.
راهکارهایی برای کاهش نصفی مصرف حافظه
توسعهدهنده سه تغییر عملی اعمال کرد که مصرف RAM را از ۳.۶ گیگابایت به ۱.۸۷ گیگابایت کاهش داد و پایداری را بازگرداند:
- از استفاده از نقاط ورود پویا برای داراییهای کوچک خودداری کنید – فقط آیکونهایی را که نیاز دارید ایمپورت کنید، مثلاً:
import { SearchIcon } from 'icon-pack/search'. اگر فقط به تعداد انگشتشماری نیاز دارید، استفاده از SVGهای درونخطی (inline) حتی سبکتر است. - گزینه
optimizePackageImportsرا درnext.config.tsفعال کنید – این گزینه به Turbopack میگوید که ایمپورتها را به جای ریشه پکیج، به مسیرهای فایل مشخص آنها حل کند و از بارگذاری کل درخت پکیج جلوگیری کند. - به فلگهای heap در Node تکیه نکنید – از آنجایی که مصرف حافظه Turbopack توسط زمان اجرای Rust آن کنترل میشود، فلگ
--max-old-space-sizeهیچ تأثیری بر مشکل ندارد.
ادامه دادن به محدودیتهای .wslconfig همچنان توصیه میشود. یک سقف سختگیرانه ممکن است باعث شود ماشین مجازی به جای کرش کردنِ سیستم میزبان ویندوز، متوقف (pause) شود و به شما فرصت دهد تا قبل از از کار افتادن همه چیز، مداخله کنید.
مواردی که باید در گردش کار خود زیر نظر داشته باشید
- داشبوردهای حافظه: ابزارهایی مانند
htopدر داخل WSL یا Windows Task Manager میتوانند نشان دهند که چه زمانی vmmemWSL اوج میگیرد. اگر میزان استفاده به محدودیت تنظیمشده شما نزدیک شد، هشدار (alert) تنظیم کنید. - بررسی حجم پکیجها: قبل از اضافه کردن یک کتابخانه، بررسی کنید که شامل چند ماژول است. پکیجهای بزرگ آیکون، مجموعههای ابزار (utility collections) یا کتابخانههای کامپوننت میتوانند بیسرصدا گراف توسعه را حجیم کنند.
- ایمپورتهای انتخابی: به جای استفاده از wildcard یا ایمپورتهای پویا، از named imports یا مسیرهای مستقیم فایل استفاده کنید، بهویژه در محیط توسعه که باندلر همه چیز را در حافظه نگه میدارد.
- همترازی محیط تولید و توسعه: یک بیلد موفق در حالت تولید را به عنوان یک مرحله اعتبارسنجی جداگانه در نظر بگیرید. سرور توسعه را با یک مانیتور حافظه اجرا کنید تا مشکلاتی را که فقط در حین hot-reloading ظاهر میشوند، شناسایی کنید.
نتیجهگیری
یک پکیج آیکون که به صورت پویا ایمپورت شده است، میتواند آنقدر RAM مصرف کند که یک سرور توسعه Next.js مبتنی بر WSL2 را فلج کند، حتی زمانی که منابع سیستم میزبان به عمد محدود شده باشند. با پرهیز از ایمپورتهای پویای گسترده، فعال کردن بهینهسازی ایمپورت در سطح پکیج و نظارت بر مصرف حافظه، توسعهدهندگان میتوانند محیطهای لینوکسِ درون ویندوز خود را پاسخگو نگه دارند و از خاموش شدنهای اجباری جلوگیری کنند.
