ZFS

A Unix/Linux szerverek üzemeltetése wikiből
(Változatok közti eltérés)
a (Hatékony backup zfs-re: typo)
a (l2arc: hátrányok)
1. sor: 1. sor:
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, [http://www.illumos.org/ IllumOS]-származékokhoz/-szerűségekhez ([http://hub.opensolaris.org/bin/view/Main/ OpenSolaris], [http://nexentastor.org/ NexentaStor], [https://www.illumos.org/projects/illumian/wiki illumian], [http://osdyson.org/projects/dyson/wiki Dyson] -- ez egy IllumOS-kernelre épülő Debian; kíváncsi vagyok, mi lesz belőle --, [http://smartos.org/ SmartOS], [http://openindiana.org/ OpenIndiana] stb.), [http://wiki.freebsd.org/ZFS FreeBSD]-hez és [http://zfsonlinux.org/ Linuxhoz] létezik elérhető implementáció. Az Apple egy darabig [http://www.zdnet.com/blog/storage/apple-announces-zfs-on-snow-leopard/335 dolgozott] egy Mac OS X-porton, de úgy tűnik, [http://www.zdnet.com/mac-zfs-dead-again-thanks-apple-7000001485/ felhagyott vele].
 
 
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").
 
 
== Alapfogalmak, építőelemek ==
 
 
* '''Pool''': kb. az LVM VG megfelelője. Fizikai adattárolók (<tt>vdev</tt>ek, 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) nem lehetséges.
 
*** 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 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.
 
**# 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).
 
**#* 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.
 
* '''dataset''': olyan objektum, amelynek a pool helyet biztosít. Egy poolban max. 2<sup><small>48</small></sup> 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.
 
*** 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 <tt>zfs promote</tt> 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.
 
* 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 elvileg nem), vagy
 
**** exportálhatjuk iSCSI-n.
 
** Storage Pool Allocator (SPA).
 
*** Ez kezeli az egész alatti fizikai diszkeket.
 
*** Ez tömörít, blokkszinten; emellett 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).
 
 
== 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 <tt>du(1)</tt> megnézze, mi foglal sok helyet; ha a releváns könyvtárak külön datasetekben vannak, a <tt>df(1)</tt> azonnal megmondja.
 
* Hierarchikus. A <tt>tank</tt> poolban lehet egy <tt>tank/srv</tt> nevű zfs-példány, abban egy <tt>tank/srv/www</tt>, abban egy <tt>tank/srv/www/www.domain.com</tt> 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á: <tt>zfs inherit propertyneve poolneve/datasetneve</tt>).
 
** 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 <tt>mountpoint/.zfs/snapshot/snapshotneve</tt> könyvtárakon keresztül a rendszergazda közreműködése nélkül is elérhetők.
 
