Pamięć, obciążenie i dysk pod presją
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/meminfoMemTotal: 8220244 kB
MemAvailable: 7163648 kB
Cached: 1638712 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 231852 kBCiekawe 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 -hNAME 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 511Michmod 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/swappiness60Im 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
2Godzina, 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 4Co 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 0Wyglą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 2Device 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 116: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 ddJest. 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: 137Kod 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
| Komenda | Flaga | Co robi |
|---|---|---|
| free | -h | rozmiary po ludzku |
| free | -w | osobno buffers i cache |
| free | -s N | powtarza co N sekund |
| uptime | -p | tylko czas działania, ładnie |
| vmstat | N M | co N sekund, M razy |
| vmstat | -S M | pamięć w megabajtach |
| vmstat | -w | szerokie kolumny |
| iostat | -x | rozszerzone statystyki dysków |
| iostat | -d / -c | tylko dyski / tylko procesor |
| iostat | -s | wąska wersja na 80 znaków |
| iostat | -y | pomija pierwszy raport od startu |
| iostat | -z | ukrywa dyski bez ruchu |
| pidstat | -d / -r / -u | dysk / pamięć / procesor na proces |
| pidstat | -p PID | tylko ten proces |
| iotop | -o | tylko procesy, które teraz używają dysku |
| iotop | -b -n N | tryb wsadowy, N pomiarów, do pliku |
| swapon | --show | aktywny swap |
| dmesg | -T | czas 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_cachesI 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
- 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 kolumnyvmstatsię zmieniły. Potem usuńtest.img. - Na maszynie wirtualnej zrób plik wymiany 512 MB, włącz go, pokaż
swapon --showifree -h, a potem go wyłącz i usuń. - 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 0Zmienił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 /swapfileSwap: 511Mi 0B 511MiI 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.