RAM, cache i dirty pages

Spis treści

Wiele czasu zajęło mi opanowanie w końcu tych bestii zwanych dirty pages, które to są trzymane w cache pamięci RAM i potrafią dać się nieźle we znaki, zwłaszcza gdy ma się mało pamięci operacyjnej i maszynę 64 bitową. Chodzi o to, że podczas kopiowania plików z/na pendrive, system zaczyna się strasznie przycinać, bo następuje zrzucanie danych innych procesów z RAM do SWAP by zrobić miejsce pod te dane, które są aktualnie kopiowane.

Zasada działania mechanizmu dirty pages

By zrozumieć jak działa ten mechanizm z wykorzystaniem dirty pages, trzeba rzucić pierw okiem na statystyki danych w pamięci RAM podczas procesu kopiowania danych:

$ cat /proc/meminfo | egrep -i "write|cache|dirty"
Cached:           653180 kB
SwapCached:            0 kB
Dirty:            331544 kB
Writeback:         15584 kB
WritebackTmp:          0 kB

oraz:

$ cat /proc/vmstat | egrep -i "dirty|writeback|cache"
nr_dirty 82886
nr_writeback 4346
nr_writeback_temp 0
nr_dirty_threshold 99210
nr_dirty_background_threshold 66140

Dla kontrastu, zwykła praca systemu:

$ cat /proc/meminfo | egrep -i "write|cache|dirty"
Cached:           246312 kB
SwapCached:            0 kB
Dirty:              9952 kB
Writeback:             0 kB
WritebackTmp:          0 kB

oraz:

$ cat /proc/vmstat | egrep -i "dirty|writeback|cache"
nr_dirty 2488
nr_writeback 0
nr_writeback_temp 0
nr_dirty_threshold 99940
nr_dirty_background_threshold 66627

Jak widać, podczas kopiowania ilość danych oznaczonych jako dirty jest wręcz katastrofalna, ponad 300MiB. To dlatego system z małą ilością pamięci RAM się przycina przy kopiowaniu i dlatego również dane z pamięci RAM wyskakują pod naporem tego brudnego cache.

Ci na nieco starszych maszynach mają nie lada problem do rozwiązania, bo jakby nie patrzeć używanie komputera podczas kopiowania plików jest wręcz niemożliwe -- dźwięk się przycina, mysza freezuje co parę sekund na parę kolejnych sekund. Na szczęście istnieje kilka parametrów w kernelu, które po dostosowaniu mogą nam pomóc ogarnąć system zainstalowany już na dość leciwym sprzęcie.

Poniżej jest lista plików wraz z wartościami. To są domyślne ustawienia debiana:

| Plik                                     | Wartość |
|----------------------------------------  | ------- |
| /proc/sys/vm/dirty\_background\_bytes    | 0       |
| /proc/sys/vm/dirty\_background\_ratio    | 40      |
| /proc/sys/vm/dirty\_bytes                | 0       |
| /proc/sys/vm/dirty\_ratio                | 60      |
| /proc/sys/vm/dirty\_writeback\_centisecs | 60000   |
| /proc/sys/vm/dirty\_expire\_centisecs    | 3000    |

Wartość dirty_background_bytes jest wyrażona w bajtach, zaś dirty_background_ratio w procentach. Obie odpowiadają za próg, po przekroczeniu którego proces zacznie zapisywać dirty pages na dysk. Podobnie z dirty_bytes oraz dirty_ratio, z tym, że jest to globalny próg dla wszystkich procesów, którego nie mogą przekroczyć. W obu przypadkach może być użyty tylko jeden parametr w tym samym czasie, tj. albo ratio, albo bytes. Jeśli jeden parametr jest ustawiony, drugi automatycznie dostaje wartość 0.

Jeśli chodzi o dirty_expire_centisecs , to jest to czas, po którym dane zawierające dirty pages zostaną oznaczone jako stare. Ten parametr jest wyrażony w setnych częściach sekundy, w tym przypadku jest to 30s. Parametr dirty_writeback_centisecs ma budzić kernelowski flusher, który ma za zadanie zapisać stare dirty pages (oznaczone przez dirty_expire_centisecs ), w tym przypadku wartość również jest wyrażona w setnych częściach sekundy, co daje interwał 600s.

Zgonie z powyższymi parametrami, dane przeznaczone do zapisu na dysk (dirty pages) rezydujące w pamięci ponad 30 sekund będą oznaczane jako stare i przy przy następnym wybudzeniu flushera, zostaną zrzucone na dysk, czyli po 10min, chyba, że wcześniej dojdzie do przekroczenia limitu 40%/60% maksymalnej dostępnej pamięci RAM.

Ograniczenie cache pod dirty pages

By uporać się ze zbyt dużą ilością danych, które wędrują do cache przy kopiowaniu, można zrobić dwie rzeczy. Pierwszą z nich jest ograniczenie domyślnego progu dla dirty pages (40%, 60%). Druga opcja, to zmniejszenie czasu, który dirty pages spędzają w cache.