** "Policy follows the data": egyetlen <tt>zfs mount -a</tt> 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 <tt>legacy</tt>-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 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ű [http://blogs.sun.com/bonwick/entry/zfs_dedup 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 [[RAID#RAID5|RAID5]]/[[RAID#RAID6|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").
 
* 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 -- legalábbis Linuxon -- elég lassú tud lenni, és sajnos nem lehet részletekben elvégezni.
 
* 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 nyilván 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.
 
* (Mostanáig) nagyon komolyan vette/veszi 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 [https://www.illumos.org/boards/1/topics/256 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.
 
 
=== Deduplikáció ===
 
 
<div style="float: right; width: 49%; padding: 4">{{textbox|bodycolor=#f9f9f9|width=100%|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, 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. Ez azt jelenti, hogy 2<sup><small>256</small></sup> 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:2<sup><small>256</small></sup>-hoz. Ez rettenetesen kicsi valószínűség.
 
 
Igen ám, de számolnunk kell a [http://en.wikipedia.org/wiki/Birthday_problem 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 [http://en.wikipedia.org/wiki/Birthday_problem#Probability_table táblázatot], amelyből kiolvashatjuk, hogy ha azt szeretnénk, hogy a hash-ütközés valószínűsége legfeljebb 10<sup><small>-18</small></sup> 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*10<sup><small>29</small></sup> 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<sup><small>-18</small></sup> 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<sup><small>-18</small></sup>-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áz''billió''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).
 
}}
 
</div>
 
<div style="width: 49%; padding: 4">
 
* 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.
 
* Nem kötelező az összes adatot deduplikálni; szelektíven is bekapcsolhatjuk (egyes datasetekre külön-külön).
 
** 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ó, de legfeljebb 128k; ha kis fájljaink vannak, egy-egy fájl egy blokk (és persze majdnem minden nagy fájl vége egy 128k-nál 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 <tt>zfs_arc_meta_limit</tt>); 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 (a write cache) ü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.
 
</div>
 
<div style="width: 100%; clear: both">
 
 
=== Tranzakciókezelés ===
 
 
(A [http://hub.opensolaris.org/bin/download/Community+Group+zfs/docs/zfslast.pdf 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.
 
 
=== 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:
 
<pre>
 
zfs snapshot tank/home@hetfo
 
zfs send tank/home@hetfo >/backup/home_teljes_hetfo.zfssend
 
</pre>
 
* Inkrementális backup készítése kedden:
 
<pre>
 
zfs snapshot tank/home@kedd
 
zfs send tank/home@hetfo tank/home@kedd >/backup/home_inkr_kedd-hetfo.zfssend
 
</pre>
 
* Differenciális backup készítése szerdán:
 
<pre>
 
zfs snapshot tank/home@szerda
 
zfs send tank/home@hetfo tank/home@szerda >/backup/home_diff_szerda-hetfo
 
</pre>
 
* <small>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 [http://www.bacula.org/ Bacula] szóhasználatához igyekeztem igazodni.</small>
 
* Percenkénti távoli replikáció: <pre>zfs send -i tank/home@10:13 tank/home@10:14 | ssh masikgep zfs receive -d /tank</pre>
 
 
== ZFS Linux alatt ==
 
 
* Linuxra párhuzamosan készült egy [http://zfs-fuse.net/ userspace] és egy [http://zfsonlinux.org/ 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 jogilag 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.
 
** 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 nagyon is alkalmas.
 
*** 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).
 
 
== Esettanulmányok ==
 
 
=== Linuxos gép telepítése zfs-sel ===
 
 
Tegyük fel, hogy van egy gépünk, benne két 256GB-os SSD-vel, 16+GB RAM-mal, és hogy erre 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. [http://grml.org/ 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 [http://fai-project.org/ FAI]-t, annak ismerős lesz.
 
# Rakjuk fel a futó grml-re a [http://ppa.launchpad.net/zfs-native http://ppa.launchpad.net/zfs-native] repositoryból a következő csomagokat: <pre>apt-get install zfsutils zfs-dkms spl-dkms</pre>
 
#* Előfordulhat, hogy ehhez az Ubuntuban levő libc6-ot is fel kell raknunk (ha újabb, mint ami a grml-ünkön van); másik lehetőség a zfs-es csomagok újrafordítása a grml-es környezetben.
 
# Particionáljuk a diszkeket (SSD-ket); lehetőleg használjunk GPT-t.
 
#* A biztonság kedvéért csináljunk külön partíciókat a <tt>/boot</tt>-ot tartalmazandó RAID1-tömb számára. Elvileg ugyan tud LVM-ről bootolni a grub, de az ördög nem alszik.
 
#* Ne felejtsünk el egy párszáz szektorból álló, <tt>bios_grub</tt> 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 <tt>raid</tt> flaggel.
 
#* Mivel a zfsonlinux "out of tree" modul, előfordul, hogy pl. kernelupgrade után nem fordul le helyesen; emiatt hasznos, ha be tudunk bootolni nélküle is.
 
#* Ezért csináljunk még egy RAID1-tömböt (az első a <tt>/boot</tt> helye lesz), amin majd a root filerendszer, a <tt>/usr</tt> ill. a <tt>/var</tt> nagy része kap helyet, LVM-ben.
 
#* A maradék helyen hagyjunk egy-egy partíciót, amit majd a zfs fog használni (a továbbiakban feltételezem, hogy ez a két partíció az <tt>sda4</tt> ill. az <tt>sdb4</tt>).
 
# Hozzuk létre azt a könyvtárat, amibe majd telepítünk: <pre>mkdir /t</pre>
 
# Hozzuk létre a zpoolt: <pre>zpool create -o ashift=12 -o autoexpand=on -R /t tank mirror /dev/sd[ab]4 -O atime=off -O compression=gzip-9 -O devices=off -O setuid=off -O exec=off -O dedup=on -O com.sun:auto-snapshot=false -O xattr=off -O mountpoint=/ -O canmount=off</pre>
 
#* Magyarázat: <tt>ashift=12</tt> -- 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 lehet, hogy később kerül 4k-s szektorokat használó diszk a poolba.
 
#** 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.
 
#* <tt>-o autoexpand=on</tt> -- 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.
 
#* <tt>-R /t</tt> -- állítsuk az <tt>altroot</tt> property-t <tt>/t</tt>-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.
 
#* <tt>tank</tt> -- így fogják hívni a létrehozandó poolt. A "tank" név konvenció; lehetne bármi más is.
 
#* <tt>mirror /dev/sd[ab]4</tt> -- egy tükrözött (tehát RAID1-szerű) vdevet adunk meg, amit a felsorolt két partíció fog alkotni.
 
#* <tt>-O atime=off</tt> -- alapból kikapcsoljuk az atime update-eket az uj fs-eken (ahol kell, majd külön bekapcsoljuk). Ezzel részben nyerünk egy kis teljesítőképességet, részben pedig elkerüljük az SSD túlzott "koptatását".
 
#** <tt>-o</tt>-val a poolra, <tt>-O</tt>-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.
 
#* <tt>-O compression=gzip-9</tt> -- a tömörítés a mai CPU-k korában majdnem ingyen van; az SSD-n viszont egyrészt aránylag szűk a hely, másrészt minél kevesebbet írunk rá, annál hosszabb lesz az élettartama, tehát duplán megéri tömöríteni. Ha valamelyik konkrét fájlrendszernél rettentően számít a sebesség, akkor ott érdemes lehet simán <tt>on</tt>-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.
 
#* <tt>-O devices=off</tt> -- alapból nem akarjuk értelmezni a device node-okat a poolban levő fájlrendszerekben; ahol mégis, ott majd kifejezetten bekapcsoljuk.
 
#* <tt>-O setuid=off</tt> -- alapból nem szeretnénk, ha a setuid/setgid bitek hatásosak lennének (ahol kell, majd bekapcsoljuk).
 
#* <tt>-O exec=off</tt> -- alapból nem kell, hogy a fájlrendszerekben levő fájlok végrehajthatóak legyenek.
 
#* <tt>-O dedup=on</tt> -- ez talán a legmegkérdőjelezhetőbb döntés. 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ért kapcsoltam be mégis:
 
#** Adjunk felső becslést a DDT memóriaigényére! Az SSD aránylag kicsi (256GB), és nem is az egészet használjuk zfs-sel. 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 a 256GB-ot 256 gibibyte-nak vesszük, 67108864 darab 4k-s blokk fér el rajta. Ha ezt felszorozzuk 400-zal, azt kapjuk, hogy a DDT mérete legfeljebb 25GiB lehet. 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.
 
#** Ha nem fér el a DDT az ARC-ben (RAM-ban), akkor még mindig elférhet az L2ARC-ben. Itt most nem lesz L2ARC, mert nincs a két SSD-nél kisebb, de jóval gyorsabb tárolónk. Így, ha a DDT egy része kiszorul a RAMból, a diszkről kell be-beolvasgatni; ez általában nagyon lassú lenne a sok seek miatt, de itt most a diszk egy SSD, ami szinte ingyen seekel, tehát arra számítok, hogy a DDT olvasgatása nem lassítja le nagyon az írást.
 
#** Szintén az SSD-nek köszönhetően a deduplikáció okozta fragmentációtól sem várok 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.
 
#** Másfelől viszont ''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.)
 
#*** Fontos: ha nem SSD-nk lenne, nem lenne 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.
 
#* <tt>-O xattr=off</tt> -- alapból nem szeretnénk bővített attribútumokat használni. Ezekre többnyire nincs szükségünk (ha pedig igen, akkor tudunk róla). 2012. vége felé egyébként a zfsonlinuxban elég lassú az xattr-megvalósítás; dolgoznak egy jobb megoldáson, ami a ZFS 24-es poolverziójában bevezetett "rendszerattribútumokat" használná erre a célra, de egyelőre nincs kész.
 
#* <tt>-O mountpoint=/</tt> -- azt gondolhatnánk, hogy így majd zfs lesz a root filerendszerünk, de nem; l. az alábbi opciót is:
 
#* <tt>-O canmount=off</tt> -- így maga a pool gyökere nem fog bemountolódni, csak öröklik tőle a beállításokat a gyermek-filerendszerek.
 
#* <tt>-O com.sun:auto-snapshot=false</tt> -- ez nem "gyári", "natív" property (tulajdonság), hanem a [https://github.com/dajhorn/zfs-auto-snapshot 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 <tt>namespace:label=value</tt> jelleggel; ezek szintén öröklődnek a gyermek-objektumokra). 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.
 
# Hozzuk létre a /tmp fájlrendszert: <pre>zfs create -o refquota=4G -o atime=on -o exec=on tank/tmp</pre>
 
#* Itt kell az <tt>atime</tt>, mert a <tt>tmpreaper(8)</tt> az alapján fogja eldönteni, mit törölhet.
 
#* És általában kell az <tt>exec</tt> is; sokszor akarhatnak a felhasználók a /tmp-ből futtatni valamit.
 
#* 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 <tt>tank/tmp</tt> <tt>mountpoint</tt> propertyjét <tt>legacy</tt>-re állítjuk és az <tt>fstab</tt>ból mountoljuk, mint a többi fájlrendszert. Hátrány: ha valamilyen hiba miatt nem áll rendelkezésre a zfs kernelmodul, a mount nem fog sikerülni és emiatt rossz esetben megakad a bootfolyamat. Előny: egyszerű.
 
#*# Egy initscriptbe, vagy pl. az <tt>rc.local</tt>-ba (vagy, ha [[A runit működése|runit]]ot használunk, a <tt>/etc/runit/1</tt>-be) belerakjuk, hogy <pre>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</pre>
 
#*# Az iménti két módszer kombinációja: a <tt>tank/tmp</tt> <tt>mountpoint</tt> propertyjét <tt>legacy</tt>-re állítjuk, de scriptből mountoljuk be boot közben, nem az <tt>fstab</tt>bó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 <tt>zfs mount tank/tmp</tt> helyett írhatunk <tt>zfs mount -a</tt>-t, és minden bemountolódik.
 
#*#* Ugyanakkor sajnos előfordulhat, hogy ha korábban már kiadtunk egy <tt>zfs mount -a</tt>-t úgy, hogy a <tt>/tmp</tt> nem volt üres, akkor a <tt>/tmp</tt> 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 <tt>zfs mount -a</tt>-val megint bemountoljuk őket.
 
# Hozzunk létre még néhány fájlrendszert:
 
#* <pre>zfs create -o exec=on -o setuid=on -o mountpoint=/usr -o canmount=off tank/usr</pre>
 
#* <pre>zfs create -o refquota=1G -o com.sun:auto-snapshot=true tank/usr/share</pre>
 
#** Magát a <tt>tank/usr</tt> fájlrendszert nem fogjuk mountolni (<tt>canmount=off</tt>), de a gyermekei öröklik az <tt>exec=on</tt> és a <tt>setuid=on</tt> beállítást, meg persze a mountpointot. A <tt>canmount</tt> attribútum nem öröklődik (és egyébként a <tt>refquota</tt> sem).
 
#*** A <tt>/usr</tt> így vagy a root fájlrendszer része lesz, vagy egy külön LVM LV, mondjuk xfs-sel.
 
#*** A <tt>/usr/share</tt> nem létfontosságú a bootoláshoz, és egy tipikus Debianon a <tt>gzip-9</tt> tömörítéssel simán elérhetünk 50+% megtakarítást, ami az SSD-n nem mindegy.
 
#* <pre>zfs create -o refquota=250M -o com.sun:auto-snapshot=true tank/usr/lib32</pre>
 
#* <pre>zfs create -o refquota=250M -o com.sun:auto-snapshot=true tank/usr/local</pre>
 
#* <pre>zfs create -o refquota=2G tank/usr/local/src</pre>
 
#* <pre>zfs create -o refquota=32M -o com.sun:auto-snapshot=true tank/srv</pre>
 
#* <pre>zfs create -o refquota=5G tank/srv/postgres</pre>
 
#* <pre>zfs create -o refquota=5G tank/srv/www</pre>
 
#* <pre>zfs create -o refquota=256M tank/srv/ldap</pre>
 
# <tt>debootstrap</tt>pel telepítsük a <tt>/t</tt> könyvtárba a Debianunkat, konfiguráljuk stb.
 
 
=== 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 <tt>/backup</tt> 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 <tt>rsync</tt> daemon, ami -- alkalmas hitelesítéssel stb. -- a klienseknek írásra kiajánlja ezeket a könyvtárakat.
 
** Az rsync daemon <tt>fake-super</tt> 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, <tt>rsync --inplace -aH --partial</tt> jellegű parancssorral töltik fel az adataikat.
 
** Az rsync <tt>post-xfer exec</tt> 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 [http://www.dirvish.org/ 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.
 
 
Ha majd lesz egy kis időm, közreadom a saját megoldásomat a githubon.
 
 
== Források ==
 
 
* [http://www.c0t0d0s0.org/archives/7271-ZFS-Dedup-Internals.html ZFS Dedup Internals]
 
* [http://hub.opensolaris.org/bin/download/Community+Group+zfs/docs/zfslast.pdf ZFS -- The Last Word in Filesystems]
 
 
== 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?
 

A lap 2012. december 31., 23:59-kori változata

Személyes eszközök