Pomysł na artykuł zrodził się spontanicznie podczas dyskusji w komentarzach do artykułu .

Faktem jest, że wewnętrzną specyfiką działania naszych usług jest przechowywanie ogromnej liczby drobnych plików. Obecnie mamy około setek terabajtów takich danych. Napotkaliśmy na kilka oczywistych i nieco mniej oczywistych pułapek i skutecznie je pokonaliśmy.
Dlatego dzielę się naszym doświadczeniem, może się komuś przyda.
Pierwszy problem: „Brak miejsca na urządzeniu”
Jak wspomniano w wyżej wspomnianym artykule, problem polega na tym, że wolne bloki w systemie plików są dostępne, ale inody się wyczerpały.
Liczbę używanych i wolnych inodów można sprawdzić komendą df -ih:

Nie będę streszczał artykułu, krótko mówiąc, na dysku są bloki bezpośrednio dla danych oraz bloki dla metainformacji, zwane inodami (index node). Ich liczba jest ustalana podczas inicjalizacji systemu plików (mowa o ext2 i jej potomkach) i później się nie zmienia. Równowaga między blokami danych a inodami jest obliczana na podstawie średnich danych, w naszym przypadku, gdzie jest wiele drobnych plików, równowaga powinna przesuwać się na korzyść liczby inodów — ich powinno być więcej.
W Linuxie przewidziano różne warianty z różną równowagą, a wszystkie te wstępnie obliczone konfiguracje znajdują się w pliku /etc/mke2fs.conf.
Dlatego przy pierwszej inicjalizacji systemu plików za pomocą mke2fs można wskazać odpowiedni profil.
Oto kilka przykładów z pliku:
small = {
blocksize = 1024
inode_size = 128
inode_ratio = 4096
}
big = {
inode_ratio = 32768
}
largefile = {
inode_ratio = 1048576
blocksize = -1
}
Wybór odpowiedniego wariantu zastosowania można dokonać za pomocą opcji „-T” przy wywołaniu mke2fs. Można także ręcznie podać odpowiednie parametry, jeśli nie ma gotowego rozwiązania.
Więcej szczegółów opisano w podręcznikach dla mke2fs.conf i mke2fs.
Nieporuszona w wyżej wymienionym artykule cecha — można określić rozmiar bloku danych. Oczywiście dla dużych plików ma sens większy rozmiar bloku, dla małych — mniejszy.
Należy jednak wziąć pod uwagę tak interesującą cechę, jak architektura procesora.
Kiedyś pomyślałem, że potrzebuję większego rozmiaru bloku dla dużych plików zdjęć. To działo się w warunkach domowych, na domowym magazynie danych marki WD z architekturą ARM. Nie zastanawiając się długo, ustawiłem rozmiar bloku na 8k lub 16k zamiast standardowych 4k, wcześniej mierząc oszczędności. Wszystko było świetnie, aż do momentu, gdy sam magazyn przestał działać, podczas gdy dysk nadal był sprawny. Po włożeniu dysku do zwykłego komputera z klasycznym procesorem Intela, otrzymałem niespodziankę: rozmiar bloku nie jest obsługiwany. Dobra, dane są, wszystko w porządku, ale nie można ich odczytać. Procesory i386 i podobne nie potrafią pracować z rozmiarami bloku, które nie odpowiadają rozmiarowi strony pamięci, a ten wynosi dokładnie 4k. W końcu skorzystaliśmy z narzędzi użytkownika, wszystko było wolne i smutne, ale uratowaliśmy dane. Dla zainteresowanych — google'y po nazwie narzędzia fuseext2. Morał: albo przemyśl wszystkie przypadki z góry, albo nie udawaj superbohatera i korzystaj ze standardowych ustawień dla domowników.
UPD. Zgodnie z uwagą użytkownika dodatkowo wyjaśniam, że dla i386 rozmiar bloku nie powinien przekraczać 4k, ale nie musi być dokładnie 4k, to znaczy dozwolone są 1k i 2k.
A więc, jak my rozwiązaliśmy problemy.
Po pierwsze, napotkaliśmy problem, gdy wielotera bajtowy dysk był zapełniony danymi, a my nie mogliśmy zmienić konfiguracji systemu plików.
Po drugie, potrzebne było pilne rozwiązanie.
Ostatecznie doszliśmy do wniosku, że musimy zmienić równowagę, zmniejszając liczbę plików.
Aby zmniejszyć liczbę plików, postanowiono umieścić pliki w jednym wspólnym archiwum. Biorąc pod uwagę naszą specyfikę, umieszczaliśmy w jednym archiwum wszystkie pliki za pewien okres czasu i przeprowadzaliśmy archiwizację codziennym zadaniem cron w nocy.
Wybrano archiwum zip. W komentarzach do poprzedniego artykułu sugerowano tar, ale jest jedna trudność: nie ma on spisu treści, a pliki są w nim ułożone w sposób sekwencyjny (nie bez powodu „tar” to skrót od „Tape Archive”, dziedzictwo taśmowych nośników), tzn. jeśli trzeba przeczytać plik na końcu archiwum — należy przeczytać całe archiwum, ponieważ nie ma w nim przesunięć dla każdego pliku względem początku archiwum. Dlatego to jest długotrwała operacja. W zip jest znacznie lepiej: ma on wspomniany spis treści i przesunięcia plików wewnątrz archiwum, a czas dostępu do każdego pliku nie zależy od jego lokalizacji. A w naszym przypadku można było ustawić opcję kompresji „0”, ponieważ wszystkie pliki były już wcześniej skompresowane w gzip.
Klienci pobierają pliki przez nginx, a według starego API po prostu podaje się nazwę pliku, na przykład tak:
http://www.server.com/hydra/20170416/0453/3bd24ae7-1df4-4d76-9d28-5b7fcb7fd8e5
Aby dekompresować pliki na bieżąco, znaleziono i podłączono moduł nginx-unzip-module () i skonfigurowano dwa upstreamy.
W rezultacie powstała taka konfiguracja:

