Žurnálování a konzistence
Žurnálování a co přežije výpadek proudu
Přesuneš soubor do jiného adresáře. Na disku to znamená dvě změny: přidat záznam sem a odebrat ho tam. Disk umí zapsat jeden sektor atomicky - buď celý, nebo vůbec. Dva sektory naráz nezaručí nikdo.
Vytáhni přesně v tu chvíli šňůru ze zásuvky. Soubor je teď buď na dvou místech naráz, nebo na žádném. Po zapnutí musí souborový systém poznat, co se stalo, a spravit to - jen z toho, co na disku zbylo.
Tahle stránka je o tom, jak se to dělá a co ti to zaručí. Předpokládá souborový systém, tedy i-uzel, adresář a alokaci bloků. Neřeší zálohy ani RAID, protože ani jedno není odpověď na tuhle otázku.
Než přišel žurnál, běžel fsck přes celý disk
Starý postup byl jednoduchý a brutální. Po nečistém vypnutí prošel fsck celý svazek a hledal nesrovnalosti: blok přidělený dvěma souborům, i-uzel bez odkazu, počet odkazů, který nesedí.
Trvalo to úměrně počtu i-uzlů, ne počtu změn: u terabajtového disku desítky minut, u plného a pomalého klidně hodiny. A výsledek nebyl jistý - co fsck nezařadil, skončilo v lost+found pod číslem i-uzlu.
Server, který po výpadku nabíhá hodinu a pak nabídne dvě stě souborů bez jmen, je celý důvod, proč žurnál vznikl.
Zápis napřed, provedení potom
Žurnál je vyhrazená oblast na témž svazku, do které se nejdřív zapíše, co se chystá, a teprve potom se to provede. Anglicky write-ahead logging, stejný nápad jako v databázích.
Jak žurnál funguje
sequenceDiagram
participant J as Jádro
participant Z as Žurnál
participant D as Místa na disku
J->>Z: 1. zápis záměru (oba změněné adresáře)
J->>Z: 2. potvrzovací záznam
Note over Z: tady se změna stává nezvratnou
J->>D: 3. provedení změn na jejich místa
J->>Z: 4. uvolnění záznamu ze žurnálu
Krok 2 je celý trik. Potvrzovací záznam je malý a zapíše se jedním zápisem, takže na disku buď je, nebo není. Tím se z velké skupiny změn stane operace se dvěma možnými výsledky.
Po výpadku se žurnál při připojení jen přečte. Potvrzené transakce se provedou znovu, nepotvrzené se zahodí. Trvá to vteřiny, protože se čtou desítky až stovky megabajtů, a ne celý disk.
Tři režimy ext4 a rozdíl mezi nimi
Ext4 umí tři režimy a liší se v jediné věci: co všechno patří do transakce.
| Režim | Co jde do žurnálu | Rychlost | Co se ti může stát |
|---|---|---|---|
data=journal |
metadata i obsah | nejpomalejší, data se píší dvakrát | nejmíň, obsah sedí s posledním potvrzením |
data=ordered |
metadata, data se zapíšou dřív | výchozí kompromis | soubor bude zkrácený nebo prázdný |
data=writeback |
metadata, na pořadí se nehledí | nejrychlejší, rozdíl bývá malý | v souboru najdeš zbytky cizího smazaného souboru |
Rozdíl mezi ordered a writeback je v pořadí, ne v obsahu žurnálu. V ordered jsou data na místě dřív, než se potvrdí metadata, takže i-uzel nikdy neukáže na blok, do kterého se nezapsalo.
Doporučení: nech ordered. data=journal má smysl jen tam, kde je zápis vzácný a cena chyby vysoká. Režim se nedá změnit za běhu - mount -o remount,data=writeback skončí hláškou Cannot change data mode on remount.
Copy-on-write jako druhá cesta
Btrfs a ZFS řeší tentýž problém opačně: nikdy nepřepisují na místě. Nový obsah jde do volných bloků, přepíšou se ukazatele nad ním a nakonec se jedním atomickým zápisem přehodí kořen stromu. Do té chvíle je na disku celá stará verze.
Snímky (snapshots) z toho plynou zadarmo: jsou to podržené staré kořeny, takže vzniknou okamžitě a nezaberou nic. Kontrolní součty dávají smysl, protože se blok stejně zapisuje celý znovu.
Cena je fragmentace: databáze a virtuální disky přepisují bloky uvnitř velkého souboru a na CoW z nich vzniknou desítky tisíc kousků. Proto se u nich CoW vypíná:
mkdir /var/lib/mysql-nocow
chattr +C /var/lib/mysql-nocow # atribut se dědí, soubory uvnitř vzniknou bez CoW
Doporučení: btrfs na kořen a domovy, kde snímky před aktualizací opravdu použiješ. Na svazek s databází nebo obrazy virtuálních strojů sáhni po ext4 nebo XFS.
Žurnál chrání strukturu, ne tvoje data
Tohle je nejdůležitější věc na celé stránce: žurnál chrání konzistenci souborového systému, ne obsah tvých souborů.
Zaručuje ti, že každý blok patří nejvýš jednomu souboru, každý i-uzel má správný počet odkazů a bitmapa volných bloků odpovídá skutečnosti. To je všechno.
Soubor o nula bajtech je podle těchhle pravidel v pořádku. Systém nastartuje, fsck nic nenajde - a tvoje konfigurace je prázdná. Konzistentní neznamená správný.
Kde končí odpovědnost systému
fsync a page cache
Když program zavolá write, data skončí v page cache a volání se vrátí. Na disku nejsou. Jádro je tam vyplaví během vteřin až desítek vteřin.
Jediné, co to změní, je systémové volání fsync. Vrátí řízení, teprve až je obsah na trvalém úložišti, a stojí to - na NVMe stovky mikrosekund, na plotnovém disku jednotky až desítky milisekund.
Bezpečná výměna souboru má proto čtyři kroky a žádný se nedá vynechat:
fd = open("app.conf.tmp", O_WRONLY|O_CREAT|O_TRUNC, 0600);
write(fd, data, len); // 1. píšeš stranou, původní soubor zůstává celý
fsync(fd); // 2. obsah je na disku dřív, než na něj ukáže jméno
close(fd);
rename("app.conf.tmp", "app.conf"); // 3. výměna jména, atomická z definice
dirfd = open(".", O_RDONLY|O_DIRECTORY);
fsync(dirfd); // 4. i přejmenování jsou metadata a musí se potvrdit
Krok 2 bez kroku 3 ti nechá dva soubory. Krok 3 bez kroku 2 ti nechá jméno ukazující na prázdno. Krok 4 vynechá skoro každý, protože bez něj to zdánlivě funguje.
Spor o ext4 a zpožděnou alokaci, rok 2009
Ext4 zavedl zpožděnou alokaci: bloky se nepřidělí při zápisu, ale až při vyplavení, což dovolí lepší rozvržení na disku. Okno mezi zápisem a přidělením se tím prodloužilo na desítky vteřin.
Desktopové aplikace tehdy ukládaly nastavení tak, že zapsaly dočasný soubor a hned ho přejmenovaly - bez fsync. Na ext3 to díky pořadí zápisů většinou prošlo. Na ext4 po pádu ovladače grafiky, který tehdy nebyl vzácný, zůstávaly lidem konfigurační soubory o nulové délce.
Autoři aplikací tvrdili, že jim to rozbil souborový systém. Ted Ts'o, správce ext4, tvrdil, že ty aplikace byly rozbité vždycky a ext3 jim to jen zakrýval. Obojí byla pravda. Do jádra 2.6.30 přibylo automatické vyplavení při přejmenování přes existující soubor (volba auto_da_alloc).
Poučení: chování, na které se spoléháš, ale které ti nikdo neslíbil, není záruka - je to jen aktuální implementace.
Bariéry a hardware, který lže
Celá konstrukce stojí na předpokladu, že potvrzený zápis se opravdu stal. Disky mají vlastní vyrovnávací paměť a bez donucení potvrzují dřív, než data uloží.
Proto jádro při potvrzení transakce posílá příkaz k vyprázdnění cache. Ext4 to dělá ve výchozím stavu, ext3 dlouho ne, a odtud se dodneška vrací rada vypnout to kvůli výkonu. Volbu nobarrier nasazuj jen na řadič se zálohovanou pamětí. Jinak si za jednotky procent kupuješ přesně tu poruchu, proti které žurnál stojí.
Levná SSD bez ochrany proti výpadku napájení jsou horší případ. Nemají dost energie, aby po ztrátě proudu dopsala vnitřní cache, a testy spotřebních modelů opakovaně ukázaly ztrátu dat i tam, kde systém udělal všechno správně. Tohle je jediná situace, ve které ti žurnál nepomůže - pracuje s potvrzeními, která nejsou pravdivá.
Kde žurnál nepomůže vůbec
Proti tiché korupci dat je slepý. Když se bit na disku převrátí sám, struktura zůstane v pořádku a není co přehrávat. Poznají to jen kontrolní součty dat, které mají ZFS a btrfs. Ext4 kontroluje součty metadat, obsah nikoli - vrátí ti poškozená data bez varování.
Proti rm -rf je slepý taky. GitLab v roce 2017 takhle přišel o šest hodin produkčních dat a zachránit ho nemělo co: z pěti způsobů zálohy nefungoval ani jeden.
Na obojí je záloha na jiném stroji, ne volba souborového systému.
Diagnostika
dumpe2fs -h /dev/nvme0n1p2 # hlavička superbloku: hledej has_journal a velikost žurnálu
tune2fs -l /dev/nvme0n1p2 # totéž plus počty připojení a interval kontroly
grep ' / ' /proc/mounts # s jakými volbami je kořen připojený
dmesg | grep -i 'EXT4-fs' # režim při připojení i chyby
Režim poznáš z hlášky při připojení, která končí slovy with ordered data mode. Když ve výpisu voleb žádné data= není, platí výchozí ordered.
fsck nikdy nepouštěj na připojený svazek: čte struktury, které se pod ním mění, takže „opraví“ stav, který mezitím neplatí. Kořen odpojit nejde, a tak se kontrola vynucuje při startu parametrem jádra fsck.mode=force - ne souborem /forcefsck, který návody dodneška opisují. O volbách připojení je VFS a montování.
| Příznak | Kde je problém |
|---|---|
recovering journal v dmesg po startu |
normální přehrání po nečistém odpojení |
EXT4-fs error (device nvme0n1p2) |
poškozená metadata, svazek přejde do jen pro čtení |
| Konfigurační soubor má po pádu nula bajtů | aplikace přejmenovala bez fsync |
| V souboru je binární smetí z cizího souboru | svazek běží v data=writeback |
| Chybí i to, co bylo zapsané minuty předtím | disk potvrzuje z cache, nebo je vypnuté vyprazdňování |
has_journal chybí ve výpisu dumpe2fs |
ext2, nebo ext4 založený bez žurnálu |
Data jsou poškozená, ale fsck je čistý |
tichá korupce, ext4 ji u dat nepozná |
Co si odnést
Žurnál chrání strukturu, ne obsah. Čistý fsck neznamená, že tvůj soubor má data.
Nech data=ordered. journal je pomalý, writeback pouští do souborů cizí zbytky.
Bez fsync nemáš slíbeno nic. write skončí v paměti, ne na disku.
Bezpečná výměna má čtyři kroky. Zápis, fsync, rename, fsync adresáře.
Copy-on-write přidá snímky a součty, ale i fragmentaci. Jiná odpověď na tutéž otázku.
Lhoucí hardware porazí každý žurnál. Nepravdivé potvrzení se softwarem obejít nedá.
Proti smazání a bitrotu pomáhá záloha. Souborový systém na to není a nikdy nebyl.
Kam dál
- Souborový systém - i-uzly a adresáře, tedy struktura, kterou žurnál drží pohromadě
- Vstup a výstup - page cache a plánování zápisů, odkud se
fsyncbere - Start systému - co se s nečistě odpojeným svazkem děje při bootu
- Nástroje a diagnostika -
dmesg,/proca zbytek výbavy na hledání příčiny