Przejdź do treści
Lekcja 26 z 117

Kurs › Moduł 2 › Lekcja 26

Pamięć, obciążenie i dysk pod presją

⏱ ok. 11 min czytania· moduł 2: Procesy i jak działa system

Po tej lekcji: kursant w dwie minuty odpowiada na pytanie „czemu serwer muli”: czy brakuje pamięci, procesora, czy dysk nie nadąża, i który proces jest winny.

Wstęp

„Muli” to najgorsze zgłoszenie świata. Nie mówi, co muli, od kiedy i dlaczego. To jak przyjść do lekarza i powiedzieć „coś mnie boli”. Dobry lekarz nie zgaduje. Mierzy ciśnienie, temperaturę, osłuchuje.

Admin ma swoje stetoskopy. Dziś poznasz pięć: free, uptime, vmstat, iostat i pidstat. Plus jednego seryjnego zabójcę, który mieszka w jądrze i wychodzi, kiedy pamięć się skończy. Po tej lekcji na „muli” odpowiesz w dwie minuty: „to dysk, winny jest backup, już go uspokoiłem”.

free: ile mamy pamięci

Pamięć RAM to blat w kuchni. Na blacie leżą rzeczy, na których kucharze teraz pracują. Dysk to magazyn: duży, ale do niego trzeba iść. Im większy blat, tym mniej biegania.

free -h
               total        used        free      shared  buff/cache   available
Mem:           7.8Gi       1.0Gi       5.4Gi        15Mi       1.7Gi       6.8Gi
Swap:             0B          0B          0B

-h znaczy „po ludzku”, czyli w gigabajtach zamiast w kilobajtach. Kolumny:

  • total — cały blat.
  • used — zajęte przez programy.
  • free — kompletnie pusty kawałek blatu.
  • buff/cache — rzeczy przyniesione z magazynu „na wszelki wypadek”. Jeśli ktoś znowu poprosi o ten sam plik, nie trzeba biec.
  • available — ile możesz realnie dać nowemu programowi, licząc to, co da się szybko zgarnąć z cache.

I tu jest najważniejsza lekcja lekcji: patrz na available, nie na free. Linux celowo zapełnia wolny blat cache’em, bo pusta pamięć to zmarnowana pamięć. Początkujący admin widzi „free: 200 MB” i wpada w panikę. Doświadczony patrzy na available, widzi 6 GB i idzie po kawę.

/proc/meminfo: pełna kartoteka

free to streszczenie. Pełna kartoteka leży w pliku, który nie istnieje na dysku, bo generuje go jądro na życzenie:

grep -E 'MemTotal|MemAvailable|^Cached|Dirty|SwapTotal|SwapFree' /proc/meminfo
MemTotal:        8220244 kB
MemAvailable:    7163648 kB
Cached:          1638712 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Dirty:            231852 kB

Ciekawe pole to Dirty: dane, które program już „zapisał”, ale jądro jeszcze nie odniosło ich do magazynu. Dlatego po skopiowaniu dużego pliku na pendrive’a trzeba poczekać przed wyjęciem. Komenda sync mówi: „zanieś wszystko teraz”.

Swap: pomocniczy stolik w korytarzu

Co, jeśli blat się skończy? Jądro może wynieść rzadko używane rzeczy na stolik w korytarzu, czyli na dysk. To jest swap, przestrzeń wymiany. Ratuje przed katastrofą, ale stolik jest daleko, więc jeśli kucharze ciągle po coś tam biegają, kuchnia staje.

Swap może być partycją albo zwykłym plikiem. Plik zrobisz w minutę:

sudo fallocate -l 512M /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
swapon --show
free -h
NAME      TYPE SIZE USED PRIO
/swapfile file 512M   0B   -2
               total        used        free      shared  buff/cache   available
Mem:           7.8Gi       995Mi       6.4Gi        15Mi       845Mi       6.9Gi
Swap:          511Mi          0B       511Mi

chmod 600, bo w swapie lądują kawałki pamięci programów, a w nich bywają hasła. mkswap formatuje, swapon włącza. Na stałe dopisuje się go do /etc/fstab, co zrobimy porządnie w module o dyskach. Wyłączasz przez swapoff.

