← Všechny příběhy

Ring buffer ve flash: jak off-grid gateway neztratí data, když spí

Na včelnici není zásuvka, takže gateway musí být skoro celou noc vypnutý. Jak nevolatilní ring buffer ve flash paměti odkládá telemetrii úlů, přežije restarty a nic nesmaže dřív, než přijde potvrzení z cloudu.

Ring buffer ve flash: jak off-grid gateway neztratí data, když spí

Na včelnici není zásuvka. Je to banální věta a rozhoduje o velké části architektury bjtOS. Občas taky donutí znovu otevřít rozhodnutí, která vypadala uzavřená: přesně to se stalo, když jsem se pustil do bufferingu na gatewayi, tedy na té krabičce, která sbírá data ze všech úlů na včelnici a posílá je do cloudu.

Výsledkem je nevolatilní ring buffer ve flash: kruhový buffer zapisovaný do trvalé paměti, který přežije vypnutí i restart. V tomto textu popisuji, proč je potřeba, jak je udělaný, a co mě při jeho návrhu překvapilo nejvíc: že rozšířený instinkt “nezapisuj do flash moc často, opotřebuješ ji” je pravdivý jen z poloviny, a ta chybějící polovina je právě ta, podle které se rozhoduje.

Problém: nejschopnější kus je zároveň ten nejhladovější

Gateway má uvnitř dva velmi odlišné mozky.

Jeden je malý linuxový počítač. Umí mluvit s mobilní sítí, šifrovat, spravovat fronty, opakovat pokus, když se něco pokazí. Je pohodlný a je taky hladový: zapnutý čtyřiadvacet hodin denně si za zimní noc řekne skoro o deset watthodin.

Druhý je mikrokontrolér s rádiem, ten, který poslouchá uzly v úlech. Spotřebuje nějakých čtyřicet miliwattů, o řád méně, a může zůstat zapnutý napořád, aniž by zatížil energetickou bilanci.

Počet je hned hotový. Pokud chci gateway napájený solárním panelem rozumné velikosti, jak jsem popsal v textu o tom, jak dlouho vydrží úl na baterie, počítač nemůže zůstat zapnutý celou noc. Musí být skoro pořád vypnutý a probudit se mezi západem a východem slunce dvakrát nebo třikrát, aby všechno odeslal najednou.

Dobře. Jenže senzory v úlech nevědí, že počítač spí. Rádiové zprávy přicházejí celou noc. Někdo si je musí zapsat.

Proč volatilní paměť nestačí

První samozřejmá odpověď je držet data v RAM na mikrokontroléru, který je stejně pořád zapnutý. Je to zároveň odpověď špatná a přijdeš na to, teprve když si představíš, jak doopravdy vypadá off-grid noc.

V noci panel nevyrábí. Baterie klesá, hlavně v mrazu, a hlavně ve chvíli, kdy si mobilní modem řekne o proud na přihlášení k síti. Brownout, tedy pokles napětí, po kterém se mikrokontrolér restartuje, není exotická nehoda do kolonky vzdálených možností: je to normální stav, který systém musí ustát.

A restart vynuluje RAM. Celá noc dat je pryč, potichu, aniž by si toho kdokoli všiml až do druhého rána.

Buffer tedy musí být v nevolatilní paměti, tedy ve flash: v paměti, která si to, co zapsala, uchová i s odpojeným proudem.

Program a erase nejsou tatáž operace

Tady přichází část, která mi změnila způsob, jakým jsem věc navrhl.

Kdo dělá embedded, nosí si s sebou podmíněný reflex: nezapisuj do flash moc, opotřebuješ ji. Je to zdravá zásada, ale kdo se zastaví u ní, začne navrhovat kolem problému místo skrz něj.

