ZFS
A ZFS az Oracle (leánykori nevén Sun) fájlrendszere, amely egyfajta kötetkezelőt és szoftveres RAID-megvalósítást is tartalmaz. Fő előnyei:
- Rendkívül kényelmes adminisztráció;
- SSD-k ésszerű használata (cache-ként);
- ellenőrző összegek (észleli és esetenként javítja az amúgy észrevétlen hibákat);
- a látszólag különböző rétegbe való funkciók (RAID, kötetkezelés, fájlrendszer) integrálása révén pl. RAID-helyreállításkor csak a foglalt területeket kell szinkronizálnia (és egyéb előnyök is származnak ebből; l. alant);
- röptömörítés;
- in-line deduplikáció;
- stb.
2012-ben Solarishoz, IllumOS-származékokhoz/-szerűségekhez (OpenSolaris, NexentaStor, illumian, Dyson -- ez egy IllumOS-kernelre épülő Debian; kíváncsi vagyok, mi lesz belőle --, SmartOS, OpenIndiana stb.), FreeBSD-hez és Linuxhoz létezik elérhető implementáció. Az Apple egy darabig dolgozott egy Mac OS X-porton, de úgy tűnik, felhagyott vele. A http://open-zfs.org/wiki/Features oldalon nyomon követhető, a nyílt forrású implementációk hol tartanak egymáshoz képest.
Eredetileg a Solarishoz fejlesztették ki; ez akart lenni a Nagy Végső Válasz (majdnem akkora, mint a 42) az összes fájlrendszerproblémára ("The Last Word in Filesystems").
Tartalomjegyzék[elrejtés] |
1 Alapfogalmak, építőelemek
- Pool: kb. az LVM VG megfelelője. Fizikai adattárolók (vdevek, l. lejjebb) összessége; az ezekben rendelkezésre álló tárhelyet oszthatjuk ki zvol-ok és zfs-példányok között.
- vdev: kb. az LVM PV megfelelője. Egy fizikai diszk, vagy egy több diszkből álló tömb.
- A poolban tárolt adatokat a zfs RAID0-szerűen szétteríti az egyes vdevekre.
- Egy pool bővíthető újabb vdevekkel;
- a poolhoz hozzáadott vdevek eltávolítása (speciális esetektől eltekintve) sokáig nem volt lehetséges; 2019 közepén, kb. zfsonlinux 0.8.0-tól, már igen.
- Bővítéskor nem történik újracsíkozás; az újonnan felírt adatok azonban már részben az új vdev(ek)re is kerülnek.
- Maguk a vdevek érdemben nem bővíthetők!
- vdevek lehetséges típusai:
- file (nem javasolt);
- diszk (vagy partíció);
- Ha megnő a mérete, fel tudjuk használni a plusz helyet.
- mirror (RAID1, akárhány diszkből);
- raidz, raidz1 (kb. RAID5);
- raidz2 (kb. RAID6);
- raidz3 (csíkozott, paritásos, három paritásblokkot használó elrendezés; így bármely három diszk kiesését túlélheti);
- A raidz-s vdevekhez sajnos nem tudunk új diszket adni.
- spare (melegtartalék);
- log (más néven ZIL; a szinkron írások biztonságos gyorsítására, szerializálására szolgáló opcionális írás-"cache", pl. SSD);
- A log maga is lehet mirror; sőt, ajánlatos mirrorba tenni, hiszen a szinkron írásaink eredménye egy ideig csak a ZIL-ben van meg.
- A ZIL igazából nem cache, hanem inkább journal: olyan tartós tár, amibe gyorsan ki lehet írni a szinkron írandó adatot és így gyorsan vissza lehet térni az írást kezdeményező alkalmazásba; a ZILbe írt adat pedig akkor sem vész el, ha közben elmegy az áram.
- Így a ZIL haszna igazából az, hogy a szinkron írásokat is lehet RAMban pufferelni (hiszen a ZILben megvannak arra az esetre, ha elszállunk és törlődik a RAM): tehát a szinkron írások majdnem olyan gyorsak lesznek, mint az aszinkron írások.
- A rendszer induláskor a ZILben tárolt kijelölt műveleteket el kell végezni a diszkeken is.
- cache (a poolhoz tartozó opcionális másodlagos olvasás-cache, pl. SSD).
- A zfs terminológiájában az ilyen cache az "L2ARC" (Level 2 Adaptive Replacement Cache).
- Az L2ARC kifejezetten SSD-re van optimalizálva: pl. nagy blokkokban ír.
- Kb. 2021-ig gond volt, hogy nem volt perzisztens: reboot után a cache üres volt, és csak nagyon lassan (adott esetben napok alatt) "melegedett be".
- A cache jelenleg nem lehet sem mirror, sem raidz[123].
- A cache alapértelmezés szerint csak a véletlen (nem prefetch-elhető) olvasásokat cache-eli.
- A zfs terminológiájában az ilyen cache az "L2ARC" (Level 2 Adaptive Replacement Cache).
- dataset: olyan objektum, amelynek a pool helyet biztosít. Egy poolban max. 248 darab lehet. Típusai:
- zvol: kb. az LVM LV megfelelője. Egy blokkeszköz, amin tetszőleges fs-t létrehozhatunk (vagy pl. odaadhatjuk egy virtuális gépnek, mint diszket).
- Snapshotolható, deduplikálható, röptömöríthető anélkül, hogy a benne levő fs-nek erről tudnia kéne.
- Kiajánlható iSCSI-val, ATA over Ethernettel stb.
- Csak speciális esetben érdemes használni; többnyire jobb:
- filesystem: egy zfs-példány.
- Mountolható, snapshotolható, deduplikálható, röptömöríthető stb.
- Kiajánlható NFS-sel, CIFS-szel (windows-os share-ként).
- snapshot: egy zvol vagy filesystem adott pillanatbeli, csak olvasható képe.
- clone: egy snapshot írhatóvá tett másolata; annyi helyet foglal, amennyi módosítást tárol az eredeti snapshothoz képest.
- A clone a zfs promote művelettel filesystem-mé léptethető elő; ekkor az eredeti fájlrendszer, amelyről a clone készült, válik klónná (és így megszüntethető). Ez akkor jó, ha pl. a clone-ban kipróbáltunk valamit és elégedettek vagyunk vele, úgyhogy meg szeretnénk tartani.
- zvol: kb. az LVM LV megfelelője. Egy blokkeszköz, amin tetszőleges fs-t létrehozhatunk (vagy pl. odaadhatjuk egy virtuális gépnek, mint diszket).
- A ZFS logikai/funkcionális részei:
- ZFS POSIX Layer (ZPL) -- ettől látszik fájlrendszernek.
- Tranzakciókkal bízza meg az alatta levő réteget; ezek vagy teljes egészükben végbemennek, vagy egyetlen részük sem teljesül.
- Data Management Unit (DMU).
- Ez egy általános objektumtároló; ez szavatolja az adatintegritást (ellenőrző összegekkel).
- Használható a ZPL nélkül is (ez a zvol); pl.
- közvetlenül épülhet rá adatbázis, vagy
- csinálhatunk egy zvolban tetszőleges egyéb fs-t (a Solaris/IllumOS swapelni is tud zvolra; a Linux is, de resume-olni nem tud róla), vagy
- exportálhatjuk iSCSI-n/AoE-n.
- Storage Pool Allocator (SPA).
- Ez kezeli az egész alatti fizikai diszkeket.
- Ez tömörít, blokkszinten; emellett, ha bármilyen tömörítés be van kapcsolva, automatikusan megtalálja a "lukakat" (csupa nullás területeket) a fájlokban és azoknak még csak nem is allokál helyet (tömörítettet sem).
- ZFS POSIX Layer (ZPL) -- ettől látszik fájlrendszernek.
2 Jellemzői, működése
- Rettenetesen nagy bír lenni (logikailag több adatot tud tárolni, mint amennyi a Földön található összes anyag segítségével fizikailag tárolható lenne).
- A meta-adatok is dinamikusan nőnek: akárhány fájl lehet egy könyvtárban, akárhány fájl lehet összesen stb.
- Emiatt csak 64bites rendszeren ajánlott használni.
- Nem kell előre eldöntenünk, mi mekkora legyen: a rendelkezésre álló helyből ("pool") a zfs maga gazdálkodik (nyilván állíthatunk be korlátokat).
- Egy-egy "fájlrendszer", amit létrehozunk, igazából csak a benne tárolt fájlok adminisztratív csoportosítását szolgálja.
- Nem kell várnunk, hogy a du(1) megnézze, mi foglal sok helyet; ha a releváns könyvtárak külön datasetekben vannak, a df(1) azonnal megmondja.
- Hierarchikus. A tank poolban lehet egy tank/srv nevű zfs-példány, abban egy tank/srv/www, abban egy tank/srv/www/www.domain.com stb. Ezek nemcsak alkönyvtárak, hanem különálló filerendszerek;
- önállóan mountolhatóak,
- snapshotolhatóak
- kvótázhatóak, és
- alapbeállításaikat a felmenőiktől öröklik. Ez állati kényelmes: ha sok fájlrendszeren akarunk egy propertyt átállítani, átállítjuk a közös ősükben és meg is vagyunk.
- Feltéve, hogy az adott property értékét minden leszármazott fájlrendszer a szülőtől örökli; ha kézzel módosítjuk, megszűnik az öröklés (de vissza lehet térni hozzá: zfs inherit propertyneve poolneve/datasetneve).
- Egyes adminisztrációs feladatok -- Solarison/IllumOS-en -- delegálhatók konkrét felhasználóknak: tehát pl. megengedhetjük, hogy Józsi snapshotolja a saját home-ját.
- A snapshotok a mountpoint/.zfs/snapshot/snapshotneve könyvtárakon keresztül a rendszergazda közreműködése nélkül is elérhetők.
- "Policy follows the data": egyetlen zfs mount -a paranccsal be lehet mountolni az összeset egyszerre, akkor is, ha másik gépbe raktuk a poolt, mert a mountpoint is benne tárolódik, nem az fstabban (kivéve, ha legacy-ra állítjuk a mountpointot).
- Architektúrafüggetlen.
- A byte-sorrendet úgy kezeli, hogy a blockokban egy bit jelöli, az adott block big vagy little endian;
- az írás mindig natív byte-sorrenddel történik;
- olvasáskor byte-csere kell, ha a beolvasott blokk byte-sorrendje nem azonos a CPU-éval.
- A byte-sorrendet úgy kezeli, hogy a blockokban egy bit jelöli, az adott block big vagy little endian;
- A blokkméret változó (de extenteket nem használ).
- Ha egy objektum 42k, akkor egy 42k-s blokkban fogja tárolni.
- NTFS-jellegű ACL-eket valósít meg (NFSv4 ACL; jobb, mint a POSIX ACL).
- Fájlok tulajdonosa windowsos SID is lehet (nemcsak unixos UID/GID).
- Tranzakciókezelést használ; a lemezen elvileg mindig csak konzisztens állapot létezik, tehát elvileg sosem kell fsck-zni.
- Sosem helyben írja felül az adatokat; az új adatokat új helyre írja, majd "átállítja az érvényes adatra mutató pointert".
- Így automatikusan előáll egy olcsó (értsd: gyors) snapshot is (hiszen az eredeti adatok még megvannak). A snapshotolás overheadje csak annyi, hogy ezeknek a "pointereknek" a snapshot létrehozásakor érvényes állapotát kell megőrizni.
- Integritásellenőrzés is van benne, így elvileg véd a kábelhibákból és hasonlókból eredő adathibák ellen.
- Ha olvasáskor hibát talál, a poolban meglevő redundancia segítségével javítja; ha nincs redundancia, I/O hibát kapunk, és nem férünk hozzá a hibás adathoz. Ez nem mindig jó.
- Blokkszintű deduplikációt valósít meg, ha szeretnénk.
- "RAID-Z" (RAID5-szerűség); a fő előnyei elvileg:
- Nincs szükség read-modify-write-ra (mint a RAID5/RAID6 esetében), mert dinamikus csíkszélességet használ, így minden írás mindig teljes csík.
- Példa: ha négy diszkünk van és 3000 byte-ot írunk, akkor az első három diszkre kiír 1024-1024 byte-ot és a negyedikre ezek paritását. (Azért 1024-et, mert mindig teljes fizikai blokkokat ír; ebben a konkrét esetben 72 byte-ot elpazarol.)
- A tranzakciókezelésnek köszönhetően elkerüli a "write hole" problémát: amíg nem íródott ki a paritás is és az adat is, addig nem válik érvényessé az új adat (nem mutat rá "pointer").
- Nincs szükség read-modify-write-ra (mint a RAID5/RAID6 esetében), mert dinamikus csíkszélességet használ, így minden írás mindig teljes csík.
- Az integritásellenőrzésnek köszönhetően akkor is kijavítja a hibákat, ha az olvasás látszólag sikerül (tehát amikor egy hagyományos RAID-megvalósítás nem nyúlna a paritáshoz).
- Resync ("resilvering") esetén csak azokat a blokkokat másolja, amikben tényleg van adat.
- Érdemes "scrub"-okat (teljes integritásellenőrzést) ütemezni; ilyenkor a háttérben végigolvassa az adatokat és kijavítja az esetleges hibákat, ha van redundancia. Ha nincs, akkor megtudjuk, melyik fájlunk vált olvashatatlanná. A scrub értelemszerűen elég sokáig tarthat, ha sok az adat (de lehet szüneteltetni és folytatni).
- A "fontos" metaadat-blokkokat több példányban tárolja, mint a kevésbé fontosakat.
- Egyébként egyes fs-ek szintjén is kérhetjük, hogy az adott fs-t két vagy több példányban tárolja; így akár egyetlen diszken is tudjuk a fontos adatainkat -- nyilván jókora sebességveszteség árán -- redundánsan tárolni. Ez a hibás szektorok ellen véd, a teljes diszk kiesése ellen természetesen nem. Főként notebookon lehet hasznos.
- Automatikusan csíkoz a rendelkezésre álló vdevek fölött.
- Ha új vdevet rakunk be, arra is elkezd csíkozni és a tranzakció-alapú írások miatt idővel, szép lassan, minden adat az összes diszkre szétterül.
- A 2010-es évekig nagyon komolyan vette a kompatibilitást/hordozhatóságot:
- Minden poolnak (és minden zfs-példánynak) van egy verziószáma.
- Minden megvalósítás egy konkrét verziószámig bezárólag minden verziójú poolt/fs-t képes kezelni és helyben upgrade-elni a legújabb általa ismert verzióra.
- Mivel azonban az Oracle nem látszik hivatalosan kiadni a legújabb fejlesztéseket (pl. a titkosítást) és általában sem ápol jó viszonyt a nyílt forrású közösséggel, a zfs fejlesztése több vonalon folyik párhuzamosan: az Oracle ott folytatta, ahol a Sun abbahagyta, a nyílt forráskód körül kiépült közösség pedig szintén, csak másképp. Valószínűnek látszik, hogy az Oracle termékekkel létrehozott zpoolokat a szabad termékek (még jó darabig) nem fogják támogatni és természetesen fordítva. Az utolsó hordozhatónak tekinthető pool-verzió talán a 28-as.
- Az Oracle titkosítása helyett egyébként az OpenZFS-ben lett másik, saját titkosítás, tehát a képesség megvan, csak nem kompatibilis egymással a két változat.
2.1 Deduplikáció
- Többféle hash közül választhatunk, és azt is előírhatjuk, hogy hash-egyezés esetén az adatokat hasonlítsa is össze byte-ról byte-ra, nehogy egy esetleges -- amúgy rendkívül valószínűtlen -- hash-ütközés az adatok sérülését okozza.
- A hash-ütközés valószínűségéről l. a következő alfejezetet; a lényeg az, hogy ezt az ellenőrzést nem nagyon érdemes bekapcsolni.
- Nem kötelező az összes adatot deduplikálni; szelektíven is bekapcsolhatjuk (egyes datasetekre külön-külön).
- Egyébként pontosan így is érdemes csinálni: csak azokat az adatokat deduplikálni, ahol nagyon sokat nyerünk.
- De a deduplikációs táblázat az egész poolban közös, tehát a poolon belüli különböző datasetek között is megvalósul a deduplikáció.
- A deduplikáció rendkívül memóriaigényes:
- Kb. 3-400 byte memória (L2ARC) kell minden blokkhoz; a blokkméret változó (2022-ben legfeljebb 16MiB); ha kis fájljaink vannak, egy-egy fájl egy blokk (és persze majdnem minden nagy fájl vége egy kisebb blokk).
- Ha L2ARC-ban van a deduplikációs táblázat (DDT), akkor minden max. 128k-nyi L2ARC-hoz kb. 200 byte memória kell.
- Ökölszabályok: ne deduplikáljunk, ha
- nem számítunk legalább 50% hely-megtakarításra ÉS
- nincs legalább 24, de inkább 32GB RAMunk.
- A deduplikáció, főként, ha nincs megtámogatva SSD-s cache-sel, jelentősen ronthatja a szekvenciális olvasás sebességét, hiszen fragmentációt okoz.
- Ha nincs elég RAMunk (vagy SSD-s cache-ünk), a deduplikáció több nagyságrenddel ronthatja az írás sebességét. Ez nem fog azonnal kiderülni, csak akkor, amikor a DDT elér egy bizonyos méretet.
- Jó tudni: az ARC (az operatív memóriában levő cache) maximális mérete alapértelmezés szerint az összes RAM háromnegyede, vagy az összes RAM mínusz 1GB (amelyik nagyobb). Ennek alapértelmezés szerint maximum 25%-át fordítja metaadatok (mint pl. a DDT) cache-elésére a zfs. Mindkét paraméter állítható (utóbbi neve zfs_arc_meta_limit); ha deduplikálunk, érdemes lehet növelni őket.
- A DDT nyilván a diszken is helyet foglal; ha sok az egyedi adat, a táblázat könnyen nagyobbra nőhet, mint amennyit a kevés duplikátumon megspórolunk.
- A kiírt blokkok akkor kapnak DDT-bejegyzést, ha kiírásuk pillanatában aktív a datasetjükben a deduplikáció. Következmények:
- A kikapcsolt deduplikáció mellett kiírt blokkok nem lesznek deduplikálva (nyilván).
- Ha később bekapcsoljuk a deduplikációt, és az iménti blokkok újabb példányát írjuk ki, a DDT szempontjából ezek lesznek ezeknek a blokkoknak az első példányai, vagyis szintén nem takarítunk meg tárhelyet.
- Ha utólag akarunk deduplikálni sok adatot, valószínűleg a zfs send/receive (l. később) a legelegánsabb megoldás.
- A deduplikáció aszinkron művelet; a rendszer a háttérben végzi el a ZIL vagy az írás-puffer ürítésekor.
- Így a szinkron írások nem kell, hogy megvárják;
- cserébe a ZIL hamarabb megtelhet, ha sok, amúgy deduplikálható szinkron írást kell tárolnia.
2.1.1 Mennyire valószínűtlen a hash-ütközés?
Hash-ütközésnek nevezzük azt az esetet, amikor két különböző bemenethez ugyanazt a kimenetet rendeli egy hash-függvény. Mivel a lehetséges bemenetek száma végtelen (ill. itt, még ha legfeljebb 128k nagyságú blokkokat nézünk is, 256131072 körül van, ami majdnem ugyanaz), a kimeneteké pedig véges, szükségszerűen végtelen sok olyan különböző bemenet-pár van, amelyeknek azonos a hash-e. A deduplikáció szempontjából ez azért releváns, mert alapértelmezés szerint az azonos hash-sel rendelkező adatblokkokat azonos tartalmúnak tekinti; így ütközés esetén a később kiírt deduplikált fájl (egy blokkja) helyén a korábban kiírt ütköző fájl tartalmát fogjuk látni.
Alapértelmezés szerint sha256-os hasht használ a zfs, ha bekapcsoljuk a deduplikációt (amúgy, deduplikáció nélkül, a fletcher-4 nevű checksumot használja). Ez azt jelenti, hogy 2256 különböző hash lehetséges. Ha feltesszük, hogy kb. egyenletes eloszlással rendelődik hash a különféle bemenetekhez (márpedig egy jó hashnél ez nagyjából teljesül), akkor annak az esélye, hogy két különböző bemenethez ugyanaz a hash tartozik, 1:2256-hoz. Ez rettenetesen kicsi valószínűség.
Igen ám, de számolnunk kell a születésnap-paradoxonnal is. Ahogy nő a bemenetek (blokkok) száma, nő az ütközés valószínűsége is, és korántsem lineárisan. Az imént hivatkozott wikipédia-szócikk tartalmaz egy táblázatot, amelyből kiolvashatjuk, hogy ha azt szeretnénk, hogy a hash-ütközés valószínűsége legfeljebb 10-18 legyen (tehát négy nagyságrenddel kisebb, mint a bithiba valószínűsége a merevlemezen -- másképp: tízezer bithibára jusson egy hash-ütközés), akkor legfeljebb 4,8*1029 különböző blokkunk lehet. A minimális blokkméret 512 byte, tehát 223517417907714843750 tebibájtnyi egyedi 512 bájtos blokkokból álló adatban már kb. 10-18 valószínűséggel fordul elő hash-ütközés (ez kb. 203287907 yobibyte; egy yobibyte 1024 zebibyte, egy zebibyte 1024 exbibyte, egy exbibyte 1024 pebibyte, egy pebibyte 1024 tebibyte).
Mivel a valóságban általában 512 byte-nál nagyobbak a blokkok, a fenti egy alsó becslés arra, mennyi adatot kellene tárolnunk ahhoz, hogy a hash-ütközés valószínűsége elérje a 10-18-t. Összehasonlításképpen: egy 2011-es becslés szerint az emberiség 1986. és 2011. között kb. 295 exabyte-nyi adatot hozott létre. Ennek a hétszázbilliószorosát (tényleg nem hétszázmilliárdszorosát: hétszázbilliószorosát) kellene tárolnunk, és még mindig elenyészően kicsi lenne a hash-ütközés valószínűsége.
Ha azonban ezzel együtt is kockázatosnak érezzük a dolgot, előírhatjuk, hogy az azonosnak látszó blokkokat a zfs bitről bitre hasonlítsa össze, mielőtt valóban azonosnak tekinti őket (ez természetesen nem használ a sebességnek).
2.2 Tranzakciókezelés
(A ZFS -- The Last Word in Filesystems c. prezentáció 9. oldalán van egy jó ábra, ami alapján könnyebb megérteni, hogy működik.)
A lényeg:
- Az adatok egy fastruktúrában tárolódnak.
- Ha a fa egy levelét módosítani kell, allokálunk egy új adatterületet és oda írjuk ki a módosított levelet.
- Ezután a levél szülőjében kell(ene) átírni azt a pointert, amely az érvényes adatra mutat, úgy, hogy az új blokkra mutasson; csakhogy ez is egy ugyanolyan módosítás, mint az előző, úgyhogy itt is új blokkot allokálunk, amibe beleírjuk az új szülő-csomópontot, az új gyermek-pointerrel.
- És így tovább a gyökérig (ez az "überblock").
- Azokat a blokkokat, amelyekre már nem mutat pointer, szabadnak kell jelölni; ha ezt nem tesszük meg, kész is a snapshotunk (hiszen nem írtunk felül semmit).
- Persze a blokkok felszabadítása is trükkös kell, hogy legyen, különben lehetne, hogy éppen arra nem kerül sor egy áramkimaradás miatt.
- További előny: ha van sok szabad hely, a véletlen írások (az überblokk -- vagy a rá mutató "pointer" -- frissítését leszámítva) szekvenciális írássá alakulnak.
2.3 zfs send/receive
Célja: zfs hatékony másolása egyik gépről a másikra (vagy egyik poolból a másikba).
Alapelv: snapshotot másol (így biztosítva a másolat konzisztenciáját), de:
- inkrementális módja is van, amikor két snapshot közti különbséget másoljuk;
- a különbség előállításának költsége a változások mennyiségével arányos (nem az adatok teljes mennyiségével).
- zvol esetén a receive művelet időtartama alatt a zvol nem használható; fájlrendszer típusú dataset esetén igen.
Példák (egyelőre nem próbáltam ki őket):
- Teljes backup készítése hétfőn:
zfs snapshot tank/home@hetfo zfs send tank/home@hetfo >/backup/home_teljes_hetfo.zfssend
- Inkrementális backup készítése kedden:
zfs snapshot tank/home@kedd zfs send tank/home@hetfo tank/home@kedd >/backup/home_inkr_kedd-hetfo.zfssend
- Differenciális backup készítése szerdán:
zfs snapshot tank/home@szerda zfs send tank/home@hetfo tank/home@szerda >/backup/home_diff_szerda-hetfo
- Megj.: az "inkrementális" és "differenciális" backup fogalmának értelmezése nem egységes (az egyik termék így használja, a másik esetleg pont fordítva vagy legalábbis másképp). Itt a Bacula szóhasználatához igyekeztem igazodni.
- Percenkénti távoli replikáció:
zfs send -i tank/home@10:13 tank/home@10:14 | ssh masikgep zfs receive -d /tank
- A zfs send és az ssh közé érdemes lehet beépíteni valamit a pipeline-ba, ami pufferel; pl. pv, buffer, mbuffer. Ez sokat tud gyorsítani az átvitelen, mert így a send és a receive folyamat párhuzamosan fog dolgozni, nem pedig lényegében felváltva, mint az egyszerű pipe esetén, ami jó esetben is csak kb. 64KiB-nyi adatot pufferel.
3 ZFS Linux alatt
- Linuxra párhuzamosan készült egy (2022-re teljesen felejthető) userspace és egy kernelspace megvalósítás.
- A kernelspace implementáció licencproblémák miatt sosem kerülhet bele a hivatalos kernelbe (az OpenSolarist, aminek a ZFS a része, a CDDL alatt teszik elérhetővé, ez pedig -- bár erről némileg megoszlanak a vélemények -- jogilag a Sun eredeti szándéka szerint nem kompatibilis a GPL-lel). Az egyetlen megoldás az lenne, hogy valaki nulláról újraírja a ZFS-t (úgy, hogy az adatstruktúrák és algoritmusok ugyanazok, de a kód teljesen új).
- A FUSE-os megoldásnak nincsenek ilyen gondjai, mivel a GPL-nek nem kell megfelelnie (de természetesen a kernelbe amúgy sem tudna bekerülni).
- Jelen sorok írásakor (2012. őszén) a kernelspace-verzió használata az ajánlott; a zfs-fuse fejlesztése megtorpant, a fejlesztők már szintén a zfsonlinuxot (a kernelbeli megvalósítást) használják.
- A zfs-fuse-t ugyanakkor egyszerűbb telepíteni, úgyhogy a zfs kipróbálására azért még alkalmas lehet.
- Vigyázat! A zfs-fuse 2012. vége felé aktuális, 0.7.0-ás verziója bizonyos terhelések mellett instabil (nálam nem okozott adatvesztést, de rendelkezésreállási problémákat igen).
- A zfs-fuse régebbi pool- és zfs-verziókat implementál, mint a zfsonlinux, így a zfsonlinuxszal létrehozott poolt nem használhatjuk vele (de fordítva igen).
4 Esettanulmányok
4.1 Linuxos gép telepítése zfs-sel
Tegyük fel, hogy van egy gépünk, benne két SSD-vel (sda, sdb), két HDD-vel (sdc, sdd), 16+GB RAM-mal, és hogy erre Devuan/GNU (vagy akár Debian/GNU) Linuxot szeretnénk telepíteni. Az alábbiakban leírom ennek a folyamatnak néhány lépését és elmagyarázom a tervezői döntéseket.
- Bootoljunk be valamilyen live médiumról (pl. GRML-es USB-kulcsról).
- Haladók készíthetnek maguknak saját, a zfs-támogatást eleve tartalmazó grml-t; kulcsszó: "grml remastering". Aki látott már FAI-t, annak ismerős lesz.
- Rakjuk fel a futó grml-re a Devuan ceresből (vagy a Debian sidből) a következő csomagokat:
zfsutils-linux zfs-dkms spl-dkms
- Ehhez valószínűleg át kell írnunk a /etc/apt/sources.list környékét.
- Particionáljuk a diszkeket és SSD-ket; lehetőleg használjunk GPT-t.
- A biztonság kedvéért csináljunk külön partíciókat a /boot-ot tartalmazandó RAID1-tömb számára. Elvileg ugyan tud zfs-ről bootolni a grub, de az ördög nem alszik.
- Ne felejtsünk el egy párszáz szektorból álló, bios_grub flaggel ellátott partíciót csinálni a diszk elejére (mondjuk a 34-edik szektortól az 511-edikig), hogy a grub tudja hova másolni magát.
- A RAID-tömbünket alkotandó partíciókat lássuk el a raid flaggel.
- Hozzunk létre az SSD-ken egy további RAID1-tömböt a swapnek; elvileg lehet ugyan zfs-re swapelni, de resume-olni biztosan nem lehet róla, és voltak bugok a swap körül, amik fagyáshoz/panichoz vezethettek.
- A maradék helyen hagyjunk egy-egy partíciót, amit majd a zfs fog használni (ezek a partíciók az sd[ab]4 ill. sd[cd]3 lesznek).
- Az SSD-kre L2ARC fog kerülni (zfs olvasás-cache); ZIL-t ugyanezekre az SSD-kre nem érdemes tenni.
- A ZIL-t tartalmazó eszközön érdemes kikapcsolni az írások cache-elését, ez viszont az SSD-knél teljesítmény- és élettartam-csökkenéssel jár.
- A ZIL-nek akkorának kell lennie, hogy pár másodpercnyi szinkron írás elférjen rajta; ez ritkán több, mint 1-2GB. Az 1-2GB-os ZIL kedvéért ne kelljen már az ennél jóval nagyobb SSD cache-ét kikapcsolni...
- Ha a szinkron írásokat akarjuk gyorsítani, inkább legyen cache-elő diszkvezérlőnk (BBWC-s vagy FBWC-s).
for disk in /dev/sd[a-d]; do parted -s $disk mklabel gpt parted -s $disk mkpart grub 34s $[8192*512-1]B parted -s $disk toggle 1 bios_grub parted -s $disk mkpart raid1_boot $[8192*512]B 256M parted -s $disk toggle 2 raid done for disk in /dev/sd[ab]; do parted -s $disk mkpart raid1_swap 256M 8G # kb. 8GB swap; nyilván lehet több, vagy kevesebb is parted -s $disk mkpart l2arc 8G 100% # a hely fennmaradó része cache lesz done for disk in /dev/sd[cd]; do parted -s $disk mkpart 256M 100% # a hely fennmaradó része zfs adatterület lesz done mdadm --create /dev/md0 --assume-clean --level=1 --raid-devices=4 /dev/sd[ab]2 -W /dev/sd[cd]2 mke2fs -b 4096 -L boot -m 0 -t ext2 /dev/md0 mdadm --create /dev/md1 --assume-clean --level=1 --raid-devices=2 /dev/sd[ab]3 mkswap -f -L swap0 /dev/md1 swapon /dev/md1
- Hozzuk létre azt a könyvtárat, amibe majd telepítünk:
mkdir /t
- Hozzuk létre a zpoolt:
zpool create -o ashift=12 -o autoexpand=on -o autoreplace=on -R /t tank \
- Magyarázat: ashift=12 -- csak a pool létrehozásakor állítható property; azt adja meg (log2 alapon), mekkora "szektormérettel" dolgozzon a zfs.
- Azért állítjuk 4k-ra, mert valószínű, hogy később kerül 4k-s szektorokat használó diszk a poolba (ha nincs is eleve).
- Azért nem állítjuk még nagyobbra, mert az SSD-nek valószínűleg 128k körüli (vagy még nagyobb) méret kedvezne, azzal viszont túl sok helyet pazarolnánk el.
- -o autoexpand=on -- ha megnő a poolt alkotó vdev, a zfs kezdje el használni a több helyet anélkül, hogy ezt külön kérnénk.
- -o autoreplace=on -- ha a poolt alkotó egyik diszk kiesik, és a ZFS egy újat talál a helyén, akkor automatikusan elkezdi használni, manuális beavatkozás (zfs replace parancs) nélkül.
- -R /t -- állítsuk az altroot property-t /t-re. Így, amíg a live rendszeren dolgozunk, a /t (valójában /t/mountpoint, de a mountpoint "/" lesz) alatt jönnek létre az új zfs-eink, de ha később a valódi rendszert bootoljuk be, minden az elvárt helyén lesz.
- tank -- így fogják hívni a létrehozandó poolt. A "tank" név dokumentációs konvenció; a valóságban ne így nevezzük a poolt, hanem utaljon a név a funkciójára (pl. "gepnev-root").
- mirror /dev/sd[cd]4 -- egy tükrözött (tehát RAID1-szerű) vdevet adunk meg, amit a felsorolt két partíció fog alkotni.
- cache /dev/sd[ab]3 -- a két SSD 3-as partícióját L2ARC-nak fogjuk használni.
- -O atime=off -- alapból kikapcsoljuk az atime update-eket az uj fs-eken (ahol kell, majd külön bekapcsoljuk). Ezzel nyerünk egy kis teljesítőképességet.
- -o-val a poolra, -O-val pedig a pool gyökerében levő zfs-példányra vonatkozó opciókat (propertyket) állítjuk be.
- A pool gyökerében beállított tulajdonságok (néhány kivételtől eltekintve) öröklődnek a gyermek-fájlrendszerekre; az alapértelmezéseket így csak egyszer, a pool gyökerén kell beállítanunk, de ahol akarjuk, felülbírálhatjuk őket.
- -O compression=gzip-9 -- a tömörítés a mai CPU-k korában majdnem ingyen van. Ha valamelyik konkrét fájlrendszernél sokat számít a sebesség, akkor ott érdemes lehet simán on-ra állítani a tömörítést (az sokkal gyorsabb, mint a gzip-9, de még mindig tömörít), vagy akár ki is kapcsolni.
- -O devices=off -- alapból nem akarjuk értelmezni a device node-okat a poolban levő fájlrendszerekben; ahol mégis, ott majd kifejezetten bekapcsoljuk.
- -O setuid=off -- alapból nem szeretnénk, ha a setuid/setgid bitek hatásosak lennének (ahol kell, majd bekapcsoljuk).
- -O exec=off -- alapból nem kell, hogy a fájlrendszerekben levő fájlok végrehajthatóak legyenek.
- -O dedup=on -- ez nem véletlenül nincs megadva. A deduplikáció bekapcsolása általában nem javasolt, mert nagyon memóriaigényes és súlyos lassuláshoz vezethet (ha a DDT nem fér el az ARC-ben, vagy pláne ha az L2ARC-ben sem). Mi lenne, ha mégis bekapcsolnánk?
- Adjunk felső becslést a DDT memóriaigényére! A DDT mérete szempontjából a kis blokkméret a legrosszabb (az egyedi blokkok számával arányos a mérete). Az ashift=12 miatt a legkisebb lehetséges blokkméret 4k. Ha feltesszük, hogy 256 gibibyte-ot fogunk tárolni, 67108864 darab 4k-s blokkunk lehet. Ha ezt felszorozzuk 400-zal, azt kapjuk, hogy a DDT mérete legfeljebb 25GiB lehet (tehát a worst-case becslésünk az, hogy a DDT mérete az összes tárolandó adat méretének 10%-a). A valóságban ennél jóval kisebb táblára számíthatunk, mivel nagy fájlok is lesznek az SSD-n (amelyeknél így 4k-nál jóval nagyobb, általában 128kiB lesz a blokkméret) és várhatóan nem lesz mind egyedi. Ha minden blokk 128k-s lenne, az 1GiB-ot sem érné el a DDT mérete (vagyis jó esetben kb. a tárolt adatok méretének néhány ezreléke a DDT).
- Ha nem fér el a DDT az ARC-ben (RAM-ban), akkor még mindig elférhet az L2ARC-ben. Ha ott sem fér el, a diszkről kell be-beolvasgatni; ez általában nagyon lassú lenne a sok seek miatt.
- Az SSD cache-nek (L2ARC-nak) köszönhetően a deduplikáció okozta fragmentációtól itt most nem várunk jelentős lassulást.
- Az SSD élettartama tekintetében két hatás áll egymással szemben: egyfelől a DDT karbantartásával összefüggő írási műveletek csökkentik az élettartamot, másfelől viszont a sikeres deduplikációnak köszönhetően kevesebb adatot kell kiírni. Mivel minden új blokk kiírásakor módosul a DDT, extrém esetektől eltekintve nem valószínű, hogy a deduplikációval megtakarított írásokat is figyelembe véve összességében kevesebbet írnánk, mint kikapcsolt deduplikációval.
- Ha csak SSD-ink vannak, akkor a deduplikációval valamennyi helyet megtakaríthatunk, és így több szabad hellyel gazdálkodhat az SSD wear leveling logikája -- arról nem is beszélve, hogy lassabban futunk ki a helyből. (Kivéve, ha döntően egyediek a blokkok, mert akkor a DDT feleslegesen foglalja a helyet.)
- Persze ha HDD-ink vannak, nem elég "valamennyi" helyet megtakarítani ahhoz, hogy megérje deduplikálni. "Sok" megtakarítás kellene hozzá.
- Nehéz előre megmondani, mennyire lesznek deduplikálhatóak az adatok.
- -O xattr=sa -- a bővített attribútumokat (amikben pl. capability-k vagy POSIX ACL-ek lehetnek) a 24-es ZFS poolverzióban bevezetett "rendszerattribútumokként" szeretnénk tárolni. Ez jobb, mint az "on" állás, ami egy rejtett fájlba teszi az adott alkönyvtár összes fájljának összes bővített attribútumát.
- -O mountpoint=/ -- azt gondolhatnánk, hogy így majd zfs lesz a root filerendszerünk, de nem; l. az alábbi opciót is:
- -O canmount=off -- így maga a pool gyökere nem fog bemountolódni, csak öröklik tőle a beállításokat a gyermek-filerendszerek.
- -O acltype=posixacl -- támogassuk a POSIX ACL-eket.
- -O refquota=32M -- a pool gyökérfilerendszere nem fog adatokat tárolni, úgyhogy állítsunk be egy kicsi kvótát -- így hamar feltűnik, ha mégis írni kezdünk rá.
- -O normalization=formC -- Unicode normalizáció (a Unicode-ban ugyanazt a jelet többféleképpen is elő lehet állítani; enélkül a normalizáció nélkül lehetne egy könyvtárban két különböző fájlunk, amelyeknek a neve mégis azonos, csak más-más Unicode-szekvenciaként áll elő). Ezt a propertyt csak a fájlrendszer létrehozásakor lehet beállítani, később nem módosítható.
- -O com.sun:auto-snapshot=false -- ez nem "gyári", "natív" property (tulajdonság), hanem a zfs-auto-snapshot script működését befolyásolja (tetszőleges saját címkéket helyezhetünk el a zpool-beli objektumokon namespace:label=value jelleggel; ezek szintén öröklődnek a gyermek-objektumokra). Itt most azt kérjük, hogy alapértelmezés szerint ne snapshotolja a fájlrendszereinket a zfs-auto-snapshot; amelyikről szeretnénk rendszeresen snapshotot csinálni, azon majd true-ra állítjuk ezt a property-t.
- Magyarázat: ashift=12 -- csak a pool létrehozásakor állítható property; azt adja meg (log2 alapon), mekkora "szektormérettel" dolgozzon a zfs.
- Hozzuk létre a /tmp fájlrendszert:
zfs create -o refquota=4G -o atime=on -o exec=on -o overlay=on -o sync=disabled tank/tmp
- Itt kell az atime, mert a tmpreaper(8) az alapján fogja eldönteni, mit törölhet.
- És általában kell az exec is; sokszor akarhatnak a felhasználók a /tmp-ből futtatni valamit.
- sync=disabled -- mivel a tmp-ben nincsenek értékes adatok, ne húzzuk feleslegesen az időt a szinkron írásokkal (hanem kezeljük őket hagyományos, aszinkron írásként).
- A /tmp-vel még annyi bonyodalom van, hogy a zfs alapból nem hajlandó olyan könyvtárra mountolni fs-t, amely nem üres; márpedig mire odáig jutunk a bootfolyamatban, hogy a /tmp-t bemountolnánk, addigra elképzelhető, hogy lesz már benne valami szemét. Erre a következő megoldások vannak:
- A tank/tmp mountpoint propertyjét legacy-re állítjuk és az fstabból mountoljuk, mint a többi fájlrendszert. Hátrány: csúnya/inkonzisztens. Ha csak lehet, ne használjunk legacy mountpoint propertyt.
- Egy initscriptbe, vagy pl. az rc.local-ba (vagy, ha runitot használunk, a /etc/runit/1-be) belerakjuk, hogy
mkdir -p /tmp.orig; mountpoint -q /tmp && mount --move /tmp /tmp.orig && mountpoint -q /tmp.orig && umount -l /tmp.orig; find /tmp -delete && zfs mount tank/tmp
. Hátrány: gány. - Az iménti két módszer kombinációja: a tank/tmp mountpoint propertyjét legacy-re állítjuk, de scriptből mountoljuk be boot közben, nem az fstabból. Ennek az a hátránya, hogy ha a tmp alatt további zfs-eink is vannak (pl. minden vagy néhány felhasználónak saját), akkor azokat is mind "kézzel" kell bemountolni, míg az előző megoldásban zfs mount tank/tmp helyett írhatunk zfs mount -a-t, és minden bemountolódik.
- Ugyanakkor sajnos előfordulhat, hogy ha korábban már kiadtunk egy zfs mount -a-t úgy, hogy a /tmp nem volt üres, akkor a /tmp nem mountolódott be, a gyermekei viszont igen. Erre a problémára szerintem nincs szép megoldás. Ronda megoldásnak jó, ha egy ciklussal umountoljuk az összes, a /tmp alá mountolódó zfs-t, mielőtt a /tmp-be mountolandó zfs-t mountolnánk, majd zfs mount -a-val megint bemountoljuk őket.
- Az itt látható -o overlay=on property, ami aránylag friss (a korábban említett megoldások leírásakor még nem létezett). Ha egy filerendszeren ez a property be van kapcsolva, akkor akkor is be lehet mountolni, ha a könyvtár, ahová mountolnánk, nem üres.
- Hozzunk létre még néhány fájlrendszert:
TODO
-
zfs create -o refquota=32M -o com.sun:auto-snapshot=true tank/srv
-
zfs create -o refquota=5G tank/srv/postgres
-
zfs create -o refquota=5G tank/srv/www
-
zfs create -o refquota=256M tank/srv/ldap
-
- debootstrappel telepítsük a /t könyvtárba a Debianunkat, konfiguráljuk stb.
4.2 Hatékony backup zfs-re
Néhány óra alatt össze lehet dobni egy backup-megoldást, ami a következőket csinálja:
- A backupokat tároló gépen (a továbbiakban: backup-szerver) van egy zpool, bekapcsolt tömörítéssel (és, ha az erőforrások engedik, deduplikációval).
- Minden mentendő fájlrendszerhez tartozik egy külön zfs-példány (filesystem típusú dataset).
- Értelmes elrendezés pl. az, ha van egy /backup pool, ami alatt minden kliensgépnek van egy könyvtára (ami akár lehet külön fs is), és ezeken belül az egyes kliensek egyes fájlrendszereinek is van egy-egy könyvtára (ami viszont határozottan külön fs).
- A backup-szerveren fut egy rsync daemon, ami -- alkalmas hitelesítéssel stb. -- a klienseknek írásra kiajánlja ezeket a könyvtárakat.
- Az rsync daemon fake-super beállítással fut; így mezei userként is minden attribútumot, tulajdonságot, ACL-t stb. képes tárolni, csak xattr-támogatás kell hozzá.
- A kliensek, amikor menteni akarnak, rsync --inplace -aH --partial jellegű parancssorral töltik fel az adataikat.
- Az rsync post-xfer exec beállítása segítségével minden transzfer végeztével lefuttatunk egy scriptet, ami snapshotolja az éppen írt fájlrendszert.
- A snapshoton beállítunk néhány saját propertyt, pl. hogy -- az rsync visszatérési értéke alapján -- sikeres volt-e a mentés, ill. hogy mikor jár le a snapshot élettartama.
- cronból időnként töröljük a lejárt snapshotokat.
- A backupok helyreállításához ugyanezeket a könyvtárakat csak olvasásra is kiajánljuk, post-xfer exec script nélkül.
A megoldás sokban hasonlít pl. a dirvish-re; két fő előnye, hogy
- a régi backupok letörlése sokkal gyorsabb (egy snapshot megszüntetése nagyságrendekkel kevesebb idő, mint egy sokezer fájlt tartalmazó könyvtárfa törlése); és hogy
- ha egy fájlnak csak egy része változott (vagy pláne: csak a jogai), a dirvish az egészről új példányt tárolna, mi pedig a snapshotolásnak (és esetleg a deduplikációnak) köszönhetően nem tároljuk duplán a változatlan részeket.
A saját megoldásomat közreadtam a githubon.
5 Források
- ZFS Dedup Internals
- ZFS -- The Last Word in Filesystems
- Hybrid Storage Pools - Using Disk and Flash with ZFS (videó)
6 Potenciális zh-kérdések
- Mik a zfs fő újdonságai, előnyei a "hagyományos" fájlrendszerekhez képest?
- Mi a különbség az LVM és a zfs snapshot-megoldása között? Melyik hogyan működik? Melyik gyorsabb és miért?
- Mi szól a zfs esetében a deduplikáció használata ellen és mi szól mellette?
- Mi a különbség a zfs kontextusában a volume és a filesystem között?
- Mi a különbség a RAID6 és a RAIDZ2 között?