Lo storage in Linux, quali sono le feature di un filesystem moderno e come è possibile utilizzarle?

Nei primi anni 2000, sul sistema operativo Solaris, nasceva il filesystem ZFS, ovvero Zettabyte File System. Pensato per grandi deployment e per girare con abbondanti quantitativi di spazio e RAM, portava sul tavolo delle caratteristiche inedite per un filesystem, che costituiscono tutt’oggi i capisaldi di un filesystem di nuova generazione. Stiamo parlando di:

  • Compressione trasparente: lo spazio non è mai troppo e, a esclusione di file già compressi per definizione come file jpeg, mpeg e simili, è sempre possibile ottenere più capacità usabile semplicemente attivando la compressione trasparente, se il filesystem la supporta. Alcuni file traggono forte beneficio anche da compressioni blande e/o solo degli zeri, con un vantaggio anche di performance (se il processore è all’altezza) in quanto sarà necessario leggere meno dati fisici.
  • Deduplica: se avete un grosso numero di file che differiscono molto poco tra di loro, il filesystem scriverà i blocchi in comune una volta sola.
  • Copy-on-write e transazioni atomiche: nessun file verrà realmente sovrascritto ma il nuovo dato verrà scritto in un’altra zona del disco. Solo se la scrittura si è conclusa correttamente, il filesystem punterà al nuovo file. L’utente non ha alcuna interferenza e, dalla sua prospettiva, ha sovrascritto normalmente un file. Ciò rende il fileystem molto resistente agli ‘imprevisti’ e alle interruzioni di corrente, anche in assenza di journal, al punto che ZFS, ad esempio, non ha un tool come fsck (o scandisk, per intenderci).
  • Snapshotting: l’abilità di ‘fotografare’ lo stato del filesystem e poter ritornare a quello stato ogni qualvolta si voglia, come una macchina del tempo.
  • Gestione integrata e intelligente di dischi e volumi: come vedremo oggi, sarà possibile gestire volume, repliche, mirroring e caching senza dover ricorrere a utility esterne.
  • Checksumming di metadati e dati: consiste nel generare un checksum di ogni blocco prima ancora di averlo scritto su disco. Tali checksum verranno poi nuovamente verificate automaticamente a ogni ri-lettura del dato (oppure manualmente su richiesta, su tutto il disco) quindi avremo sempre la certezza che il dato coincide con quanto era stato scritto in origine. Mai più sorprese e dati corrotti, anche a distanza di svariati anni, ammesso di avere copie ridondanti dei dati (vedi punto seguente).
  • Resilienza e autoriparazione: se avete impostato delle copie dei dati, siano esse sullo stesso disco o, meglio, su dischi diversi, e il checksum in lettura dei dati non dovesse coincidere con quello atteso (siamo quindi in presenza di un dato corrotto), il filesystem andrà a leggere l’altra copia del dato sull’altro disco (che si suppone essere quella integra, ma verrà anch’essa verificata) e sostituirà quella corrotta con quella integra, senza che l’utente abbia interruzioni (tutto ciò verrà comunque loggato).

Tutto ciò è evidentemente orientato all’utilizzo enterprise, ma grazie all’open source possiamo avere queste tecnologie anche sul nostro laptop.

Una prima cosa che notiamo è che queste caratteristiche non sono facili da trovare nei filesystem di largo uso. Non troveremo nulla di tutto ciò in filesystem come, ad esempio, ext4 o xfs, che costituiscono, senza ombra di dubbio, la stragrande maggioranza dei filesystem utilizzati a livello server e desktop in ambito Linux.

Lato Windows, NTFS ci offre la compressione e una feature assimilabile allo snapshotting sotto forma di shadow copies. Situazione vagamente migliore per ReFS, ma nessuno dei due è open source.

La scelta, sostanzialmente, non è ampia: Il Linux Volume Management (LVM) offre una forma di snapshotting, ma non tutto il resto. Stratis offre parecchie delle feature di cui sopra ma è ancora una technology preview. Come tecnologie mature abbiamo, quindi, esclusivamente ZFS e btrfs.

