zabójca pamięci

19 kwietnia, 2013 (20:38) | centos, linux | By: admin

Odwiedził mnie ostatnio stary znajomy. Ma na imię OOM killer i jest wyjątkowo uciążliwym typem. Jakiś czas temu zostaliśmy sobie przedstawieni przez kolegę Kernela i od tego momentu nie mogę się od tej nieciekawej znajomości uwolnić. Wspomniany gość nawiedza mnie w bardzo losowych i nieoczekiwanych momentach, wypowiada kilka zdań (które najczęściej są skrupulatnie logowane przez nadwornego skrybę – sysloga), a następnie sieje zniszczenie wśród znajdujących się w pobliżu procesów. Pół biedy, jeżeli za cel obierze jakieś testowe miejsce, do którego nikt poza mną nie zagląda i które nie jest źródłem dochodów dla moich Klientów. Wtedy bawimy się w kotka i myszkę (czasami nawet dość długo), aż w końcu któremuś z nas ta zabawa się nudzi i rozstajemy się w stanie zawieszenia broni. Ale czasami zdarza się tak, że kiler zaczyna majstrować przy produkcyjnych systemach. Oj, tego to ja bardzo nie lubię (głównie chyba dlatego, że lubię ciszę i spokój, a nerwowe maile i telefony związane z niedziałaniem systemu ten spokój skutecznie zaburzają) i w takich sytuacjach podejmuję aktywną walkę ze złem. A jak wiadomo, żeby pokonać wroga, trzeba go najpierw poznać…

Jądro Linuksa domyślnie skonfigurowane jest tak, aby być bardzo optymistycznie nastawionym do świata. Jeżeli jakiś proces chce zarezerwować dla siebie trochę pamięci, to bardzo często ją dostanie. Nawet wtedy, gdy system fizycznie aż takich możliwości nie ma. Dzieje się tak dzięki założeniu, że procesy alokują więcej pamięci, niż faktycznie wykorzystują, co z kolei prowadzi do marnowania cennych bajtów. A przecież wszyscy wiedzą, że bajtów marnować nie należy. Niestety, w pewnym momencie może nastąpić sytuacja, w której wspomniane procesy zaczynają używać całej przydzielonej im pamięci (dlaczego nie? przecież jądro im na to pozwoliło), a system nie ma gdzie tego wszystkiego pomieścić. I tu powstaje bardzo duży problem, któremu Linux stara się zaradzić wywołując proces OOM killera. Ten ostatni w sposób losowy(*) wybiera jakiś inny proces i go ubija, mając jednocześnie nadzieję, że taka operacja zwolni na tyle dużo pamięci, aby system mógł kontynuować pracę. Jednak nie zawsze się to udaje, a nawet jak się uda, to okazuje się, że trafiło na serwer SSH, a maszyna stoi kilkaset kilometrów od naszego aktualnego miejsca pobytu. Niezależnie od wyboru OOM killera, jego obecność zawsze jest niepokojąca i należy zrobić wszystko, aby jej zapobiec w przyszłości.

Szczęśliwie dla nas, nastąpił w historii rozwoju jądra moment, w którym kilku programistom się trochę nudziło i zaczęli kombinować, co można by było w zarządzaniu pamięcią zmienić. Wpadli oni na bardzo słuszny pomysł, że nie wszystkie systemy dobrze radzą sobie z wyrafinowanymi algorytmami przewidującymi rzeczywiste zużycie pamięci. Konsekwencją tego pomysłu było umożliwienie administratorowi zmiany domyślnego zachowania Linuksa za pomocą parametrów jądra. Wprawdzie do dyspozycji mamy tylko dwa takie parametry, jednak do większości zastosowań powinno to wystarczyć. Oto nasi bohaterowie:

  • vm.overcommit_memory – parametr przyjmujący wartości 0, 1 lub 2. Wartość zerowa jest wartością domyślną i oznacza, że system będzie próbował oszacować ilość wolnej pamięci i na tej podstawie przydzielać ją procesom, które o to proszą. Jeżeli zmienimy ten parametr na jeden, to system będzie udawał, że ma nieskończoną ilość pamięci i rozdawał ją na lewo i prawo… aż wszystko padnie :-) Ostatnią możliwością jest wartość 2, określana jako „never overcommit”, która jest bezpośrednio związana z drugim parametrem:

  • vm.overcommit_ratio – jeżeli poprzedni parametr ustawiliśmy na „2”, to tutaj definiujemy jak dużo pamięci system może przydzielić procesom. Jeżeli np. ustawimy tutaj wartość „80”, to procesy dostaną pamięć o rozmiarze partycji swap + 80% fizycznej pamięci RAM zainstalowanej w maszynie. Aha, można tu ustawić wielkości większe od 100, dzięki czemu system będzie w stanie zrobić overcommit, ale na naszych zasadach.

Najczęściej polecanymi ustawieniami tych parametrów, które mają nas zabezpieczyć przez szaleńczymi działaniami OOM killera są takie:

[root@ns ~]# sysctl -a | grep overcommit
vm.overcommit_memory = 2
vm.overcommit_ratio = 80
[root@ns ~]#

