ZFS
a (l2arc: hátrányok) |
(Visszavontam, bugos a WikiEditor.php KornAndras (vita | szerkesztései) szerkesztését (oldid: 5742)) |
||
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 2013. január 1., 00:07-kori változata
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.
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 |
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) 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. 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.
- 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 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).
- 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 -- 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 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.
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.
- 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 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 (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.
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
3 ZFS Linux alatt
- Linuxra párhuzamosan készült egy 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 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).
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 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. 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 http://ppa.launchpad.net/zfs-native repositoryból a következő csomagokat:
apt-get install zfsutils zfs-dkms spl-dkms
- 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 /boot-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ó, 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.
- 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 /boot helye lesz), amin majd a root filerendszer, a /usr ill. a /var 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 sda4 ill. az sdb4).
- 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 -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
- 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 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.
- -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.
- -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 konvenció; lehetne bármi más is.
- mirror /dev/sd[ab]4 -- egy tükrözött (tehát RAID1-szerű) vdevet adunk meg, amit a felsorolt két partíció fog alkotni.
- -O atime=off -- 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".
- -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; 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 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 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.
- -O xattr=off -- 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.
- -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 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). 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 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.
- 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: 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 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
- 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.
- Hozzunk létre még néhány fájlrendszert:
-
zfs create -o exec=on -o setuid=on -o mountpoint=/usr -o canmount=off tank/usr
-
zfs create -o refquota=1G -o com.sun:auto-snapshot=true tank/usr/share
- Magát a tank/usr fájlrendszert nem fogjuk mountolni (canmount=off), de a gyermekei öröklik az exec=on és a setuid=on beállítást, meg persze a mountpointot. A canmount attribútum nem öröklődik (és egyébként a refquota sem).
- A /usr így vagy a root fájlrendszer része lesz, vagy egy külön LVM LV, mondjuk xfs-sel.
- A /usr/share nem létfontosságú a bootoláshoz, és egy tipikus Debianon a gzip-9 tömörítéssel simán elérhetünk 50+% megtakarítást, ami az SSD-n nem mindegy.
- Magát a tank/usr fájlrendszert nem fogjuk mountolni (canmount=off), de a gyermekei öröklik az exec=on és a setuid=on beállítást, meg persze a mountpointot. A canmount attribútum nem öröklődik (és egyébként a refquota sem).
-
zfs create -o refquota=250M -o com.sun:auto-snapshot=true tank/usr/lib32
-
zfs create -o refquota=250M -o com.sun:auto-snapshot=true tank/usr/local
-
zfs create -o refquota=2G tank/usr/local/src
-
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.
Ha majd lesz egy kis időm, közreadom a saját megoldásomat a githubon.
5 Források
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?