Non esistono statistiche precise su quale dei due sia più utilizzato, ma è verosimile che il primo posto spetti proprio a ZFS, in quanto il primo a proporre tali feature nonché il più ‘anziano’ dei due. Esso, però, ha un piccolo problema: il licensing.

Anche al netto del fatto che Sun Microsystems, ormai da anni, sia stata acquisita da Oracle, la licenza di ZFS non è mai stata GPL ma CDDL. Si tratta sempre di una licenza open source che, però, è incompatibile con la GPL e che non consentirebbe di trovare ZFS già pacchettizzato in forma binaria inseme a dei binari licenziati come GPL.

Ciò che però possiamo tranquillamente fare è installarlo a posteriori con un paio di comandi da terminale e la licenza CDDL non ha comunque impedito a Ubuntu di offrirlo nell’installer ufficiale ormai dal 2019.

L’unica scelta, quindi, paragonabile a ZFS, che abbia una anche licenza GPL compatibile con Linux (permettendoci quindi di trovare i binari già pacchettizzati e rapidamente installabili) e sia oggetto di attivo sviluppo, rimane btrfs.

Vediamo qualche esempio.

La prima cosa che ci interesserà sicuramente fare sarà creare un volume. Se non ne abbiamo uno fisico, potremo comunque usare un file come supporto, creandone ad esempio uno sparso di 10GB con un:

fallocate -l 10G myfile

Nel nostro caso, tuttavia, useremo dei dischi virtualizzati e ometteremo le istruzioni per come installare btrfs o ZFS, in quanto dipenderanno dalla distribuzione che usate ma, al giorno d’oggi, si tratta di un paio di minuti e un paio di comandi da copiare e incollare.

Dal momento che le operazioni di creazione di nuovi filesystem sono sempre distruttive e non vorremmo mai piallare il drive sbagliato, tutto comincia con il leggere la mappa dei dispositivi:

root@storage:~# lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda      8:0    0   80G  0 disk 
├─sda1   8:1    0 79.5G  0 part /
├─sda2   8:2    0    1K  0 part 
└─sda5   8:5    0  497M  0 part [SWAP]
sdb      8:16   0  250G  0 disk 
sdc      8:32   0  250G  0 disk 
sdd      8:48   0  250G  0 disk 
sde      8:64   0  250G  0 disk 
root@storage:~# 

Procederemo contemporaneamente per btrfs e ZFS per mostrarvi i comandi equivalenti nella loro semplicità, ma ovviamente deciderete voi quale dei due adottare.

Proviamo, quindi, a creare due filesystem con label MMUL-ZFS e MMUL-BTRFS e impostiamo una compressione al volo:

root@storage:# mkfs.btrfs /dev/sdb -L MMUL-BTRFS
btrfs-progs v5.10.1 
See http://btrfs.wiki.kernel.org for more information.

Label:              MMUL
UUID:               1027f6ce-54e8-4be9-af67-9d198553169e
Node size:          16384
Sector size:        4096
Filesystem size:    250.00GiB
Block group profiles:
  Data:             single            8.00MiB
  Metadata:         DUP               1.00GiB
  System:           DUP               8.00MiB
SSD detected:       no
Incompat features:  extref, skinny-metadata
Runtime features:   
Checksum:           crc32c
Number of devices:  1
Devices:
   ID        SIZE  PATH
    1   250.00GiB  /dev/sdb

root@storage:~# mkdir /MMUL-BTRFS
root@storage:~# mount /dev/sdb /MMUL-BTRFS -o compress-force=zstd
root@storage:~# zpool create MMUL-ZFS /dev/sdd
root@storage:~# zfs set compression=lz4 MMUL-ZFS

Una prima differenza che salta all’occhio è che ZFS non ci ha dato alcun output. Controlliamo il risultato delle nostre operazioni:

root@storage:~# df -hT
Filesystem     Type      Size  Used Avail Use% Mounted on
udev           devtmpfs  951M     0  951M   0% /dev
tmpfs          tmpfs     194M  624K  193M   1% /run
/dev/sda1      ext4       78G  2.8G   71G   4% /
tmpfs          tmpfs     968M     0  968M   0% /dev/shm
tmpfs          tmpfs     5.0M     0  5.0M   0% /run/lock
tmpfs          tmpfs     194M     0  194M   0% /run/user/1001
/dev/sdb       btrfs     250G  3.8M  248G   1% /MMUL-BTRFS
MMUL-ZFS       zfs       241G  128K  241G   1% /MMUL-ZFS

Come si può notare nell’ultima colonna a destra e nelle ultime righe, entrambi i filesystem sono stati creati e montati correttamente nelle rispettive cartelle, dentro cui è possibile già scrivere. Vediamone i dettagli:

root@storage:~# btrfs device stats /MMUL-BTRFS
[/dev/sdb].write_io_errs    0
[/dev/sdb].read_io_errs     0
[/dev/sdb].flush_io_errs    0
[/dev/sdb].corruption_errs  0
[/dev/sdb].generation_errs  0
root@storage:~# zpool status
  pool: MMUL-ZFS
 state: ONLINE
config:

	NAME        STATE     READ WRITE CKSUM
	MMUL-ZFS    ONLINE       0     0     0
	  sdd       ONLINE       0     0     0

errors: No known data errors

Potrete già notare come entrambi i filesystem sono, appunto, già pronti e offrono, tra le altre, anche delle dashboard come quelle che state vedendo che vi informano su eventuali errori di lettura, scrittura e checksum a livello di blocchi singoli e che persistono tra un riavvio e l’altro, tenendo quindi traccia persistente e consentendovi di prenderne atto.

Creiamo un file di esempio, casuale:

root@storage:~# dd if=/dev/urandom of=/MMUL-ZFS/file-di-test bs=4M status=progress count=10
10+0 records in
10+0 records out
41943040 bytes (42 MB, 40 MiB) copied, 0.200916 s, 209 MB/s


root@storage:~# dd if=/dev/urandom of=/MMUL-BTRFS/file-di-test bs=4M status=progress count=10
10+0 records in
10+0 records out
41943040 bytes (42 MB, 40 MiB) copied, 0.160741 s, 261 MB/s

Abbiamo visto che il checksumming permette di cogliere immediatamente la corruzione dati ma, se volessimo rendere i nostri storage resilienti e auto-riparanti, dovremmo aggiungere almeno un pò di ridondanza. Vediamo come aggiungere un ulteriore disco a ciascuno dei filesystem, così da farli operare in mirroring.

Per BTRFS, aggiungiamo il device /dev/sdc al pool MMUL-BTRFS:

root@storage:~# btrfs device add /dev/sdc /MMUL-BTRFS -f

root@storage:~# btrfs balance start -dconvert=raid1 -mconvert=raid1 /MMUL-BTRFS
Done, had to relocate 4 out of 4 chunks

root@storage:~# btrfs filesystem usage /MMUL-BTRFS
Overall:
    Device size:		 500.00GiB
    Device allocated:		   8.12GiB
    Device unallocated:		 491.88GiB
    Device missing:		     0.00B
    Used:			  81.34MiB
    Free (estimated):		 247.90GiB	(min: 247.90GiB)
    Free (statfs, df):		 247.90GiB
    Data ratio:			      2.00
    Metadata ratio:		      2.00
    Global reserve:		   3.25MiB	(used: 0.00B)
    Multiple profiles:		        no

Data,RAID1: Size:2.00GiB, Used:40.50MiB (1.98%)
   /dev/sdb	   2.00GiB
   /dev/sdc	   2.00GiB

Metadata,RAID1: Size:2.00GiB, Used:160.00KiB (0.01%)
   /dev/sdb	   2.00GiB
   /dev/sdc	   2.00GiB

System,RAID1: Size:64.00MiB, Used:16.00KiB (0.02%)
   /dev/sdb	  64.00MiB
   /dev/sdc	  64.00MiB

Unallocated:
   /dev/sdb	 245.94GiB
   /dev/sdc	 245.94GiB

Per ZFS, invece, aggiungiamo il device /dev/sde al pool MMUL-ZFS:

root@storage:~# zpool attach MMUL-ZFS /dev/sdd /dev/sde
root@storage:~# zpool status
  pool: MMUL-ZFS
 state: ONLINE
  scan: resilvered 40.4M in 00:00:02 with 0 errors on Fri Sep  2 13:58:38 2022
config:

	NAME        STATE     READ WRITE CKSUM
	MMUL-ZFS    ONLINE       0     0     0
	  mirror-0  ONLINE       0     0     0
	    sdd     ONLINE       0     0     0
	    sde     ONLINE       0     0     0

errors: No known data errors

Come si può notare, il nostro file sarà ora presente in doppia copia (una per ogni disco) e ogni nuovo blocco di dati che scriveremo verrà scritto su entrambi i dischi, i quali saranno uno specchio dell’altro. Ciò permette, oltre ad avere della ridondanza dati e, quindi, di auto-correggere errori eventuali, anche di aumentare le performance in lettura che, pur non raddoppiando linearmente al raddoppiare dei dischi, saranno comunque maggiori rispetto a un disco singolo in quanto il carico è tra loro distribuito.

Abbiamo, di fatto, creato una situazione definibile come RAID1, a livello software e senza necessità di hardware dedicato o utility esterne.

E le snapshot? Niente di più facile. Per BTRFS dovremo prima creare una cartella (ricordiamo che le snapshot di BTRFS sono accessibili come se fossero delle semplici folder permettendoci quindi di fare il restore anche di singoli file). Ne creeremo una leggibile e scrivibile, ma è possibile passare un -r nel caso vogliate che sia read only:

root@storage:~# mkdir /MMUL-BTRFS/.snapshots

root@storage:~# btrfs subvolume snapshot /MMUL-BTRFS/ /MMUL-BTRFS/.snapshots/mysnapshot
Create a snapshot of '/MMUL-BTRFS/' in '/MMUL-BTRFS/.snapshots/mysnapshot'

Per ZFS, invece, sarà semplicemente un:

root@storage:~# zfs snapshot MMUL-ZFS@mysnapshot

root@storage:~# zfs list -t snapshot            
NAME                  USED  AVAIL     REFER  MOUNTPOINT
MMUL-ZFS@mysnapshot     0B      -     40.0M  -

Come ZFS ci ricorda, con “USED: 0 bytes”, le snapshot non hanno un costo di spazio finchè i blocchi non vengono modificati e, quindi, si accumulano delle reali differenze tra lo stato precedente e il successivo.

Possiamo pensare alle snapshot come un punto di ripristino, a cui è sempre possibile tornare, anche se nè esse nè il mirroring sono da considerare come sostituti a dei reali backup.

E per ritornare allo stato della snapshot salvata? Nulla di più facile.

Per BTRFS, la situazione è molto versatile perchè le snapshot, pur essendo delle fotografie dei dati nello stato in cui erano quando le abbiamo create, sono perfettamente accessibili dal vostro stesso file browser entrando sotto la cartella .snapshots che noi abbiamo nominato arbitrariamente come tale.

Si, avete capito bene: in questa cartella avete la copia esatta di tutto il vostro volume in tante cartelle quante snapshot avete deciso di creare e potete, quindi, anche fare il restore di singoli file semplicemente copiandoli da questa folder a quella ‘vera’, o anche limitarvi solo a leggere il contenuto di tale file (o magari fare una diff).

Per montare l’intera snapshot esistono più modi e vi rimandiamo alla doc ufficiale, ma uno di essi può essere quello di passare il parametro subvol appropriato per montarla direttamente:

root@storage:~# umount /dev/sdb
root@storage:~# mount /dev/sdb /MMUL-BTRFS -o subvol=.snapshots/mysnapshot

Per ZFS, invece, non potremo fare il restore di ogni singolo file in modo separato in quanto le snapshot non sono direttamente sfogliabili come cartelle normali, ma sarà più semplice l’esecuzione:

root@storage:~# zfs list -t snapshot
NAME                  USED  AVAIL     REFER  MOUNTPOINT
MMUL-ZFS@mysnapshot     0B      -     40.0M  -