I faktycznie – jeżeli w systemie zaczyna buszować OOM killer i nie jest to sytuacja jednorazowa, takie zabezpieczenie wydaje się być niezłym rozwiązaniem. Należy jednak pamiętać, że nasze aplikacje nie są zbyt przewidywalne dla jądra systemu, co może być pierwszą oznaką tego, że zostały źle napisane i będzie z nimi więcej problemów. Na szczęście nie zawsze tak jest :-)

(*) – Tak naprawdę nie jest to zdarzenie losowe, a OOM killer ma dość zagmatwane algorytmy, za pomocą których wybiera swoją ofiarę. Jednak nie są one idealne, wobec czego wybór często okazuje się bardzo problematyczny.

I jeszcze jedno. Ponieważ każdy poważny artykuł albo wpis na blogu powinien mieć mnóstwo odnośników do źródeł wiedzy, a ja jakoś w treści mało linkowałem, to tutaj kilka ciekawych pozycji związanych z tematem:

Trackback URL: https://konrad.bechler.pl/2013/04/zabojca-pamieci/trackback/

«

»

Comments

Comment from Wojtek
Time 21 kwietnia 2013 at 19:44

W razie problemów z Linuxem fajnie wiedzieć, że coś takiego jest, bo być może łatwiej będzie znaleźć rozwiązanie, niż lamersko robić reinstalkę ;).

Comment from admin
Time 22 kwietnia 2013 at 08:26

Hehe, w razie problemów z Linuksem (z resztą z każdym innym systemem także) najpierw warto zajrzeć do logów i spróbować zrozumieć, co się z naszym środowiskiem dzieje. A Linux jest o tyle „przyjazny”, że bardzo ładnie o sobie opowiada.

K.

Comment from rozie
Time 23 kwietnia 2013 at 19:07

Poczytaj dokładniej o oom killer – warto. Po pierwsze, algorytmy nie są losowe (choć z elementem losowości). Po drugie, można konkretne procesy wyłączyć spod władzy oom killera (nigdy nie zostają ubite, polecam np. dla serwera SSH). Po trzecie, mieszanie produkcji i testowych maszyn ssie, szczególnie, że aplikacje na testowych wydają się cieknąć/a przynajmniej zużywać dużo pamięci. Po czwarte – RAM is cheap.

Comment from konrad
Time 23 kwietnia 2013 at 19:46

@rozie: Przecież w gwiazdce napisałem, że nie są losowe. Chociaż z moich doświadczeń (fakt, niezbyt bogatych) wynika, że ten mechanizm potrafi się zachować dość dziwacznie i nieoczekiwanie. Wyłącznie SSH spod władzy killera się sprawdza, ale nie rozwiązuje problemów ze „stabilnością” długofalową całego systemu. Nie wiem za bardzo, gdzie napisałem, że mieszanie produkcji i testu w jednym systemie jest dobre i że tak robię. Za to twierdzę, że problemy, w których zaczyna grasować oom killer często nie pojawiają się podczas pracy w środowisku testowym a dopiero w produkcji, np. przy dyżum obciążeniu. A co do cen RAMu to nie zawsze jest prawdą. Niekiedy trzeba zmienić całą maszynę, bo na aktualną platformę nie da się już nic wepchąć. A w przypadku dedyków gdzieś w świecie niestandardowe konfiguracje kosztują bardzo… różnie. A, no i jeszcze – źle napisana aplikacja zeżre cały RAM, niezależnie od tego, ile mu go dostarczymy.

Comment from rozie
Time 24 kwietnia 2013 at 22:25

Nie napisałeś, że mieszanie jest dobre. Zasugerowałem się zdaniem „pół biedy, jeżeli za cel obierze jakieś testowe miejsce”, że mieszasz. Jeśli chodzi o produkcję, to zakładam, że na siebie zarabia, a RAM jest realtywnie tani. Co do źle napisanej aplikacji – robota dla programistów. Jeśli jest źle napisane, to nie ma takiej ilości RAM, która wystarczy i nie ma sensu z koniem się kopać. Przy czym jak proces nie może zaalokować, to już masz awarię (z punktu widzenia użytkownika) – jakieś zlecone zadanie się nie wykona.

Comment from konrad
Time 25 kwietnia 2013 at 08:37

@rozie: Jako „testowe miejsce” miałem na myśli system, który nie chodzi w produkcji i którego awaria nie wiąże się z żadnymi negatywnymi skutkami (poza pewną ilością pracy dla administratora). Tak na dobrą sprawę rozdział poszczególnych części infrastruktury (dev + test + produkcja) to temat na tyle szeroki, że dałoby się książkę napisać.

A co do lepiej lub gorzej napisanych aplikacji, to mam systemy, na których uruchamiane są aplikacje pisane przez różnych programistów i przynależne do różnych projektów. I z tego względu wolę, żeby pojedynczy serwis wywalił się z powodu problemów z alokacją pamięci, niż żeby położyła się cała maszyna. Niestety, nie są to na tyle potężne rzeczy, żeby dla każdej z nich stawiać oddzielny system (choćby wirtualny).