Ideea articolului a apărut spontan dintr-o discuție în comentariile la articol .

Problema este că specificitatea internă a funcționării serviciilor noastre constă în stocarea unui număr enorm de fișiere mici. În prezent, avem aproximativ sute de terabayți de astfel de date. Și ne-am lovit de câteva capcane evidente și nu foarte evidente și am reușit să le depășim.
De aceea împărtășesc experiența noastră, poate va fi de folos cuiva.
Prima problemă: „No space left on device”
Așa cum s-a menționat în articolul menționat anterior, problema este că există blocuri libere pe sistemul de fișiere, dar inode-urile s-au terminat.
Pentru a verifica numărul de inode-uri utilizate și disponibile, se poate folosi comanda df -ih:

Nu voi relata articolul, pe scurt, pe disc există atât blocuri pentru date, cât și blocuri pentru metainformații, acestea fiind inode-urile (index node). Numărul lor este stabilit la inițializarea sistemului de fișiere (vorbim despre ext2 și succesorii săi) și nu se schimbă ulterior. Echilibrul între blocurile de date și inode-uri este calculat pe baza unor date statistice medii, în cazul nostru, când avem multe fișiere mici, balansul ar trebui să se încline spre numărul de inode-uri — acestea ar trebui să fie mai numeroase.
În Linux, deja au fost prevăzute opțiuni cu un echilibru diferit, iar toate aceste configurații pre-calculate se află în fișierul /etc/mke2fs.conf.
De aceea, la inițializarea inițială a sistemului de fișiere prin mke2fs, se poate indica profilul dorit.
Iată câteva exemple din fișier:
small = {
blocksize = 1024
inode_size = 128
inode_ratio = 4096
}
big = {
inode_ratio = 32768
}
largefile = {
inode_ratio = 1048576
blocksize = -1
}
Alegerea opțiunii dorite se poate face cu opțiunea „-T” la apelarea mke2fs. De asemenea, se pot specifica manual parametrii necesari, dacă nu există o soluție gata pregătită.
Mai multe detalii sunt descrise în manualele pentru mke2fs.conf și mke2fs.
O caracteristică care nu a fost menționată în articolul menționat anterior este posibilitatea de a stabili dimensiunea blocului de date. Este evident că pentru fișiere mari are sens să existe o dimensiune mai mare a blocului, iar pentru cele mici — una mai mică.
Totuși, trebuie să ținem cont de o caracteristică interesantă, și anume arhitectura procesorului.
M-am gândit odată că am nevoie de o dimensiune mai mare a blocului pentru fișierele mari cu poze. A fost o chestiune de utilizare acasă, pe un server de stocare de tip WD pe arhitectura ARM. Fără să stau prea mult pe gânduri, am setat dimensiunea blocului la 8k sau 16k în loc de standardul de 4k, măsurând în prealabil economiile. Și totul a fost minunat, până în momentul în care stocarea a cedat, dar discul era intact. Puneți discul într-un computer obișnuit cu un procesor Intel, și am avut surpriza: dimensiune a blocului nesuportată. Ne-am împotmolit. Datele sunt acolo, totul este bine, dar nu se pot citi. procesoarele i386 și similare nu pot lucra cu dimensiuni ale blocului care nu corespund dimensiunii paginii de memorie, care este exact 4k. În esență, totul s-a terminat prin utilizarea utilitarelor din spațiul utilizatorului, totul a fost lent și trist, dar datele au fost salvate. Pentru cei interesați — căutați după numele utilitarului. fuseext2. Morala: fie să gândești toate cazurile în avans, fie să nu te comporți ca un supererou și să folosești setările standard pentru utilizatorii obișnuiți.
UPD. Ca urmare a observației utilizatorului clarific că pentru i386 dimensiunea blocului nu trebuie să depășească 4k, dar nu trebuie neapărat să fie exact 4k, adică dimensiunile de 1k și 2k sunt permise.
Deci, iată cum am rezolvat problemele.
În primul rând, ne-am confruntat cu problema când discul de multe terabytes era plin de date, iar reconfigurarea sistemului de fișiere nu era posibilă.
În al doilea rând, aveam nevoie de o soluție urgentă.
În cele din urmă, am ajuns la concluzia că trebuie să ajustăm echilibrul, reducând numărul de fișiere.
Pentru a reduce numărul de fișiere, s-a decis să arhivăm fișierele într-un singur arhivă comună. Având în vedere specificitatea noastră, am arhivat toate fișierele pentru o anumită perioadă de timp și am efectuat arhivarea printr-o sarcină cron zilnic, noaptea.
Am ales arhiva zip. În comentariile de la articolul anterior s-a sugerat tar, dar există o dificultate: acesta nu are o tabelă de conținut, iar fișierele sunt aranjate secvențial (nu degeaba «tar» este un acronim pentru «Tape Archive», moștenire de la unitățile de bandă), adică dacă vrei să citești un fișier la sfârșitul arhivei, trebuie să citești întreaga arhivă, deoarece nu există offset-uri pentru fiecare fișier în raport cu începutul arhivei. Așadar, aceasta este o operațiune îndelungată. În zip este mult mai bine: are tocmai acea tabelă de conținut și offset-uri pentru fișiere în interiorul arhivei, iar timpul de acces la fiecare fișier nu depinde de poziția sa. În cazul nostru, am putut seta opțiunea de compresie „0”, deoarece toate fișierele au fost deja comprimate în gzip.
Clienții descarcă fișierele prin nginx, și conform vechiului API, se specifică simplu numele fișierului, de exemplu:
http://www.server.com/hydra/20170416/0453/3bd24ae7-1df4-4d76-9d28-5b7fcb7fd8e5
Pentru a decomprima fișierele în timp real, am găsit și conectat modulul nginx-unzip-module () și am configurat două upstream-uri.
Ca rezultat, am obținut următoarea configurație:

Cele două gazde din setări arătau astfel:
server {
listen *:8081;
location / {
root /home/filestorage;
}
}server {
listen *:8082;
location ~ ^/hydra/(d+)/(d+)/(.*)$ {
root /home/filestorage;
file_in_unzip_archivefile "/home/filestorage/hydra/$1/$2.zip";
file_in_unzip_extract "$2/$3";
file_in_unzip;
}
}
Și configurația upstream-urilor pe nginx-ul superior:
upstream storage {
server server.com:8081;
server server.com:8082;
}
Cum funcționează:
- Clientul se îndreaptă spre front nginx
- Front nginx încearcă să livreze fișierul din primul upstream, adică direct de pe sistemul de fișiere
- Dacă fișierul nu există — încearcă să-l ofere din al doilea upstream, care caută fișierul în interiorul arhivei
Problema a doua: din nou „No space left on device”
Aceasta este a doua problemă cu care ne-am confruntat când erau multe fișiere în director.
Încercăm să creăm un fișier, iar sistemul se plânge că nu mai este loc. Schimbăm numele fișierului și încercăm din nou să-l creăm.
Reușim.
Arată aproximativ astfel:

Verificarea inodes nu a dat rezultate — sunt multe libere.
Verificarea locului — același lucru.
Ne-am gândit că poate sunt prea multe fișiere în director, iar pentru asta există o limită, dar din nou nu: Numărul maxim de fișiere pe director: ~1.3 × 10^20
Și se poate crea un fișier, dacă schimbi numele.
Concluzia — problema este în numele fișierului.
Căutările ulterioare au arătat că problema este în algoritmul de hashare folosit la construirea indexului directorului, la un număr mare de fișiere se observă coliziuni cu toate consecințele care decurg din acestea. Mai multe detalii pot fi citite aici:
Această opțiune poate fi dezactivată, dar… căutarea fișierelor după nume ar putea deveni imprevizibil de lungă atunci când treceți prin toate fișierele.
tune2fs -O "^dir_index" /dev/sdb3
În general, ca soluție temporară, aceasta ar putea funcționa.
Morala: un număr mare de fișiere într-un director este de obicei rău. Nu ar trebui să procedați astfel.
De obicei, în astfel de cazuri, se creează subdirectoare, după primele litere ale numelui fișierului sau după alte criterii, de exemplu, după date, ceea ce în majoritatea cazurilor ajută.
Dar numărul total de fișiere mici rămâne încă o problemă, chiar și atunci când sunt aranjate în directoare — atunci, consultați prima problemă.
Problema a treia: cum să vizualizăm lista de fișiere dacă sunt multe
În situația noastră, când avem multe fișiere, cu siguranță ne-am confruntat cu problema de a vizualiza conținutul unui director.
Soluția standard este comanda ls.
Bine, să vedem ce rezultă din 4772098 de fișiere:
$ time ls /home/app/express.repository/offercache/ >/dev/null
real 0m30.203s
user 0m28.327s
sys 0m1.876s
30 de secunde… cam mult. Este important de menționat că cea mai mare parte a timpului este cheltuită pe procesarea fișierelor în spațiul utilizatorului, nu pe operațiunile nucleului.
Dar există o soluție:
$ time find /home/app/express.repository/offercache/ >/dev/null
real 0m3.714s
user 0m1.998s
sys 0m1.717s
3 secunde. De 10 ori mai rapid.
Ura!
UPD.
O soluție și mai rapidă de la utilizator — dezactivarea sortării la ls
time ls -U /home/app/express.repository/offercache/ >/dev/null
real 0m2.985s
user 0m1.377s
sys 0m1.608s
Problema a patra: un LA mare atunci când lucrați cu fișiere
Din când în când, apare situația în care trebuie să copiați o mulțime de fișiere de pe o mașină pe alta. În acest caz, de multe ori, LA crește considerabil, deoarece totul depinde de performanța discurilor.
Cel mai rațional lucru pe care doriți să-l faceți este să folosiți SSD. E cu adevărat grozav. Problema este doar costul SSD-urilor de mai multe terabayți.
Dar dacă discurile sunt obișnuite, fișierele trebuie copiate, iar acest lucru se întâmplă totodată într-un sistem de producție, unde supraîncărcarea duce la nemulțumirea clienților? Există cel puțin două instrumente utile: nice și ionice.
nice — diminuează prioritatea procesului, astfel scheduler-ul alocă mai multe quanta de timp altor procese cu prioritate mai mare.
În practica noastră, a fost util să setăm nice la maxim (19 — este prioritate minimă, -20 (minus 20) — prioritate maximă).
ionice — de asemenea, corectează prioritatea de intrare/ieșire (I/O scheduling)
Dacă aveți RAID și a trebuit să se sincronizeze brusc (după un reboot nereușit sau dacă trebuie să restaurați matricea RAID după schimbarea unui disc), în unele situații are sens să reduceți viteza de sincronizare, astfel încât celelalte procese să poată funcționa într-un mod mai adecvat. O comandă utilă pentru aceasta este:
echo 1000 > /proc/sys/dev/raid/speed_limit_max
Problema a cincea: Cum se sincronizează fișierele în timp real
Avem aceleași cantități uriașe de fișiere pe care trebuie să le salvăm pe un al doilea server pentru a evita… Fișierele sunt scrise constant, așa că, pentru a avea pierderi minime, trebuie să le copiem cât mai repede posibil.
Soluția standard: Rsync peste SSH.
Aceasta este o opțiune bună, dacă nu trebuie să o facem la fiecare câteva secunde. Și sunt multe fișiere. Chiar dacă nu le copiem, tot trebuie să înțelegem ce a fost modificat, iar compararea a câtorva milioane de fișiere înseamnă timp și o încărcătură pe discuri.
Adică, trebuie să știm imediat ce trebuie copiat, fără a lansa o comparație de fiecare dată.
Soluția este: lsyncd. Lsyncd — . Acesta funcționează tot prin rsync, dar monitorizează suplimentar sistemul de fișiere pentru modificări folosind inotify și fsevents și inițiază copierea doar pentru fișierele care au apărut sau s-au modificat.
Problema a șasea: cum să înțelegem cine folosește discul
Aceasta probabil că știe toată lumea, dar, totuși, pentru a completa imaginea: pentru monitorizarea subsistemului de discuri există o comandă iotop – similară cu top, dar arată procesele care folosesc cel mai activ discurile.

De altfel, vechiul și bunul top ne permite și să înțelegem dacă există probleme cu discurile sau nu. Pentru aceasta, există doi parametrii cei mai adecvați: Load Average și IOwait.

Primul arată câte procese stau în așteptare pentru servicii, de obicei peste 2 – deja ceva nu decurge cum trebuie. În timpul copierii active către serverele de backup tolerăm până la 6-8, după care situația se consideră anormală.
Al doilea – cât de ocupat este procesorul cu operațiile de disc. IOwait >10% – motiv de îngrijorare, deși la serverele noastre cu un profil de sarcină specific, avem adesea stabil 40-50%, și acesta este într-adevăr norma.
Închei aici, deși cu siguranță sunt multe aspecte cu care nu ne-am confruntat, aștept cu plăcere comentariile și descrierile unor cazuri reale interesante.
Sursa: habr.com
