"Nie blokuj pętli zdarzeń..." - zapewne słyszałeś to zdanie wiele razy... Nie dziwię się, ponieważ jest to jedno z najważniejszych założeń podczas pracy z Node. Ale jest też druga "rzecz", której nie należy blokować - Worker Pool. Jej zaniedbanie może mieć znaczący wpływ na wydajność aplikacji, a nawet jej bezpieczeństwo.
Nici
Najważniejsza rzecz do zapamiętania: istnieją dwa rodzaje wątków w Node.js: Główny wątek - który jest obsługiwany przez Pętla zdarzeńoraz Pula pracowników (pula wątków) - która jest pulą wątków -
dzięki libuv. Każdy z nich ma inne zadanie. Celem pierwszego z nich jest obsługa nieblokujących operacji wejścia/wyjścia, a drugi jest odpowiedzialny za intensywną pracę procesora, a także blokowanie operacji wejścia/wyjścia.

But what is a thread and how it’s different than a process? There are several differences, but the most important one for us is how the memory is allocated to them. You can think about a process like about an application. Inside each process, there is a chunk of memory dedicated just for this process. So, one process doesn`t have access to the memory of the second one, and this property ensures high security. To establish communication between them, we must do some work. Threads are different. Threads runs inside a process and share the same memory so there is no problem at all with threads sharing data.
Jednak jedna kwestia powoduje problem. Jest to tzw. warunek wyścigu. Wątki mogą działać w tym samym czasie, więc skąd mamy wiedzieć, który kończy się pierwszy? Może się zdarzyć, że przy pierwszym uruchomieniu pierwsza operacja zakończy się jako pierwsza, a następnym razem może być odwrotnie i druga operacja zakończy się przed pierwszą. Wyobraź sobie pracę z operacjami zapisu/odczytu w takich warunkach! Koszmar! Czasami bardzo trudno jest napisać poprawne kod w środowisku wielowątkowym.
Ponadto języki wielowątkowe mają duży narzut pamięciowy, ponieważ tworzą osobny wątek dla każdego żądania; więc jeśli chcesz wywołać 1000 żądań, tworzą 1000 wątków.
Jak poradzić sobie z takim problemem? Zamiast tego użyć pojedynczego wątku! I to jest właśnie to Węzeł oferuje.

Jako JavaScript deweloper Zachęcam do obejrzenia film
w którym Bart Belder jasno wyjaśnia koncepcję pętli zdarzeń. Powyższy diagram pochodzi z jego prezentacji. A jeśli w ogóle nie znasz tych terminów, zarówno Węzeł Libuv i Libuv mają doskonałą dokumentację 🙂
O blokowaniu
W Rozwój JavaScript mówią, że ponieważ Węzeł jest jednowątkowa i nieblokująca, można osiągnąć wyższą współbieżność przy tych samych zasobach niż w przypadku rozwiązań wielowątkowych. To prawda, ale nie jest to tak piękne i łatwe, jak mogłoby się wydawać.
Od Node.js jest jednowątkowa (część JS), zadania intensywnie wykorzystujące procesor będą blokować wszystkie żądania w toku, dopóki dane zadanie nie zostanie zakończone. Tak więc prawdą jest, że w Node.js można zablokować każde żądanie tylko dlatego, że jedno z nich zawierało instrukcję blokującą. Kod blokujący oznacza, że jego zakończenie zajmuje więcej niż kilka milisekund. Nie należy jednak mylić długiego czasu odpowiedzi z blokowaniem. Odpowiedź z bazy danych może trwać bardzo długo, ale nie blokuje procesu (aplikacji).
Metody blokujące są wykonywane synchronicznie, a metody nieblokujące są wykonywane asynchronicznie.
Jak spowolnić (lub zablokować) pętlę zdarzeń?
- podatne wyrażenie regularne - podatne wyrażenie regularne to takie, na którym silnik wyrażeń regularnych może zająć wykładniczy czas; możesz przeczytać o nich więcej tutaj,
- Operacje JSON na dużych obiektach,
- przy użyciu synchronicznych interfejsów API z Węzeł zamiast wersji asynchronicznych; wszystkie metody I/O w bibliotece standardowej Node.js zapewniają również ich wersje asynchroniczne,
- inne błędy programistyczne, takie jak synchroniczne nieskończone pętle.
W takim przypadku, skoro Worker Pool korzysta z puli wątków, czy możliwe jest ich zablokowanie? Niestety tak 🙁 Węzeł opiera się na filozofii jeden wątek dla wielu klientów. Załóżmy, że dane zadanie wykonywane przez konkretnego Workera jest bardzo złożone i wymaga więcej czasu na jego ukończenie. W rezultacie Worker zostaje zablokowany i nie można go użyć do wykonania żadnego z innych oczekujących zadań, dopóki jego instrukcje nie zostaną wykonane. Jak już zapewne się domyśliłeś, może to mieć wpływ na wydajność. Możesz zapobiec takim problemom, minimalizując różnice w czasach zadań za pomocą partycjonowania zadań.
Wnioski
Unikaj blokowania, to na pewno. Jeśli tylko możesz, zawsze wybieraj asynchroniczne wersje standardowych API bibliotek. W przeciwnym razie po uruchomieniu aplikacji klient może doświadczyć kilku problemów, zaczynając od obniżonej przepustowości, a kończąc na całkowitym wycofaniu, co jest fatalne z punktu widzenia użytkownika.
Czytaj więcej:
Dlaczego (prawdopodobnie) powinieneś używać Typescript
Jak nie zabić projektu złymi praktykami kodowania?
Strategie pobierania danych w NextJS