[RHEL] [Ubuntu] Polecenia są identyczne w obu rodzinach. Różni się to, co założył instalator: plik, partycję albo wolumen LVM. Sprawdzisz to właśnie swapon --show, kolumna TYPE.

Jak chętnie jądro używa korytarza, mówi parametr swappiness:

cat /proc/sys/vm/swappiness
60

Im niżej, tym dłużej jądro trzyma wszystko na blacie. Pamiętasz profil virtual-guest z TuneD? On właśnie zmieniał to na 30.

uptime: jak długo i jak ciężko

uptime
nproc
 16:29:04 up 11 min,  0 user,  load average: 5.24, 2.04, 0.78
2

Godzina, od kiedy działa i trzy tajemnicze liczby: load average, czyli średnie obciążenie z ostatniej minuty, pięciu i piętnastu minut.

Co to znaczy „obciążenie 5”? Wyobraź sobie kolejkę w Biedronce. Load to średnia liczba ludzi, którzy są obsługiwani przy kasie albo czekają w kolejce. Wynik ma sens dopiero, kiedy wiesz, ile kas jest otwartych. Liczbę kas, czyli rdzeni procesora, poda nproc.

U mnie load 5,24 przy dwóch kasach. Kolejka ponad dwa razy dłuższa niż przepustowość. Przy szesnastu kasach te same 5,24 to spokojne popołudnie. A trzy liczby czytasz jak trend: 5,24, 2,04, 0,78 to obciążenie, które właśnie rośnie. Ktoś przed chwilą coś odpalił.

vmstat: tablica rozdzielcza

uptime mówi „jest tłok”. vmstat mówi „gdzie”.

vmstat 2 4

Co dwie sekundy jedna linijka, cztery razy. Uruchomiłem to, a w tym czasie w drugim oknie zapisywałem na dysk 2 GB zer:

procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu-------
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st gu
 1  0      0 6749280   1536 845644    0    0    57   655  178    1  2  1 96  0  0  0
 0  2      0 6726116   2196 861172    0    0  7966 286760 1856 4205 23 10 28 37  2  0
 0  1      0 6726116   2260 861360    0    0    32 521728 1737 2261  1  8 45 46  0  0
 0  0      0 6732748   2276 861472    0    0    36 215568 1161 1471  5  5 75 13  1  0

Wygląda jak tablica odlotów na lotnisku, ale czytasz tylko kilka kolumn:

  • r — ilu kucharzy chce teraz kuchenki. Ciągle więcej niż rdzeni? Brakuje procesora.
  • b — ilu stoi zablokowanych i czeka na magazyn, czyli na dysk.
  • si / so — ruch na stoliku w korytarzu: wnoszenie i wynoszenie ze swapu. Stale większe od zera? Brakuje pamięci, i to jest zawsze zły znak.
  • bi / bo — ile kilobajtów na sekundę czytamy z dysku i zapisujemy na dysk.
  • us / sy / id / wa — procent czasu procesora: programy, jądro, nic, czekanie na dysk.
  • st — czas „skradziony”. Na maszynie wirtualnej to czas, kiedy fizyczny serwer pod spodem zajął się kimś innym. Wysokie st? Pogadaj z dostawcą chmury, nie ze swoim serwerem.

W moim przykładzie: b rośnie, bo skacze do pół gigabajta na sekundę, wa do 46 procent. Procesor się nudzi i czeka na dysk. Diagnoza: dysk.

I uwaga: pierwsza linijka to średnia od uruchomienia systemu, nie stan obecny. Dlatego zawsze dajesz odstęp i liczbę powtórzeń, i czytasz od drugiej linijki. To samo dotyczy iostat.

iostat -x: który dysk się poci

iostat jest w pakiecie sysstat, razem z pidstat. [Ubuntu] sudo apt install sysstat, [RHEL] sudo dnf install sysstat.

iostat -xs -d vda 1 2
Device             tps      kB/s    rqm/s   await  areq-sz  aqu-sz  %util
vda             141.27  10740.64    44.09    0.74    76.03    0.11   3.57

Device             tps      kB/s    rqm/s   await  areq-sz  aqu-sz  %util
vda            1138.00 416260.00     0.00    2.26   365.78    3.05  84.40

