Technikalia

Jak naprawiłem wyciek pamięci w Node.js u lokalnego klienta

Autor Eric Kudler, Mentor Fullstack·12 listopada 2024·6 min czytania

Zaczęło się od telefonu we wtorek o 8:14. Aplikacja u klienta z Gdańska wywalała się regularnie co 3 godziny i 12 minut. Pokażę wam, jak namierzyliśmy ten błąd bez strzelania na oślep i co z tego wynika dla Twojego kodu.

Aplikacja, która puchła w oczach

Mój klient prowadzi mały system rezerwacji dla 48 lokalnych punktów usługowych. Wszystko działało sprawnie, dopóki nie dodali nowej funkcji generowania raportów w zeszły czwartek. Serwer miał 2 GB RAM-u, co przy Node.js zazwyczaj wystarcza z nawiązką. Jednak po wdrożeniu aktualizacji zużycie pamięci rosło o około 14 MB na każdą minutę pracy systemu. Po około trzech godzinach serwer osiągał limit i po prostu padał.

Najgorsze było to, że działo się to w godzinach szczytu, kiedy najwięcej osób próbowało zarezerwować termin. Logi pokazywały tylko ogólny błąd o braku pamięci na stercie, ale zero konkretów, która funkcja zawiniła. Siedzimy nad tym, aż ruszy – tak obiecałem właścicielowi firmy, więc we wtorek rano usiadłem do konsoli, żeby wyłapać ten wyciek przed obiadem.

Aplikacja, która puchła w oczach

Narzędzia zamiast zgadywania

Żadnej magii, tylko logika. Odpaliłem aplikację lokalnie z flagą inspect i podpiąłem się pod Chrome DevTools. To darmowe narzędzie, które każdy programista ma pod ręką, a mało kto wyciska z niego maksa. Zrobiłem pierwszy zrzut pamięci (heap snapshot) zaraz po starcie, kiedy system ważył 42 MB. Potem użyłem prostego skryptu, który zasymulował 127 wejść na nową stronę z raportami.

Drugi zrzut pamięci pokazał już 184 MB. Porównanie tych dwóch plików w widoku Comparison od razu wywaliło na wierzch winowajcę. Okazało się, że nowa tablica o nazwie activeConnections nie była czyszczona po zamknięciu zapytania do bazy danych. Każdy wygenerowany raport zostawiał po sobie śmieci w pamięci, których mechanizm czyszczenia (Garbage Collector) nie mógł ruszyć, bo ciągle widniały jako potrzebne obiekty.

W Node.js najtrudniejszy błąd to często jedna mała tablica, o której zapomniałeś podczas pisania kodu przy kawie.

11 linijek kodu, które uratowały system

Naprawa nie była skomplikowana, ale znalezienie tego miejsca zajęło nam dokładnie 2 godziny i 18 minut. Musieliśmy dodać blok finally, który jawnie usuwał referencje do obiektów bazy danych po zakończeniu operacji. W JavaScript pamięć jest zwalniana automatycznie, ale jeśli trzymasz coś w globalnej zmiennej lub zapomnisz zamknąć subskrypcję, serwer w końcu się zapcha.

Po wdrożeniu poprawki w piątek o 14:30, zużycie pamięci ustabilizowało się na poziomie 89 MB i nie drgnęło przez kolejne 12 godzin pracy. Klient przestał dostawać powiadomienia o awariach, a ja mogłem wrócić do mentoringu. To pokazuje, że programowanie to nie tylko klepanie nowych funkcji, ale przede wszystkim umiejętność naprawiania tego, co już działa, ale robi to źle.

11 linijek kodu, które uratowały system

Czego nauczysz się na moich konsultacjach?

Na tym przykładzie uczę moich kursantów, że debugowanie to proces, a nie loteria. Zamiast zmieniać losowe linie kodu i liczyć na cud, bierzemy profilery i sprawdzamy fakty. Na mentoringu Eric Kudler Fullstack Mentoring przechodzimy przez 7 takich realnych przypadków, które spotkały mnie w pracy w ciągu ostatnich 8 lat. Bez lania wody o teorii, której nigdy nie użyjesz w małej firmie.

Programowanie to rzemiosło. Jeśli chcesz budować portfolio, które działa i robi wrażenie na rekruterach, musisz umieć wytłumaczyć, jak optymalizujesz kod. Firmy szukają ludzi, którzy potrafią zaoszczędzić im 200 zł miesięcznie na kosztach serwera poprzez poprawne zarządzanie pamięcią. Jeśli chcesz sprawdzić, czy Twój kod jest solidny, sprawdź nasze terminy na ten miesiąc.

Programowanie to rzemiosło. Liczy się kod, który działa na produkcji, a nie tylko na Twoim laptopie.