root@storage:~# zfs rollback MMUL-ZFS@mysnapshot

Tale comando riporterà il filesystem MMUL-FFS allo stato esatto in cui era quando abbiamo creato quella snapshot e perderemo, quindi, ogni modifica fatta successivamente (nota anche che non è possibile fare un rollback del rollback in quanto il rollback elimina tutto ciò che cronologicamente avviene dopo quella ‘fotografia’, inclusa la creazione delle nuove snapshot).

Correva anche il luogo comune che ZFS richiedesse quantità di RAM esoste ma, se controlliamo quanto appena fatto sulla nostra macchina, la situazione risulta abbastanza tranquilla con 207MB di RAM in uso:

root@storage:~# free -m
               total        used        free      shared  buff/cache   available
Mem:            1934         207        1294           0         432        1582
Swap:            496           0         496

E ciò è dato dal fatto che non abbiamo attivato la feature più affamata di RAM, ovvero la deduplica dei dati. Vi è però una nota da fare: sebbene entrambi i file system facciano, come tutti i filesystem moderni, largo uso della RAM per il caching e per migliorare le prestazionioni, la cache di ZFS non viene vista come tale dai sistemi Linux e, pertanto, esso tenderà a mostrarvi una quantità di RAM utilizzata molto elevate.

Il problema è aggirabile ed è, largamente, cosmetico, in quanto tale RAM viene prontamente rilasciata da ZFS in caso di bisogno, ma è anche possibile configurarlo in modo che ne occupi pochissima.

Btrfs, in questo senso, vince, in quanto meglio integrato con Linux e non sofferente di questa problematica.

In linea generale, btrfs è arrivato pressoché in parità di feature con ZFS, sebbene non ne includa tutto il feature set e manchi di parti interessanti come la crittografia (che però è possibile comunque avere in pochi minuti creando i volumi btrfs su LUKS invece che direttamente sui dischi), la possibilità di aggiungere interi dischi come cache, di avere diversi livelli e algoritmi di compressione per ogni ‘filesystem’ nonché, per ognuno di essi, diversi recordsize.

Esso però compensa portando feature non presenti in ZFS come la possibilità di avere delle snapshot read only ma anche, a scelta, read/write liberamente sfogliabili come folder, diventando quindi dei veri e propri fork dei dati, ovvero delle versioni alternative dello steso dato, senza occupare ulteriore spazio su disco ma, di fatto, occupando solo lo spazio necessario a registrarne le differenze.

Siete, comunque, liberi di utilizzarli entrambi in quanto, ripetiamo, entrambi sono software open source. Ci sarebbe molto da aggiungere e, per entrambi i filesystem, c’è tanto da sapere e da apprendere.

Ci premeva, però, nel frattempo, farveli conoscere e mettere alla luce delle potenzialità che sono largamente sconosciute ai non addetti ai lavori e che, perchè no, possono anche essere utilizzate in ambito desktop se avete una quantità consistente e importante di dati che non volete assolutamente perdere o per evitare, ad esempio, di imbattervi nella vostra foto preferita e trovarla corrotta, chissà ormai da quanti anni, e non più recuperabile dal momento che è ormai stata scritta nella sua versione corrotta anche nei vostri backup meno recenti.

Appassionato di Linux e della cultura open-source da vent'anni, continuo a fare del mio meglio per diffondere tale filosofia e soprattutto condividere la conoscenza.

C'è sempre qualcuno, laffuori, che sta avendo un problema che tu hai già risolto e, condividendo la soluzione, puoi fare la differenza.

Se quel qualcuno sei tu, chiedi pure alla community. La trovi ovunque, ad esempio su reddit.com/r/italyinformatica, reddit.com/r/debian, reddit.com/r/ubuntu, reddit.com/r/archlinux, reddit.com/r/linux, sui forum specifici delle distro come https://bbs.archlinux.org/ oppure puoi consultare direttamente le wiki, come ad esempio quella di Arch a https://wiki.archlinux.org/title/Table_of_contents.

Perchè nessun problema andrebbe risolto più di una volta.

Tags: , , , ,