Ve flash typu NOR, té integrované v mikrokontrolérech, které používáme, existují dvě dobře odlišné operace:

  • Program, vlastní zápis. Vynuluje bity, které je potřeba, dělá se po malých kouscích, klidně mnohokrát uvnitř stejného sektoru, pokud se zapisuje do dosud panenského místa. Znatelně neopotřebovává.
  • Erase, mazání. Vrátí celý sektor, typicky 4 KB, do samých jedniček. Tohle je ta operace s omezenou endurancí, řádově sto tisíc cyklů na sektor.

Praktický důsledek převrací instinkt naruby. Zapsat každou jednotlivou zprávu hned, jak dorazí, není plýtvání: je to program, stojí málo a dělá buffer odolným vůči výpadkům napájení, protože i když mikrokontrolér o milisekundu později umře, datum už tam je. Nepočítá se počet zápisů, ale počet oběhů, které buffer po paměti udělá, protože při každém oběhu se platí mazání recyklovaných sektorů.

Je to rozlišení, které dovolí zvolit obezřetnější chování, tedy zapisuji hned a vždycky, aniž bych to platil životností hardwaru.

Kruh, dva indexy a schopnost se sám zrekonstruovat

Struktura bufferu je ta nejstarší v oboru: kruhový buffer, ring buffer. Hlava, která postupuje zápisem, ocas, který postupuje čtením, a kruh přihrádek, po němž se oba točí. Když hlava dorazí na konec, začne znovu od začátku a znovu použije už přečtený prostor. Je to FIFO pevné velikosti: nikdy neroste, nikdy nefragmentuje a horní mez zaplnění je známá hned od začátku. Na zařízení, které má fungovat samo roky, má předvídatelnost větší cenu než elegance.

Každý zapsaný záznam nese minimální hlavičku: značku pro rozpoznání, délku a CRC, tedy kontrolní kód, který řekne, jestli je obsah neporušený. Jsou potřeba pro nejhorší okamžik, kterým je restart.

Po zapnutí mikrokontrolér ničemu nevěří: znovu proskenuje partition a zrekonstruuje hlavu i ocas od nuly tím, že přečte záznamy jeden po druhém. Pokud proud spadl uprostřed zápisu, ten záznam neprojde kontrolou CRC, zahodí se a fronta zůstane konzistentní. Buffer se opraví sám, aniž by potřeboval, aby mu někdo nechal vzkaz, co dělal chvilku před smrtí.

Nad holou flash se dá položit souborový systém navržený pro mikrokontroléry, který přináší už vyřešený wear leveling, tedy rozložení opotřebení na všechny sektory, a odolnost vůči ztrátě napájení. Zaplatí se trochou zápisů navíc kvůli metadatům, což je při našich objemech zanedbatelné, a na oplátku se člověk vyhne ručnímu přepisování celé třídy chyb, které už zaplatili jiní. Ruční správa holé partition zůstává jako záložní varianta, kdyby jednou byla potřeba deterministicky vyšší efektivita.

Čísla, což je ta uklidňující část

Vezměme včelnici o dvaceti úlech, náš typický modelový případ.

Jeden záznam se vejde do nějakých čtyřiceti bytů, takže do sektoru o 4 KB jich jde asi sto. Dlouhá zimní noc o čtrnácti hodinách, při hustém tempu vysílání, vyprodukuje něco jako pět set šedesát zpráv: něco přes dvacet kilobytů.

Dvacet kilobytů. Na partition, které jich vyhradíme pět set až tisíc.

Tak proč tak velkou, když je špička tak malá? Ne kvůli normální noci. Kvůli té noci, kdy se modem nepřihlásí k síti. V tom případě se počítač probudí, zkusí to, neuspěje a buffer není vyprázdněn: data zůstanou na místě a vyrazí až v dalším okně. Rezerva neslouží k pojmutí provozu, slouží k nákupu dnů autonomie, když chybí mobilní pokrytí.