Samo trzymanie dirty pages w pamięci RAM nie jest zbyt dobrym pomysłem, bo jeśli byśmy dokonywali zapisu szeregu plików w tym samym czasie i wartość nr_dirty zwiększy się, to nawet jeśli kopiowanie zakończy się powodzeniem, część danych dalej rezyduje w pamięci RAM i czeka aż 10min zanim zostanie zapisana na dysk. Ja ten parametr dirty_expire_centisecs zmniejszyłem do wartości 200 i po 2s dirty pages są oznaczane jako stare. Natomiast parametr dirty_writeback_centisecs zmniejszyłem do wartości commit w /etc/fstab dla montowanych partycji. Domyślnie commit wynosi 5s (można zmienić przez dopisanie commit=5 dobierając odpowiednio wartość). Wyższa wartość oznacza mniej operacji na dysku, niższą temperaturę urządzenia i mniej ruchów głowicą, co też przełoży się w jakimś tam stopniu na zużycie energii ale kosztem uszkodzenia większej ilości danych przy powieszeniu się systemu czy odcięciu prądu. Jeśli mamy stabilny system, możemy ten parametr sobie zwiększyć. Nie należy jednak mylić ich ze sobą, bo commit=5 w /etc/fstab odpowiada za zapisanie danych do dziennika systemu plików, by w przypadku zawału systemu było wiadomo jakie pliki uległy uszkodzeniu. Natomiast dirty_writeback_centisecs, jak już wspomniałem, przerzuca dane z pamięci RAM na dysk -- są to dwie osobne operacje.

Trzeba także wziąć pod uwagę, że zrzucanie dirty pages z pamięci RAM na dysk niczym się nie różni od wydawania polecenia sync co określony przedział czasu. Jeśli zbyt często będziemy zrzucać dirty pages, ucierpi na tym transfer. Podobnie ze zbyt niskim ograniczeniem progu dla dirty pages w cache RAM. Jeśli chodzi o wartość parametru dirty_background_ratio , ustawiłem go sobie na 3%, zaś dirty_ratio na 5% ogólnej wartości pamięci RAM posiadanej w systemie. W tym przypadku jest to 1GiB, także maksymalna wartość dla dirty pages to 50MiB. Transfer na moim dysku nie przekracza 30MiB/s, także te wartości wydają się być racjonalne. Po przekroczeniu 5% progu, dane automatycznie będą zrzucane z pamięci RAM na dysk, co powinno skutecznie uniemożliwić zrzucenie normalnych danych do SWAP, by zrobić tym sposobem więcej miejsca dla cache pod dirty pages.

Niemniej jednak, dirty pages powstałe przy kopiowaniu danych różnią się nieco od tych powstałych przy zwykłym użytkowaniu systemu. Logi systemowe czy aplikacje użytkownika, jak by nie patrzeć, generują trochę danych i zawsze mam około 5-10MiB dirty pages. Powyższe wartości, odpowiednio, 200 i 500 dla dirty_expire_centisecs i vm.dirty_writeback_centisecs powinny zapobiec trzymaniu zbyt wielu dirty pages w cache pamięci RAM. Zatem rzućmy okiem jak obecnie wyglądają statystyki.

Podczas kopiowania danych:

$ cat /proc/meminfo | egrep -i "write|cache|dirty"
Cached:           382528 kB
SwapCached:          204 kB
Dirty:             11344 kB
Writeback:          4444 kB
WritebackTmp:          0 kB

oraz:

$ cat /proc/vmstat | egrep -i "dirty|writeback|cache"
nr_dirty 2861
nr_writeback 1111
nr_writeback_temp 0
nr_dirty_threshold 4850
nr_dirty_background_threshold 2910

A przy zwykłej pracy systemu:

$ cat /proc/meminfo | egrep -i "write|cache|dirty"
Cached:           291268 kB
SwapCached:          204 kB
Dirty:                 8 kB
Writeback:             0 kB
WritebackTmp:          0 kB

oraz:

$ cat /proc/vmstat | egrep -i "dirty|writeback|cache"
nr_dirty 2
nr_writeback 0
nr_writeback_temp 0
nr_dirty_threshold 4888
nr_dirty_background_threshold 2932

Jak widać powyżej, przy kopiowaniu danych nie ma już ponad 300MiB dirty pages. Zamiast tego mamy około 11MiB. Za to sprawa z systemem w stanie spoczynku wygląda ciekawie, bo jak można zaobserwować, jest bardzo niewiele dirty pages. Czasami parametr nr_dirty skacze na 100 ale po chwili spada do 0.

Konfiguracja dla sysctl.conf

Powyższe ustawienia można zapisać, tak by były ładowane ze startem systemu. Trzeba dopisać poniższe linijki do /etc/sysctl.conf :

#vm.dirty_background_bytes = 16777216
vm.dirty_background_ratio = 3
#vm.dirty_bytes = 50331648
vm.dirty_ratio = 5
vm.dirty_writeback_centisecs = 500
vm.dirty_expire_centisecs = 200

Można także teraz załadować te ustawienia bez potrzeby resetowania systemu przez wpisanie do terminala poniższego polecenia:

# sysctl -p

Jako, że cześć parametrów może nie zostać załadowanych przez skrypty debianowe, najlepiej dodać do /etc/rc.local poniższą linijkę:

sysctl -p >/dev/null 2>&1
Mikhail Morfikov avatar
Mikhail Morfikov
Po ponad 10 latach spędzonych z różnej maści linux'ami (Debian/Ubuntu, OpenWRT, Android) mogę śmiało powiedzieć, że nie ma rzeczy niemożliwych i problemów, których nie da się rozwiązać. Jedną umiejętność, którą ludzki umysł musi posiąść, by wybrnąć nawet z tej najbardziej nieprzyjemniej sytuacji, to zdolność logicznego rozumowania.