Pojedynczy dynamiczny import ikon powalił serwer deweloperski na maszynie z 16 GB pamięci RAM działającej na Windows Subsystem for Linux 2 (WSL2), zmuszając proces vmmemWSL do pochłonięcia całej dostępnej pamięci i zamrożenia całego okna Linuxa. Awaria wystąpiła podczas uruchamiania projektu Next.js 16 korzystającego z Turbopack, mimo posiadania pliku .wslconfig, który ogranicza zużycie pamięci RAM maszyny wirtualnej.

Dlaczego mały import może stać się potworem

Deweloper próbował rozwiązywać ikony w czasie wykonywania (runtime) za pomocą dynamicznego punktu wejścia. Import pobrał pakiet ikon zawierający około 9 000 modułów. Turbopack, bundler oparty na Rust, który napędza serwer deweloperski Next.js 16, buduje pełną mapę modułów dla każdego dotkniętego pakietu. W trybie deweloperskim mapa ta znajduje się w pamięci i jest aktualizowana przy każdej zmianie pliku. Załadowanie całego pakietu ikon zmusiło Turbopack do alokacji ogromnej ilości pamięci RAM, co szybko doprowadziło do uderzenia w sztywny limit ustawiony w pliku .wslconfig. Gdy limit został osiągnięty, maszyna wirtualna WSL przestała odpowiadać; skrót Ctrl + C nie działał, a jedynym wyjściem było wymuszone zamknięcie systemu Windows (hosta).

Produkcyjne budowanie (build) tego samego kodu zakończyło się sukcesem, ponieważ bundler kompiluje graf tylko raz, generuje zasoby i kończy działanie. Serwer deweloperski przechowuje jednak graf w pamięci, aby umożliwić hot-reloading. Pomyślny build nie gwarantuje zatem, że środowisko deweloperskie przetrwa ten sam wzorzec importowania.

Szersze konsekwencje

Deweloperzy pracujący z narzędziami opartymi na Linuxie wewnątrz systemu Windows polegają na WSL2, aby izolować zużycie zasobów. Gdy pojedynczy import wyczerpuje pamięć maszyny wirtualnej, cały host może stać się ociężały lub przestać odpowiadać, co wpływa na wszelkie inne kontenery lub aplikacje na tej samej maszynie. Incydent ten uwypukla również brak dopasowania między typowymi flagami optymalizacji pamięci Node.js a architekturą Turbopack: Turbopack działa w Rust, a nie w V8, więc zwiększenie flagi Node --max-old-space-size nie ma żadnego wpływu na ograniczenie jego zużycia pamięci RAM.

Co właściwie poszło nie tak

  • Dynamiczny punkt wejścia: Instrukcja importu nakazała bundlerowi traktowanie całego pakietu ikon jako pojedynczego modułu ładowanego leniwie (lazy-loaded). Turbopack zareagował agresywnym (eager) analizowaniem każdego pliku w celu zbudowania mapy modułów.
  • Graf serwera deweloperskiego: W przeciwieństwie do jednorazowej kompilacji produkcyjnej, serwer deweloperski przechowuje pełny graf zależności w RAM, aby zapewniać natychmiastową informację zwrotną przy zmianach plików.
  • Limit pamięci: Plik .wslconfig ograniczał maszynę wirtualną do ułamka pamięci RAM hosta. Gdy zapotrzebowanie Turbopack przekroczyło ten limit, maszyna wirtualna zamarzła.

Rozwiązania, które zmniejszyły zużycie pamięci o połowę

Deweloper wprowadził trzy praktyczne zmiany, które zmniejszyły zużycie RAM z 3,6 GB do 1,87 GB i przywróciły stabilność:

  1. Przestań używać dynamicznych punktów wejścia dla małych zasobów – Importuj tylko te ikony, których potrzebujesz, np. import { SearchIcon } from 'icon-pack/search'. Jeśli potrzebujesz tylko kilku, osadzone (inline) pliki SVG są jeszcze lżejsze.
  2. Włącz optimizePackageImports w next.config.ts – Ta opcja instruuje Turbopack, aby rozwiązywał importy do konkretnych ścieżek plików zamiast do głównego katalogu pakietu, co zapobiega ładowaniu całego drzewa pakietu.
  3. Nie polegaj na flagach sterty (heap) Node – Ponieważ zużycie pamięci przez Turbopack jest zarządzane przez jego środowisko uruchomieniowe Rust, flaga --max-old-space-size nie ma wpływu na ten problem.

Warto nadal utrzymywać limity w pliku .wslconfig. Sztywny limit może spowodować wstrzymanie działania maszyny wirtualnej zamiast awarii systemu Windows, co daje szansę na interwencję, zanim wszystko zostanie całkowicie zablokowane.

Na co zwracać uwagę we własnym workflow

  • Pulpity monitorowania pamięci: Narzędzia takie jak htop wewnątrz WSL lub Menedżer zadań systemu Windows mogą ujawnić, kiedy następuje skok zużycia vmmemWSL. Ustaw alerty, jeśli zużycie zbliża się do skonfigurowanego limitu.
  • Audyty rozmiaru pakietów: Przed dodaniem biblioteki sprawdź, ile modułów zawiera. Duże pakiety ikon, kolekcje narzędziowe lub biblioteki komponentów mogą po cichu rozdmuchać graf deweloperski.
  • Selektywne importy: Preferuj importy nazwane lub bezpośrednie ścieżki do plików zamiast importów wieloznacznych (wildcard) lub dynamicznych, szczególnie w środowisku deweloperskim, gdzie bundler utrzymuje wszystko w pamięci.
  • Zgodność środowiska produkcyjnego i deweloperskiego: Traktuj pomyślny build produkcyjny jako oddzielny krok walidacji. Uruchamiaj serwer deweloperski z monitorem pamięci, aby wyłapać problemy, które pojawiają się tylko podczas hot-reloadingu.

Wnioski

Pojedynczy, dynamicznie importowany pakiet ikon może zużyć tyle pamięci RAM, że sparaliżuje serwer deweloperski Next.js oparty na WSL2, nawet gdy zasoby hosta są celowo ograniczone. Unikając szerokich importów dynamicznych, włączając optymalizację importów na poziomie pakietu i monitorując zużycie pamięci, deweloperzy mogą utrzymać responsywność swoich środowisk Linux-w-Windows i uniknąć wymuszonych restartów systemu.