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

Il punto è che la nostra infrastruttura prevede il salvataggio di enormi quantità di piccoli file. Attualmente abbiamo circa centinaia di terabyte di dati. E abbiamo incontrato alcune ovvie e non così ovvie problematiche e le abbiamo affrontate con successo.
Perciò condivido la nostra esperienza, potrebbe essere utile a qualcuno.
Primo problema: «No space left on device»
Come accennato nell'articolo precedente, il problema è che ci sono blocchi liberi nel filesystem, ma gli inode sono esauriti.
Puoi controllare il numero di inode utilizzati e disponibili con il comando df -ih:

Non voglio ripetere l'articolo, ma in breve, su un disco ci sono sia blocchi per i dati sia blocchi per le informazioni meta, ovvero gli inode (index node). Il loro numero è fissato al momento dell'inizializzazione del filesystem (si parla di ext2 e dei suoi discendenti) e non cambia in seguito. L'equilibrio tra i blocchi dati e gli inode è calcolato sulla base di dati statistici medi; nel nostro caso, avendo molti piccoli file, l'equilibrio dovrebbe spostarsi a favore del numero di inode — devono essercene di più.
In Linux sono già previste opzioni con diversi equilibri, e tutte queste configurazioni precalcolate si trovano nel file /etc/mke2fs.conf.
Pertanto, durante l'inizializzazione 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
}
Puoi scegliere l'opzione desiderata con l'opzione «-T» durante la chiamata a mke2fs. È anche possibile specificare manualmente i parametri necessari, se non esiste una soluzione pronta.
Maggiore dettagli sono descritti nei manuali per mke2fs.conf e mke2fs.
Una caratteristica non trattata nell'articolo precedente è che si può specificare la dimensione del blocco dati. Ovviamente, per file di grandi dimensioni ha senso avere blocchi più grandi, per file piccoli, invece, è meglio avere blocchi più piccoli.
Tuttavia, è importante tenere conto di una caratteristica interessante: l'architettura della CPU.
In un certo momento, ho pensato che per i grandi file di foto avessi bisogno di un blocco più grande. Era tutto a casa, su un NAS WD con architettura ARM. Non ci ho pensato due volte e ho impostato la dimensione del blocco a 8k o 16k invece del standard 4k, dopo aver misurato il risparmio. E tutto funzionava perfettamente fino al momento in cui il NAS si guastò, anche se il disco era integro. Mettendo il disco in un PC normale con un processore Intel, ho ricevuto una sorpresa: dimensione del blocco non supportata. Non ci siamo riusciti. I dati ci sono, tutto bene, ma non si possono leggere. I processori i386 e simili non possono gestire dimensioni di blocco che non corrispondono alla dimensione della pagina di memoria, la quale è esattamente 4k. Alla fine, abbiamo dovuto usare utilità dal user space; è stato tutto lento e triste, ma i dati sono stati salvati. Chi è interessato — google per il nome dell’utilità fuseext2. Morale: o pianificate tutto in anticipo, o non comportatevi da supereroe e usate le impostazioni standard per le casalinghe.
UPD. A seguito del commento dell'utente preciso che per i386 la dimensione del blocco non deve superare 4k, ma non deve necessariamente essere esattamente 4k, quindi sono ammissibili anche 1k e 2k.
Dunque, come abbiamo risolto i problemi.
Innanzitutto, ci siamo trovati di fronte al problema quando un disco multimille terabyte era pieno di dati, e non potevamo modificare la configurazione del filesystem.
In secondo luogo, era necessario trovare una soluzione urgente.
Alla fine, abbiamo concluso che dovevamo cambiare l'equilibrio, riducendo il numero di file.
Per ridurre il numero di file, abbiamo deciso di raggruppare i file in un unico archivio. Considerando la nostra specificità, abbiamo accorpato in un solo archivio tutti i file di un certo lasso di tempo, eseguendo la compressione con un'attività cron giornaliera di notte.
Abbiamo scelto l'archivio zip. Nei commenti all'articolo precedente era stato suggerito tar, ma con esso c'è una difficoltà: non ha un indice e i file sono memorizzati in modo continuo (non a caso «tar» è un'abbreviazione per «Tape Archive», eredità dei supporti a nastro), cioè, se devi leggere un file alla fine dell'archivio, devi leggere tutto l'archivio, poiché non ci sono offset per ogni file rispetto all'inizio dell'archivio. Pertanto, è un'operazione lunga. Con zip è tutto molto meglio: ha un indice e offset dei file all'interno dell'archivio, e il tempo di accesso a ogni file non dipende dalla sua posizione. Inoltre, nel nostro caso, è possibile specificare l'opzione di compressione «0», poiché tutti i file erano già stati compressi in gzip.
I clienti scaricano i file tramite nginx, e secondo il vecchio API, viene semplicemente indicato il nome del file, ad esempio:
http://www.server.com/hydra/20170416/0453/3bd24ae7-1df4-4d76-9d28-5b7fcb7fd8e5
Per estrarre i file al volo, abbiamo trovato e integrato il modulo nginx-unzip-module () e 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+)/(\d+)/(.+)$ {
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 sul nginx superiore:
upstream storage {
server server.com:8081;
server server.com:8082;
}
Come funziona:
- Il client si connette al front nginx
- Il front nginx cerca di restituire il file dal primo upstream, ovvero direttamente dal file system
- Se il file non esiste, cerca di restituire dal secondo upstream, che prova a trovare il file all'interno dell'archivio
Il secondo problema: di nuovo "No space left on device"
Questo è il secondo problema con cui ci siamo trovati ad affrontare, quando ci sono molti file nella directory.
Stiamo cercando di creare un file, il sistema si lamenta che non ci sia spazio. Cambiamo il nome del file e proviamo a crearlo di nuovo.
Funziona.
Appare più o meno così:

Il controllo degli inode non ha dato risultati: ce ne sono molti disponibili.
Il controllo dello spazio - lo stesso.
Abbiamo pensato che potrebbero esserci troppi file nella directory, e che ci sia un limite, ma non è così: Numero massimo di file per directory: ~1.3 × 10^20
E anche un file può essere creato, se si cambia il nome.
In sintesi, il problema è nel nome del file.
Ulteriori ricerche hanno rivelato che il problema è nell'algoritmo di hashing utilizzato per costruire l'indice della directory, con un numero elevato di file si osservano collisioni con tutte le conseguenze. Puoi leggere di più qui:
È possibile disattivare questa opzione, ma... la ricerca di un file per nome potrebbe diventare imprevedibilmente lunga mentre si scorrono tutti i file.
tune2fs -O "^dir_index" /dev/sdb3
In generale, come soluzione temporanea può funzionare.
Morale: avere molti file in una directory è generalmente dannoso. Non è una buona pratica.
Di solito in questi casi si creano directory annidate, in base alle prime lettere del nome del file o ad altri parametri, ad esempio per date, nella maggior parte dei casi salva la situazione.
Ma avere un numero elevato di piccoli file rimane problematico, anche se suddivisi in directory - perciò vedi il primo problema.
Terzo problema: come vedere l'elenco dei file, se sono molti.
Nella nostra situazione, avendo molti file, ci siamo comunque trovati ad affrontare il problema di come visualizzare il contenuto della directory.
Soluzione standard — squadra ls.
Ok, vediamo cosa otteniamo con 4772098 file:
$ time ls /home/app/express.repository/offercache/ >/dev/null
real 0m30.203s
user 0m28.327s
sys 0m1.876s
30 secondi... sono un po' troppi. Inoltre, la maggior parte del tempo viene spesa nell'elaborazione dei file nello spazio utente, e non in quello 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 — disabilitare l'ordinamento con ls
time ls -U /home/app/express.repository/offercache/ >/dev/null
real 0m2.985s
user 0m1.377s
sys 0m1.608s
Problema quattro: alto LA durante la gestione dei file
Occasionalmente ci si trova nella situazione in cui è necessario copiare un sacco di file da una macchina a un'altra. Spesso il LA cresce in modo esponenziale, poiché tutto dipende dalle prestazioni dei dischi stessi.
La cosa più sensata da fare è utilizzare un SSD. È davvero fantastico. L'unico problema è il costo degli SSD di diversi terabyte.
Ma se i dischi sono normali, i file devono essere copiati e si tratta anche di un sistema di produzione, dove un sovraccarico porta a lamentele dei clienti? Ci sono almeno due strumenti utili: nicely e ionice.
nicely — riduce la priorità del processo, quindi lo scheduler distribuisce più quanti di tempo ad altri processi più prioritari.
Nella nostra esperienza, è stato utile impostare il nice al massimo (19 è la priorità minima, -20 (meno 20) è la massima).
ionice — regola di conseguenza la priorità dell'I/O (programmazione I/O)
Se utilizzi RAID e ha improvvisamente bisogno di sincronizzarsi (dopo un riavvio non riuscito o se è necessario ripristinare l'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 tal fine, puoi utilizzare questo comando:
echo 1000 >/proc/sys/dev/raid/speed_limit_max
Problema cinque: Come sincronizzare i file in tempo reale
Abbiamo sempre gli stessi enormi quantitativi di file, che devono essere copiati 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.
Soluzione standard: Rsync over SSH.
È una buona opzione, a meno che non sia necessario farlo ogni pochi secondi. E ci sono molti file. Anche se non si copiano, è comunque necessario capire cosa è cambiato, e confrontare diversi milioni di file implica tempo e carico sui dischi.
Cioè, dobbiamo sapere subito cosa copiare, senza avviare il confronto ogni volta.
Salvataggio — lsyncd. Lsyncd — . Funziona anch'esso tramite rsync, ma monitora ulteriormente il file system per eventuali modifiche utilizzando inotify e fsevents e avvia la copia solo per i file che sono stati creati o modificati.
Problema sei: come capire chi sta utilizzando i dischi
Probabilmente lo sanno tutti, ma per completezza: per monitorare il sottosistema dei dischi c'è il comando iotop – simile a top, ma mostra i processi che utilizzano maggiormente i dischi.

In effetti, il vecchio e caro top permette anche di capire se ci sono problemi con i dischi o meno. Ci sono due parametri più adatti per questo: Carico medio e IOwait.

Il primo mostra quanti processi sono in attesa di essere elaborati, solitamente più di 2 è già un segnale di problemi. Durante il processo di copia attiva sui server di backup, tolleriamo fino a 6-8, dopo di che la situazione è considerata anomala.
Il secondo indica quanto il processore è coinvolto nelle operazioni sui dischi. IOwait >10% è motivo di preoccupazione, anche se sui nostri server con un carico specifico possiamo avere stabilmente il 40-50%, ed è davvero la norma.
Concludo qui, anche se ci sono sicuramente molti aspetti che non ci siamo trovati ad affrontare, attendo con piacere commenti e descrizioni di casi reali interessanti.
Fonte: habr.com