K opotřebení: zhruba tisíc tři sta záznamů denně dělá třináct sektorů denně. Na partition o jednom megabytu, tedy dvě stě padesát šest sektorů, udělá kruh jeden oběh za necelých dvacet dní. To je méně než dvacet mazání na sektor za rok, proti endurance sto tisíc cyklů. Omezení prostě neleží tady: jsme na škále, kterou hardware nikdy neuvidí. Ověří se to jednou v tabulkovém procesoru a pak se na to přestane koukat.

Stojí za to to říct, protože je to typ výpočtu, který se vždycky vyplatí udělat dopředu: ušetřil mi optimalizaci proti problému, který neexistuje, a nechal mě vybrat bezpečnější chování bez výčitek svědomí.

Pravidlo, které to celé dělá poctivým: maže se až po potvrzení

Poslední díl je ten, který mění buffer v záruku.

Když se počítač probudí, mikrokontrolér mu data nepředá proto, aby se jich zbavil. Předává mu je čtením z ocasu a ocas postoupí teprve tehdy, když přijde potvrzení, že ta data druhá strana v cloudu přijala. Žádné mazání před potvrzením. Nikdy.

Výsledkem je doručení at-least-once, alespoň jednou: v nejhorším případě dorazí tatáž zpráva dvakrát a dál po proudu je rozpoznána jako duplikát a zahozena, protože každá zpráva s sebou nese stabilní identifikátor. Přijatelný nejhorší případ je duplikát. Ztráta ne.

Obě fronty tedy pracují v sérii a každá má své řemeslo. Ta ve flash na mikrokontroléru hromadí data dlouhodobě a přežije vypnutý počítač. Ta na počítači, která v našem systému už existuje a je odzkoušená, řídí jednotlivou relaci směrem do cloudu se svými opětovnými připojeními a pokusy. Je to stejná filozofie, se kterou vznikl celý přepravní řetězec bjtOS, o čemž jsem psal v textu postavil jsem cestu dřív než senzory: datum se nikdy neztratí, hromadí se, dokud není způsob, jak ho doručit.

Co zůstává otevřené

Bylo by nepoctivé vyprávět to jako hotovou věc. Tohle je návrh napsaný, nadimenzovaný a zdůvodněný, ne ještě kód, který běží na včelnici. Co chybí, vím přesně, a je to ta nejpoučnější část:

  • protokol vyprazdňování, tedy jak spolu oba mozky mluví během přelévání, se značkami začátku a konce relace a s postupem ocasu navázaným na potvrzení;
  • čas, který dnes mikrokontrolér dostává od počítače, jenž ho bere ze sítě. S vypnutým počítačem si ho musí mezi jedním a druhým probuzením udržet sám a znovu se srovnat, až ten druhý zase naskočí;
  • urgentní cesta: poplach proti krádeži nemůže čekat na noční okno, musí počítač zapnout okamžitě. A aby ho přijímač poznal, aniž by otevíral obsah zprávy, je potřeba bit priority čitelný v hlavičce, což se musí sladit s firmwarem uzlů, a tedy s verzovaným wire kontraktem;
  • watchdog: co se stane, když se počítač zapne a nikdy nepotvrdí, protože zůstal viset ve startu nebo při přihlašování k síti.

Kritérium: co se stane v tom nejhorším okamžiku

Zásadní rozhodnutí ale padlo a obstojí v jednoduchém kritériu, které při návrhu používám často: co se stane v tom nejhorším okamžiku?

Nejhorší okamžik je tady únorová noc, se slabou baterií, s modemem, který nechytá signál, a s napětím, které tancuje. V tu chvíli musí někde být sešit, ve kterém je všechno už zapsané a který se nesmaže, když zhasne světlo.

Je to tentýž princip, který vede zbytek systému: digitálně nativní úl se navrhuje od omezení, ne od senzorů. Pokud tě zajímá celkový obraz, je tu text jak funguje chytrý úl, a o tom, na čem stavíme, jsou stránky věnované technologii a produktu.