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ą. Podstawowe dwa problemy na jakie natknąłem się w walce z dirty pages to: wyrzucanie normalnych danych z RAM do swap by zrobić miejsce dla cache i tym samym dla dane, które się kopiuje z jednego miejsca na dysku w drugie, a drugi problem dotyczył przycinania się systemu przy kopiowaniu plików na pendrive.

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:

Every 1.0s: cat /proc/meminfo | egrep -i "write|cache|dirty"     Wed Apr 23 02:36:47 2014

Cached:           653180 kB
SwapCached:            0 kB
Dirty:            331544 kB
Writeback:         15584 kB
WritebackTmp:          0 kB

oraz:

Every 1.0s: cat /proc/vmstat | egrep -i "dirty|writeback|cache"     Wed Apr 23 02:36:47 2014

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:

Every 1.0s: cat /proc/meminfo | egrep -i "write|cache|dirty"     Wed Apr 23 02:32:46 2014

Cached:           246312 kB
SwapCached:            0 kB
Dirty:              9952 kB
Writeback:             0 kB
WritebackTmp:          0 kB

oraz:

Every 1.0s: cat /proc/vmstat | egrep -i "dirty|writeback|cache"     Wed Apr 23 02:32:57 2014

nr_dirty 2488
nr_writeback 0
nr_writeback_temp 0
nr_dirty_threshold 99940
nr_dirty_background_threshold 66627

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

Powyższe dane są w oparciu o standardowe ustawienia debiana. Sporo ludzi nie ma bladego pojęcia dlaczego coś takiego ma miejsce i zwyczajnie w świecie wyznaje mit, że linux wie lepiej jak zarządzać danymi w pamięci RAM i nie zawracają sobie tym głowy.

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

Poniżej jest lista parametrów wraz z wartościami -- to są domyślne ustawienia debiana:

$ cat /proc/sys/vm/dirty_background_bytes
0
$ cat /proc/sys/vm/dirty_background_ratio
40
$ cat /proc/sys/vm/dirty_bytes
0
$ cat /proc/sys/vm/dirty_ratio
60
$ cat /proc/sys/vm/dirty_writeback_centisecs
60000
$ cat /proc/sys/vm/dirty_expire_centisecs
3000

dirty_background_bytes oraz dirty_background_ratio -- pierwsza wartość jest wyrażona w bajtach, a druga 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 -- albo ratio albo bytes. Jeśli jeden parametr jest ustawiony, drugi automatycznie dostaje wartość 0. Z kolei dirty_expire_centisecs 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ć flusher kernelowski, 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, to co rezyduje w cache pamięci RAM i jest oznaczone jako dirty pages oraz siedzi tam ponad 30s, jest oznaczane jako stare i przy następnym wybudzeniu flushera, zostanie zrzucone na dysk, czyli po 10min, chyba, że wcześniej dojdzie do przekroczenia limitu 40% i 60% maksymalnej dostępnej pamięci RAM.

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 dozwolonego na przebywanie dirty pages 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 zmieniają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ć tych dwóch parametrów 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 RAM na dysk -- są to dwie osobne operacje.

Trzeba także wziąć pod uwagę, że operacja zrzucania 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, w tym przypadku, 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:

Every 1.0s: cat /proc/meminfo | egrep -i "write|cache|dirty"     Wed Apr 23 03:50:14 2014

Cached:           382528 kB
SwapCached:          204 kB
Dirty:             11344 kB
Writeback:          4444 kB
WritebackTmp:          0 kB

oraz:

Every 1.0s: cat /proc/vmstat | egrep -i "dirty|writeback|cache"     Wed Apr 23 03:50:14 2014

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:

Every 1.0s: cat /proc/meminfo | egrep -i "write|cache|dirty"     Wed Apr 23 03:52:23 2014

Cached:           291268 kB
SwapCached:          204 kB
Dirty:                 8 kB
Writeback:             0 kB
WritebackTmp:          0 kB

oraz:

Every 1.0s: cat /proc/vmstat | egrep -i "dirty|writeback|cache"     Wed Apr 23 03:52:53 2014

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.

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

Pisane w oparciu o:
http://www.westnet.com/~gsmith/content/linux-pdflush.htm [1]
https://www.kernel.org/doc/Documentation/sysctl/vm.txt [2]


Przypisy:

  1. http://www.westnet.com/~gsmith/content/linux-pdflush.htm
  2. https://www.kernel.org/doc/Documentation/sysctl/vm.txt