-x to rozszerzone statystyki, -d tylko dyski bez procesora, -s wersja wąska, żeby zmieściła się na ekranie. Pełne iostat -x ma ponad dwadzieścia kolumn, osobno dla odczytu i zapisu. Patrzysz głównie na trzy:

  • await — ile milisekund przeciętnie czeka jedno zamówienie do magazynu. Na dysku SSD pojedyncze milisekundy są ok. Dziesiątki i setki to problem.
  • aqu-sz — średnia długość kolejki do dysku.
  • %util — jaki procent czasu dysk miał cokolwiek do roboty.

pidstat i iotop: kto jest winny

Wiemy, że dysk. Ale kto go męczy? pidstat to iostat i vmstat w rozbiciu na procesy:

pidstat -d 1 1
16:29:34      UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s iodelay  Command
16:29:35        0      1713     12.00 404492.00      0.00       0  dd

Jest. dd, czterysta megabajtów na sekundę. Bez -d dostajesz procesor na proces, z -r pamięć, czyli RSS i błędy stron. Z -p PID patrzysz na jeden proces.

iotop to samo, tylko w stylu top, na żywo. Wymaga roota:

sudo iotop -o

-o pokazuje tylko procesy, które w tej chwili coś robią z dyskiem. Inaczej dostajesz listę trzystu procesów z zerami. iotop jest osobnym pakietem, instalujesz go jak każdy inny.

OOM killer: zabójca na etacie

A teraz najgorszy scenariusz. Blat pełny, stolik w korytarzu pełny, a ktoś dalej chce pamięci. Jądro ma wtedy dwa wyjścia: zawiesić całą kuchnię albo wyrzucić jednego kucharza. Wybiera to drugie. Robi to OOM killer, od „out of memory”.

Nie strzela na ślepo. Każdy proces ma punkty w /proc/PID/oom_score. Najwięcej punktów dostaje ten, kto zajmuje najwięcej pamięci, a admin może punkty przesunąć przez oom_score_adj, od -1000, czyli „nigdy mnie nie zabijaj”, do 1000, czyli „zabij mnie pierwszego”.

Pokażę Ci go na żywo, ale w klatce. Wsadziłem proces do grupy cgroups z limitem 100 MB, żeby zabójca nie ruszył niczego poza nią, i kazałem mu zjadać pamięć po 10 MB:

70 MB
80 MB
90 MB
kod wyjscia: 137

Kod wyjścia 137, czyli 128 plus 9. Sygnał dziewięć, SIGKILL, wyłączenie prądu w lokalu. Bez ostrzeżenia, bez sprzątania. A ślad zostaje w dzienniku jądra:

sudo dmesg | grep -i -E 'oom|killed process'
[  721.187082] python3 invoked oom-killer: gfp_mask=0xcc0(GFP_KERNEL), order=0, oom_score_adj=0
[  721.282991] Memory cgroup out of memory: Killed process 11404 (python3) total-vm:138056kB, anon-rss:102016kB, file-rss:7752kB, shmem-rss:0kB, UID:0 pgtables:280kB oom_score_adj:0

„Killed process” to zdanie, którego szukasz, kiedy baza danych „sama się wyłączyła w nocy”. Nie wyłączyła się sama. Została wyproszona. Na systemie z systemd znajdziesz to też w journalctl -k, o czym za trzy lekcje.

Jak to działa pod spodem

Wszystkie dzisiejsze narzędzia to tylko ładne nakładki na pliki w /proc. free czyta /proc/meminfo. uptime czyta /proc/loadavg i /proc/uptime. vmstat czyta /proc/stat i /proc/vmstat. iostat czyta /proc/diskstats. pidstat przechodzi po katalogach /proc/PID/. Same liczby w tych plikach to liczniki, które tylko rosną: „od startu przeczytano tyle sektorów”. Narzędzie robi dwa odczyty w odstępie sekundy i odejmuje. Dlatego pierwsza linijka bez odstępu to średnia od startu: nie było od czego odjąć.

