L'idea dell'articolo è nata spontaneamente da una discussione nei commenti all'articolo .

Il fatto è che la specificità interna del lavoro dei nostri servizi è la memorizzazione di un'enorme quantità di file di piccole dimensioni. Attualmente abbiamo circa centinaia di terabyte di questi dati. E ci siamo imbattuti in alcune ovvie e non così evidenti insidie e le abbiamo affrontate con successo.
Perciò condivido la nostra esperienza, potrebbe tornare utile a qualcuno.
Primo problema: «No space left on device»
Come accennato nell'articolo sopra citato, il problema è che ci sono blocchi liberi nel filesystem, ma gli inode sono esauriti.
È possibile controllare il numero di inode utilizzati e liberi con il comando df -ih:

Non ripeterò l'articolo, in breve, sul disco ci sono sia blocchi per i dati che blocchi per la meta-informazione, questi ultimi sono gli inode (index node). Il loro numero è definito al momento dell'inizializzazione del filesystem (si parla di ext2 e dei suoi eredi) e non cambia più. L'equilibrio tra i blocchi di dati e gli inode è calcolato su dati medi, nel nostro caso, avendo molti file di piccole dimensioni, l'equilibrio deve spostarsi verso il numero di inode—ce ne devono essere di più.
In Linux hanno già previsto varianti con diversi equilibri, e tutte queste configurazioni pre-calcolate si trovano nel file /etc/mke2fs.conf.
Pertanto, durante l'inizializzazione iniziale del filesystem tramite mke2fs, è possibile specificare il profilo desiderato.
Ecco alcuni esempi dal file:
small = {
blocksize = 1024
inode_size = 128
inode_ratio = 4096
}
big = {
inode_ratio = 32768
}
largefile = {
inode_ratio = 1048576
blocksize = -1
}
È possibile scegliere l'opzione desiderata utilizzando il parametro «-T» al momento della chiamata di mke2fs. Si possono anche specificare manualmente i parametri desiderati, se non è disponibile una soluzione pronta.
Maggiori dettagli sono descritti nei manuali per mke2fs.conf e mke2fs.
Una peculiarità non trattata nell'articolo sopra citato è che è possibile specificare la dimensione del blocco dati. È evidente che per file di grandi dimensioni è sensato avere una dimensione del blocco maggiore, per i file piccoli, invece, una dimensione minore.
Tuttavia, è importante considerare una caratteristica interessante, come l'architettura del processore.
Un giorno mi sono reso conto che per grandi file fotografici avevo bisogno di una dimensione di blocco più grande. Era tutto in un ambiente domestico, su un'unità di archiviazione domestica WD basata su architettura ARM. Senza pensarci troppo, ho impostato la dimensione del blocco a 8k o 16k invece del normale 4k, dopo aver misurato i risparmi. E tutto andava benissimo fino al momento in cui l'unità di archiviazione non ha smesso di funzionare, mentre il disco era sano. Mettendo il disco in un computer normale con un normale processore Intel, ho ricevuto una sorpresa: dimensione del blocco non supportata. Eccoci. I dati ci sono, tutto bene, ma non si possono leggere. I processori i386 e simili non possono lavorare con dimensioni di blocco che non corrispondono alla dimensione di pagina della memoria, e questa è esattamente 4k. Insomma, la faccenda si è conclusa con l'uso di utility nello spazio utente, tutto è stato lento e triste, ma i dati sono stati salvati. Chi è interessato, cerchi per nome dell'utilità fuseext2. Morale: o pensare a tutti i casi in anticipo, o non comportarsi da supereroe e utilizzare le impostazioni standard per le casalinghe.
UPD. A seguito dell'osservazione dell'utente specifico che per i386 la dimensione del blocco non deve superare 4k, ma non deve necessariamente essere esattamente 4k, quindi sono ammesse anche 1k e 2k.
Quindi, come abbiamo risolto i problemi.
Innanzitutto, ci siamo imbattuti nel problema quando un disco da molti terabyte era pieno di dati e non potevamo modificare la configurazione del file system.
In secondo luogo, era necessaria una soluzione urgente.
Alla fine, abbiamo concluso che era necessario cambiare l'equilibrio, riducendo il numero di file.
Per ridurre il numero di file, è stato deciso di raggrupparli in un unico archivio. Considerando la nostra specificità, abbiamo raggruppato in un unico archivio tutti i file per un certo intervallo di tempo, eseguendo l'archiviazione tramite un compito cron ogni notte.
È stato selezionato un archivio zip. Nei commenti all'articolo precedente era stato suggerito il tar, ma con esso c’è una difficoltà: non ha un indice e i file sono disposti in modo sequenziale (non a caso «tar» è un'abbreviazione di «Tape Archive», un'eredità dei nastri di memoria), cioè se è necessario leggere un file alla fine dell'archivio, è necessario leggere tutto l'archivio, poiché non ci sono offset per ogni file rispetto all'inizio dell'archivio. Quindi è un'operazione lunga. In zip è tutto molto migliore: ha quel famoso indice e offset dei file all’interno dell’archivio, e il tempo di accesso a ciascun file non dipende dalla sua posizione. Inoltre, nel nostro caso era possibile impostare l'opzione di compressione «0», poiché tutti i file erano già stati compressi in gzip.
I clienti prelevano i file tramite nginx e, secondo il vecchio API, basta specificare il nome del file, per esempio in questo modo:
http://www.server.com/hydra/20170416/0453/3bd24ae7-1df4-4d76-9d28-5b7fcb7fd8e5
Per decomprimere i file al volo, abbiamo trovato e collegato il modulo nginx-unzip-module () e abbiamo configurato due upstream.
Il risultato è stata la seguente configurazione:

Due host nelle impostazioni apparivano così:
server {
listen *:8081;
location / {
root /home/filestorage;
}
}server {
listen *:8082;
location ~ ^/hydra/(d+)/(
y) {
root /home/filestorage;
file_in_unzip_archivefile "/home/filestorage/hydra/$1/$2.zip";
file_in_unzip_extract "$2/$3";
file_in_unzip;
}
}
E la configurazione degli upstream nel nginx superiore:
upstream storage {
server server.com:8081;
server server.com:8082;
}
Come funziona:
- Il cliente si collega a nginx frontale
- Il frontale nginx cerca di restituire il file dal primo upstream, cioè direttamente dal file system
- Se il file non è presente, cerca di restituirlo dal secondo upstream, che tenta di trovare il file all'interno dell'archivio
Il secondo problema: di nuovo «No space left on device»
Questo è il secondo problema che abbiamo affrontato quando ci sono molti file nella cartella.
Stiamo cercando di creare un file, il sistema segnala che non c'è spazio. Cambiamo il nome del file e proviamo a crearlo di nuovo.
Funziona.
Apparentemente sembra così:

Controllare gli inode non ha dato risultati: ce ne sono molti disponibili.
Controllare lo spazio: lo stesso risultato.
Abbiamo pensato che magari ci fossero troppi file nella cartella, e c’è un limite, ma di nuovo no: Numero massimo di file per directory: ~1.3 × 10^20
Infatti, il file può essere creato se si cambia il nome.
Conclusione: il problema è nel nome del file.
Ulteriori indagini hanno rivelato che il problema è nell'algoritmo di hashing durante la costruzione dell'indice della cartella; con un gran numero di file si osservano collisioni con tutte le conseguenze del caso. Maggiori dettagli possono essere letti qui:
È possibile disattivare questa opzione, ma… la ricerca di un file per nome potrebbe diventare imprevedibilmente lunga quando si esaminano tutti i file.
tune2fs -O "^dir_index" /dev/sdb3
In generale, come soluzione temporanea potrebbe funzionare.
Morale: avere molti file in una directory è solitamente negativo. Non si dovrebbe fare così.
Di solito, in tali casi, si creano sottocartelle, basate sulle prime lettere del nome del file o su altri parametri, come le date; nella maggior parte dei casi, questo aiuta.
Ma il numero complessivo di file piccoli rimane un problema, anche se divisi in cartelle: vedi il primo problema.
Problema tre: come visualizzare un elenco di file se ce ne sono molti
Nella nostra situazione, in cui abbiamo molti file, ci siamo comunque trovati di fronte al problema di come visualizzare il contenuto di una directory.
La soluzione standard è il comando ls.
Ok, vediamo che cosa succede con 4772098 file:
$ time ls /home/app/express.repository/offercache/ >/dev/null
real 0m30.203s
user 0m28.327s
sys 0m1.876s
30 secondi… è un po' troppo. Inoltre, la maggior parte del tempo viene spesa nell'elaborazione dei file nell'user space, non nel lavoro del kernel.
Ma c'è una soluzione:
$ time find /home/app/express.repository/offercache/ >/dev/null
real 0m3.714s
user 0m1.998s
sys 0m1.717s
3 secondi. Dieci volte più veloce.
Evviva!
UPD.
Una soluzione ancora più veloce dall'utente — disattivando l'ordinamento di ls
time ls -U /home/app/express.repository/offercache/ >/dev/null
real 0m2.985s
user 0m1.377s
sys 0m1.608s
Problema quattro: alto LA quando si lavora con i file
Si presenta periodicamente la situazione in cui è necessario copiare un sacco di file da una macchina all'altra. In questo caso, spesso il LA cresce vertiginosamente, poiché tutto dipende dalle prestazioni dei dischi stessi.
La cosa più sensata da fare è utilizzare SSD. È davvero fantastico. L'unica questione è il costo degli SSD multi-terabyte.
Ma se i dischi sono normali, è necessario copiare i file, e ciò si verifica in un sistema di produzione, dove il sovraccarico porta a lamentele da parte dei clienti? Esistono almeno due strumenti utili: nice e ionice.
nice — riduce la priorità del processo, in modo che lo scheduler distribuisca più quanta di tempo ad altri processi con priorità superiore.
Nella nostra esperienza, ci è stato utile impostare il nice al massimo (19 è la priorità minima, -20 (meno 20) è la massima).
ionice — corregge di conseguenza la priorità di input/output (I/O scheduling)
Se utilizzi il RAID e necessita improvvisamente di sincronizzarsi (dopo un riavvio non riuscito o per il ripristino dell'array RAID dopo la sostituzione di un disco), in alcune situazioni ha senso ridurre la velocità di sincronizzazione affinché gli altri processi possano funzionare in modo più o meno adeguato. A questo proposito, può essere utile il seguente comando:
echo 1000 > /proc/sys/dev/raid/speed_limit_max
Problema cinque: Come sincronizzare i file in tempo reale
Abbiamo le stesse enormi quantità di file che devono essere sottoposti a backup su un secondo server per evitare... I file vengono continuamente scritti, quindi per avere il minimo di perdite, devono essere copiati il più rapidamente possibile.
La soluzione standard: Rsync over SSH.
È una buona opzione, a meno che non sia necessario farlo ogni pochi secondi. E i file sono tanti. Anche se non li si copia, è comunque necessario capire cosa sia cambiato, e confrontare diversi milioni di file richiede tempo e carico sui dischi.
Cioè, dobbiamo sapere subito cosa copiare, senza lanciare il confronto ogni volta.
La salvezza è: lsyncd. Lsyncd — . Funziona anche tramite rsync, ma monitora aggiuntivamente il file system alla ricerca di modifiche utilizzando inotify e fsevents e avvia la copia solo per i file che sono stati aggiunti o modificati.
Problema sei: come capire chi sta sovraccaricando i dischi
Probabilmente lo sanno tutti, ma comunque per completezza: per monitorare il sottosistema disco esiste un comando iotop — simile a top, ma mostra i processi che utilizzano di più i dischi.

A proposito, il vecchio e caro top permette anche di capire se ci sono problemi con i dischi. A questo scopo, ci sono due parametri più pertinenti: Load Average e IOwait.

Il primo mostra quante operazioni sono in attesa di essere gestite; di solito, più di 2 significa che qualcosa non va. Durante la copia attiva sui server di backup, tolleriamo fino a 6-8, dopo di che la situazione è considerata fuori norma.
Il secondo indica quanto il processore è impegnato nelle operazioni di disco. IOwait >10% è motivo di preoccupazione, anche se nei nostri server con un profilo di carico specifico abbiamo stabilmente il 40-50%, e questo è realmente la norma.
Concludo qui, anche se ci sono sicuramente molti aspetti che non abbiamo affrontato; attendo con piacere commenti e descrizioni di casi reali interessanti.
Fonte: habr.com