Dwa hosty w ustawieniach wyglądały tak:
server {
listen *:8081;
location / {
root /home/filestorage;
}
}server {
listen *:8082;
location ~ ^/hydra/(d+)/(.+)$ {
root /home/filestorage;
file_in_unzip_archivefile "/home/filestorage/hydra/$1/$2.zip";
file_in_unzip_extract "$2/$3";
file_in_unzip;
}
}
I konfiguracja upstreamów na wyższym nginx:
upstream storage {
server server.com:8081;
server server.com:8082;
}
Jak to działa:
- Klient idzie na front nginx
- Front nginx stara się dostarczyć plik z pierwszego upstreamu, tzn. bezpośrednio z systemu plików
- Jeśli pliku nie ma — próbuje dostarczyć z drugiego upstreamu, który stara się znaleźć plik w archiwum
Drugi problem: znowu „No space left on device”
To drugi problem, na który natknęliśmy się, gdy w katalogu jest wiele plików.
Próbujemy stworzyć plik, system narzeka, że nie ma miejsca. Zmieniamy nazwę pliku i ponownie próbujemy go stworzyć.
Udaje się.
Wygląda mniej więcej tak:

Sprawdzenie inodów nic nie dało — jest ich dużo wolnych.
Sprawdzenie miejsca — to samo.
Pomyśleliśmy, że może w katalogu jest zbyt wiele plików, a na to jest ograniczenie, ale znów nie: Maksymalna liczba plików na katalog: ~1.3 × 10^20
Tak czy inaczej plik można stworzyć, jeśli zmieni się nazwę.
Wniosek — problem leży w nazwie pliku.
Dalsze poszukiwania pokazały, że problem leży w algorytmie haszowania podczas budowania indeksu katalogu, przy dużej liczbie plików występują kolizje ze wszystkimi tego konsekwencjami. Więcej informacji można przeczytać tutaj:
Można wyłączyć tę opcję, ale… wyszukiwanie pliku po nazwie może stać się nieprzewidywalnie długie podczas przeszukiwania wszystkich plików.
tune2fs -O "^dir_index" /dev/sdb3
Ogólnie rzecz biorąc, jako tymczasowe rozwiązanie, może to zadziałać.
Moral: dużo plików w katalogu — to zazwyczaj źle. Tak robić nie należy.
Zazwyczaj w takich przypadkach tworzy się zagnieżdżone katalogi, według pierwszych liter nazwy pliku lub według innych parametrów, na przykład według dat, w większości przypadków to ratuje sytuację.
Jednak łączne liczba małych plików — to wciąż źle, nawet jeśli zostaną podzielone według katalogów — wtedy patrz na pierwszy problem.
Problem trzeci: jak zobaczyć listę plików, jeśli jest ich dużo
W naszej sytuacji, kiedy mamy dużo plików, w taki czy inny sposób stykamy się z problemem, jak zobaczyć zawartość katalogu.
Standardowe rozwiązanie — komenda ls.
Dobrze, zobaczmy, co się dzieje przy 4772098 plikach:
$ time ls /home/app/express.repository/offercache/ >/dev/null
real 0m30.203s
user 0m28.327s
sys 0m1.876s
30 sekund… to dość długo. Przy czym główny czas zajmuje przetwarzanie plików w przestrzeni użytkownika, a nie działanie jądra.
Ale jest rozwiązanie:
$ time find /home/app/express.repository/offercache/ >/dev/null
real 0m3.714s
user 0m1.998s
sys 0m1.717s
3 sekundy. 10 razy szybciej.
Hurra!
UPD.
Jeszcze szybsze rozwiązanie od użytkownika — wyłączenie sortowania w ls
time ls -U /home/app/express.repository/offercache/ >/dev/null
real 0m2.985s
user 0m1.377s
sys 0m1.608s
Problem czwarty: wysoki LA podczas pracy z plikami
Okazjonalnie zdarza się sytuacja, w której trzeba skopiować dużą ilość plików z jednej maszyny na drugą. Przy tym często znacznie wzrasta LA, ponieważ wszystko sprowadza się do wydajności samych dysków.
Najsensowniejszym rozwiązaniem, które przychodzi na myśl — to użycie SSD. Naprawdę świetne. Pytanie tylko o koszt wieloterałowych SSD.
Ale jeśli dyski są zwykłe, pliki trzeba skopiować, a to dodatkowo produkcyjny system, gdzie przeciążenia prowadzą do niezadowolonych głosów klientów? Jest co najmniej dwa przydatne narzędzia: miły i ionice.
miły — zmniejsza priorytet procesu, w związku z czym scheduler przydziela więcej kwantów czasu innym, bardziej priorytetowym procesom.
W naszej praktyce pomogło ustawienie nice na maksymalny (19 — to minimalny priorytet, -20 (minus 20) — maksymalny).
ionice — odpowiednio koryguje priorytet wejścia/wyjścia (I/O scheduling)
Jeśli używasz RAID i nagle potrzebuje on synchronizacji (po nieudanym reboocie lub potrzebne jest przywrócenie macierzy RAID po wymianie dysku), w niektórych sytuacjach warto zmniejszyć prędkość synchronizacji, aby pozostałe procesy mogły działać w miarę normalnie. W tym pomoże następujące polecenie:
echo 1000 > /proc/sys/dev/raid/speed_limit_max
Problem piąty: Jak synchronizować pliki w czasie rzeczywistym
Mamy te same ogromne ilości plików, które musimy zbackupować na drugi serwer, aby uniknąć... Pliki są cały czas zapisywane, dlatego aby zminimalizować straty, należy je kopiować maksymalnie szybko.
Standardowe rozwiązanie: Rsync przez SSH.
To dobry wariant, jeśli nie trzeba tego robić co kilka sekund. A plików jest dużo. Nawet jeśli ich nie kopiujemy — i tak trzeba jakoś zrozumieć, co się zmieniło, a porównanie kilku milionów plików to czas i obciążenie dysków.
Tzn. musimy od razu wiedzieć, co trzeba skopiować, bez uruchamiania porównania za każdym razem.
Ratunek — lsyncd. Lsyncd — . Działa również przez rsync, ale dodatkowo monitoruje system plików pod kątem zmian, korzystając z inotify i fsevents, i uruchamia kopiowanie tylko dla tych plików, które zostały dodane lub zmienione.
Problem szósty: jak zrozumieć, kto obciąża dyski
Pewnie wszyscy to wiedzą, ale dla pełni obrazu: do monitorowania podsystemu dyskowego istnieje polecenie iotop — coś w rodzaju top, ale pokazuje procesy, które najbardziej aktywnie wykorzystują dyski.

Swoją drogą, stary dobry top również pozwala zrozumieć, czy są problemy z dyskami, czy nie. Do tego są dwa najbardziej odpowiednie parametry: Load Average i IOwait.

Pierwszy pokazuje, ile procesów czeka na obsługę, zazwyczaj więcej niż 2 już wskazuje, że coś jest nie tak. Przy aktywnym kopiowaniu na serwery zapasowe dopuszczamy do 6-8, po tym sytuacja uważana jest za nieprawidłową.
Drugi — w jakim stopniu procesor jest zajęty operacjami dyskowymi. IOwait >10% — powód do niepokoju, chociaż na naszych serwerach o specyficznym profilu obciążenia bywa stabilnie 40-50%, i to naprawdę norma.
Na tym zakończę, chociaż z pewnością istnieje wiele kwestii, z którymi nie mieliśmy do czynienia, z przyjemnością czekam na komentarze i opisy interesujących rzeczywistych przypadków.
Źródło: habr.com