A pamięć? Jądro dzieli ją na strony po 4 KiB. Każda strona jest albo „anonimowa”, czyli dane programu, albo „plikowa”, czyli kopia kawałka pliku z dysku. Plikową można w każdej chwili wyrzucić, bo oryginał jest na dysku. To jest właśnie cache. Anonimową można tylko wynieść do swapu. Kiedy nie ma już czego wyrzucić ani gdzie wynieść, zostaje OOM killer.

Ściąga flag

KomendaFlagaCo robi
free-hrozmiary po ludzku
free-wosobno buffers i cache
free-s Npowtarza co N sekund
uptime-ptylko czas działania, ładnie
vmstatN Mco N sekund, M razy
vmstat-S Mpamięć w megabajtach
vmstat-wszerokie kolumny
iostat-xrozszerzone statystyki dysków
iostat-d / -ctylko dyski / tylko procesor
iostat-swąska wersja na 80 znaków
iostat-ypomija pierwszy raport od startu
iostat-zukrywa dyski bez ruchu
pidstat-d / -r / -udysk / pamięć / procesor na proces
pidstat-p PIDtylko ten proces
iotop-otylko procesy, które teraz używają dysku
iotop-b -n Ntryb wsadowy, N pomiarów, do pliku
swapon--showaktywny swap
dmesg-Tczas po ludzku zamiast sekund od startu

⚠ Typowy błąd

Zgłoszenie: „serwer ma tylko 300 MB wolnej pamięci, trzeba dokupić RAM”. Admin, żeby pomóc, czyści cache:

sync; echo 3 | sudo tee /proc/sys/vm/drop_caches

I w free faktycznie skacze kolumna free. Sukces? Nie. Właśnie wyrzuciłeś z blatu wszystko, co jądro przyniosło z magazynu, żeby było szybciej. Przez najbliższe minuty każdy program będzie czytał z dysku od zera, serwer zwolni, a po kwadransie cache i tak się odbuduje. To jak wyrzucenie wszystkiego z lodówki, bo „lodówka jest pełna”.

Prawdziwy problem z pamięcią wygląda inaczej: małe available, si i so w vmstat stale powyżej zera i „Killed process” w dzienniku jądra. Dopóki tego nie ma, duży cache to zdrowy objaw, nie choroba.

✎ Zadanie

  1. Otwórz dwa okna, na przykład w tmux. W pierwszym uruchom vmstat 2 5. W drugim zapisz na dysk 2 GB zer: dd if=/dev/zero of=test.img bs=1M count=2000 oflag=direct. Zanotuj, które kolumny vmstat się zmieniły. Potem usuń test.img.
  2. Na maszynie wirtualnej zrób plik wymiany 512 MB, włącz go, pokaż swapon --show i free -h, a potem go wyłącz i usuń.
  3. Sprawdź w dzienniku jądra, czy OOM killer kiedykolwiek odwiedził Twój system.
Pokaż rozwiązanie

W jednym oknie vmstat 2 4, w drugim dd z dwoma gigabajtami zer:

 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st gu
 1  0      0 6749280   1536 845644    0    0    57   655  178    1  2  1 96  0  0  0
 0  2      0 6726116   2196 861172    0    0  7966 286760 1856 4205 23 10 28 37  2  0
 0  1      0 6726116   2260 861360    0    0    32 521728 1737 2261  1  8 45 46  0  0

Zmieniły się b, czyli procesy czekające na dysk, bo, czyli zapis, i wa, czyli czas, kiedy procesor nudzi się w oczekiwaniu na dysk. Jeśli u Ciebie bo było małe, a buff/cache urosło, to zapomniałeś o oflag=direct i jądro schowało zapis w pamięci. Też lekcja.

Swap:

sudo fallocate -l 512M /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
swapon --show; free -h
sudo swapoff /swapfile; sudo rm /swapfile
Swap:          511Mi          0B       511Mi

I dziennik jądra: sudo dmesg | grep -i 'killed process' albo journalctl -k | grep -i 'killed process'. Pusto? Gratulacje, żaden kucharz nie został wyproszony. Na razie.

Co dalej

Wiemy już, jak zdiagnozować działający system. A co się dzieje, zanim w ogóle zacznie działać? W następnej lekcji przechodzimy całą drogę od wciśnięcia przycisku zasilania do ekranu logowania: BIOS, GRUB, jądro, initramfs i kierownik zmiany numer jeden.