Một lệnh import icon động duy nhất đã làm sập máy chủ phát triển (dev server) trên một máy chạy Windows Subsystem for Linux 2 (WSL2) với 16 GB RAM, khiến tiến trình vmmemWSL ngốn sạch toàn bộ bộ nhớ khả dụng và làm đóng băng toàn bộ cửa sổ Linux. Sự cố xảy ra khi đang chạy một dự án Next.js 16 sử dụng Turbopack, mặc dù đã có tệp .wslconfig để giới hạn mức sử dụng RAM của VM.

Tại sao một lệnh import nhỏ có thể trở thành một "con quái vật"

Nhà phát triển đã cố gắng giải quyết các icon tại thời điểm thực thi (runtime) bằng một điểm truy cập động (dynamic entry point). Lệnh import này đã kéo theo một gói icon chứa khoảng 9.000 module. Turbopack, trình đóng gói (bundler) dựa trên Rust hỗ trợ dev server của Next.js 16, sẽ xây dựng một bản đồ module (module map) đầy đủ cho mọi package mà nó chạm tới. Ở chế độ dev, bản đồ này nằm trong bộ nhớ và được cập nhật sau mỗi lần thay đổi tệp. Việc tải toàn bộ gói icon đã buộc Turbopack phải cấp phát một lượng lớn RAM, nhanh chóng chạm đến giới hạn cứng được thiết lập bởi tệp .wslconfig. Khi đạt đến giới hạn, VM của WSL ngừng phản hồi; nhấn Ctrl + C không có tác dụng và cách duy nhất để thoát ra là buộc phải tắt máy chủ Windows (Windows host).

Một bản build production của cùng mã nguồn đó đã thành công vì trình đóng gói chỉ biên dịch đồ thị một lần, xuất các tài nguyên (assets) và thoát. Tuy nhiên, dev server lại giữ đồ thị đó trong bộ nhớ để cho phép tính năng hot-reloading. Do đó, một bản build thành công không đảm bảo rằng môi trường dev có thể chịu đựng được cùng một kiểu import đó.

Những hệ lụy rộng hơn

Các nhà phát triển làm việc với các bộ công cụ (toolchains) dựa trên Linux bên trong Windows dựa vào WSL2 để cô lập việc sử dụng tài nguyên. Khi một lệnh import duy nhất làm cạn kiệt bộ nhớ của VM, toàn bộ máy chủ có thể trở nên chậm chạp hoặc không phản hồi, ảnh hưởng đến bất kỳ container hoặc ứng dụng nào khác trên cùng một máy. Sự cố này cũng làm nổi bật sự không tương thích giữa các cờ tinh chỉnh bộ nhớ Node.js thông thường và kiến trúc của Turbopack: Turbopack chạy trên Rust, không phải V8, vì vậy việc tăng cờ Node --max-old-space-size không có tác dụng gì trong việc kiềm chế mức tiêu thụ RAM của nó.

Điều gì thực sự đã xảy ra

  • Điểm truy cập động (Dynamic entry point): Câu lệnh import yêu cầu trình đóng gói coi toàn bộ gói icon là một module duy nhất được tải lười (lazily-loaded). Turbopack đã phản hồi bằng cách phân tích (parse) mọi tệp một cách chủ động (eagerly) để xây dựng bản đồ module.
  • Đồ thị của dev-server: Khác với việc biên dịch production một lần, dev server giữ lại toàn bộ đồ thị phụ thuộc (dependency graph) trong RAM để cung cấp phản hồi tức thì khi có thay đổi tệp.
  • Giới hạn bộ nhớ: Tệp .wslconfig đã giới hạn VM chỉ được sử dụng một phần RAM của máy chủ. Khi nhu cầu của Turbopack vượt quá giới hạn đó, VM đã bị đóng băng.

Các giải pháp giúp giảm một nửa mức sử dụng bộ nhớ

Nhà phát triển đã áp dụng ba thay đổi thực tế giúp giảm mức sử dụng RAM từ 3,6 GB xuống còn 1,87 GB và khôi phục sự ổn định:

  1. Ngừng sử dụng các điểm truy cập động cho các tài nguyên nhỏ – Chỉ import những icon bạn cần, ví dụ: import { SearchIcon } from 'icon-pack/search'. Nếu bạn chỉ cần một vài icon, sử dụng inline SVGs thậm chí còn nhẹ hơn.
  2. Bật optimizePackageImports trong next.config.ts – Tùy chọn này yêu cầu Turbopack giải quyết các lệnh import về đường dẫn tệp cụ thể thay vì gốc của package, ngăn nó tải toàn bộ cây thư mục của package đó.
  3. Đừng dựa vào các cờ heap của Node – Vì mức sử dụng bộ nhớ của Turbopack được quản lý bởi runtime Rust của nó, nên --max-old-space-size không có tác dụng đối với vấn đề này.

Việc duy trì các giới hạn trong .wslconfig vẫn là điều nên làm. Một giới hạn cứng có thể khiến VM tạm dừng thay vì làm sập máy chủ Windows, giúp bạn có cơ hội can thiệp trước khi mọi thứ bị đình trệ.

Những điều cần lưu ý trong quy trình làm việc của bạn

  • Bảng điều khiển bộ nhớ: Các công cụ như htop bên trong WSL hoặc Windows Task Manager có thể cho thấy khi nào vmmemWSL tăng đột biến. Hãy thiết lập cảnh báo nếu mức sử dụng tiến gần đến giới hạn đã cấu hình.
  • Kiểm tra kích thước package: Trước khi thêm một thư viện, hãy kiểm tra xem nó đi kèm bao nhiêu module. Các gói icon lớn, các bộ sưu tập tiện ích (utility collections) hoặc các thư viện component có thể âm thầm làm phình to đồ thị dev.
  • Import có chọn lọc: Ưu tiên sử dụng named imports hoặc đường dẫn tệp trực tiếp thay vì wildcard hoặc dynamic imports, đặc biệt là trong môi trường dev nơi trình đóng gói giữ mọi thứ trong bộ nhớ.
  • Sự tương đồng giữa Production và Dev: Hãy coi một bản build production thành công là một bước xác thực riêng biệt. Chạy dev server với một trình giám sát bộ nhớ để bắt các lỗi chỉ xuất hiện trong quá trình hot-reloading.

Bài học rút ra

Một gói icon được import động duy nhất có thể tiêu thụ đủ RAM để làm tê liệt dev server Next.js dựa trên WSL2, ngay cả khi tài nguyên của máy chủ đã được giới hạn một cách có chủ đích. Bằng cách tránh các lệnh import động quá rộng, bật tối ưu hóa import ở cấp độ package và giám sát việc sử dụng bộ nhớ, các nhà phát triển có thể giữ cho môi trường Linux-trong-Windows của họ hoạt động mượt mà và tránh việc phải tắt máy cưỡng bức.