← Wszystkie historie

Bufor cykliczny we flashu: jak gateway off-grid nie traci danych, gdy śpi

W pasiece nie ma prądu, więc gateway musi być wyłączony niemal całą noc. Jak nieulotny bufor cykliczny we flashu odkłada telemetrię uli, przeżywa restarty i nie kasuje niczego przed potwierdzeniem z chmury.

Bufor cykliczny we flashu: jak gateway off-grid nie traci danych, gdy śpi

W pasiece nie ma gniazdka. To zdanie brzmi banalnie, a decyduje o dużej części architektury bjtOS. Co jakiś czas zmusza też do ponownego otwarcia decyzji, które wydawały się zamknięte: dokładnie to mi się przydarzyło, gdy zabrałem się za buforowanie w gatewayu, czyli w skrzynce, która zbiera dane wszystkich uli w pasiece i wysyła je do chmury.

Rezultatem jest nieulotny bufor cykliczny we flashu: ring buffer zapisywany w pamięci trwałej, który przeżywa wyłączenie zasilania i restarty. W tym tekście opowiadam, po co jest potrzebny, jak jest zbudowany i co zaskoczyło mnie najbardziej podczas projektowania: że powszechny odruch “nie zapisuj za często we flashu, zużyjesz go” jest prawdziwy tylko w połowie, a brakująca połowa to właśnie ta, która pozwala podjąć decyzję.

Problem: najzdolniejszy element jest też najbardziej żarłoczny

W gatewayu siedzą dwa bardzo różne mózgi.

Pierwszy to mały komputer z Linuksem. Potrafi rozmawiać z siecią komórkową, szyfrować, obsługiwać kolejki, ponawiać próby, gdy coś pójdzie nie tak. Jest wygodny i jest też żarłoczny: włączony dwadzieścia cztery godziny na dobę zjada w zimową noc prawie dziesięć watogodzin.

Drugi to mikrokontroler z radiem, ten, który nasłuchuje węzłów w ulach. Pobiera jakieś czterdzieści miliwatów, o rząd wielkości mniej, i może być włączony w nieskończoność, nie obciążając bilansu energetycznego.

Rachunek jest prosty. Jeśli chcę gateway zasilany panelem słonecznym o rozsądnych rozmiarach, o czym pisałem przy okazji tego, jak długo wytrzymuje ul na baterii, komputer nie może być włączony całą noc. Musi być wyłączony niemal zawsze i budzić się dwa albo trzy razy między zachodem a wschodem słońca, żeby wysłać wszystko hurtem.

Dobrze. Tyle że czujniki w ulach nie wiedzą, że komputer śpi. Wiadomości radiowe przychodzą przez całą noc. Ktoś musi je zanotować.

Dlaczego pamięć ulotna nie wystarczy

Pierwsza oczywista odpowiedź to trzymać dane w RAM-ie mikrokontrolera, skoro i tak jest zawsze włączony. Jest to też odpowiedź błędna, a dochodzi się do tego dopiero wtedy, gdy pomyśli się, jak naprawdę wygląda noc off-grid.

W nocy panel nie produkuje. Bateria ugina się, zwłaszcza na mrozie, i zwłaszcza wtedy, gdy modem komórkowy pobiera prąd, żeby zaczepić się o sieć. Brownout, czyli spadek napięcia, który powoduje restart mikrokontrolera, nie jest egzotycznym wypadkiem do wpisania między mało prawdopodobne ewentualności: to normalny warunek pracy, który system musi wytrzymać.

A restart zeruje RAM. Cała noc danych znika, po cichu, i nikt się nie zorientuje aż do następnego ranka.

Zatem bufor musi leżeć w pamięci nieulotnej, czyli we flashu: w pamięci, która zachowuje zapisaną treść także po odcięciu zasilania.

Program i erase to nie ta sama operacja

Tu przychodzi część, która zmieniła mi sposób projektowania tej rzeczy.

Kto pracuje z systemami wbudowanymi, nosi w sobie odruch warunkowy: nie zapisuj za dużo we flashu, zużyjesz go. To zdrowa wskazówka, ale jeśli zatrzymamy się na niej, kończy się projektowaniem wokół problemu zamiast przez niego.

We flashu NOR, tym wbudowanym w mikrokontrolery, których używamy, istnieją dwie wyraźnie różne operacje:

  • Program, czyli właściwy zapis. Zeruje potrzebne bity, wykonuje się małymi porcjami, także wiele razy w obrębie tego samego sektora, byle zapisywać na przestrzeni jeszcze dziewiczej. Nie zużywa pamięci w zauważalny sposób.
  • Erase, czyli kasowanie. Przywraca cały sektor, zwykle 4 KB, do samych jedynek. To ta operacja ma ograniczoną wytrzymałość, rzędu stu tysięcy cykli na sektor.

Praktyczna konsekwencja odwraca odruch. Zapisywanie każdej pojedynczej wiadomości zaraz po jej odebraniu nie jest marnotrawstwem: to program, kosztuje niewiele i czyni bufor odpornym na zaniki zasilania, bo jeśli mikrokontroler padnie milisekundę później, dana jest już na miejscu. Liczyć trzeba nie liczbę zapisów, lecz liczbę okrążeń, które bufor wykonuje po pamięci, bo to przy każdym okrążeniu płaci się kasowanie na przetwarzanych ponownie sektorach.

To rozróżnienie pozwala wybrać zachowanie ostrożniejsze, czyli zapisuję od razu i zawsze, nie płacąc za to żywotnością sprzętu.

Pierścień, dwa indeksy i zdolność do odbudowy

Struktura bufora jest najstarsza w tym fachu: bufor cykliczny, czyli ring buffer. Głowa, która przesuwa się, zapisując, ogon, który przesuwa się, czytając, i pierścień komórek, po którym oba krążą. Gdy głowa dobiega do końca, rusza od początku, wykorzystując przestrzeń już odczytaną. To FIFO o stałym rozmiarze: nigdy nie rośnie, nigdy się nie fragmentuje i ma z góry znaną górną granicę zajętości. W urządzeniu, które ma działać samo przez lata, przewidywalność jest warta więcej niż elegancja.

Każdy zapisany rekord niesie minimalny nagłówek: znacznik pozwalający go rozpoznać, długość i CRC, czyli sumę kontrolną mówiącą, czy zawartość jest nienaruszona. Są potrzebne na najgorszy moment, czyli na restart.

Przy starcie mikrokontroler nie ufa niczemu: skanuje partycję ponownie i odbudowuje głowę oraz ogon od zera, czytając rekordy jeden po drugim. Jeśli zasilanie padło w połowie zapisu, ten rekord nie przechodzi kontroli CRC, zostaje odrzucony, a kolejka pozostaje spójna. Bufor naprawia się sam, bez potrzeby, by ktoś zostawił mu notatkę o tym, co robił chwilę przed śmiercią.

Na surowym flashu można oprzeć system plików pomyślany dla mikrokontrolerów, który wnosi w posagu wear leveling, czyli rozkładanie zużycia na wszystkie sektory, oraz rozwiązaną już odporność na zanik zasilania. Płaci się nieco dodatkowych zapisów na metadane, co przy naszych wolumenach jest bez znaczenia, a w zamian unika się przepisywania ręcznie całej klasy błędów, za które inni już zapłacili. Ręczne zarządzanie surową partycją pozostaje opcją rezerwową, gdyby któregoś dnia potrzebna była bardziej deterministyczna wydajność.

Liczby, czyli część uspokajająca

Weźmy pasiekę na dwadzieścia uli, nasz typowy przypadek analizy.

Rekord mieści się w jakichś czterdziestu bajtach, więc w sektorze 4 KB mieści się ich około stu. Długa, czternastogodzinna zimowa noc, przy gęstym rytmie nadawania, produkuje coś koło pięciuset sześćdziesięciu wiadomości: niewiele ponad dwadzieścia kilobajtów.

Dwadzieścia kilobajtów. Na partycji, której przeznaczamy od pięciuset do tysiąca.

To dlaczego tak szeroka, skoro szczyt jest tak mały? Nie dla normalnej nocy. Dla nocy, w której modem nie zaczepi się o sieć. W takim wypadku komputer budzi się, próbuje, nie daje rady, a bufor nie zostaje opróżniony: dane zostają na miejscu i ruszają w następnym oknie. Zapas nie służy pomieszczeniu ruchu, służy kupieniu dni autonomii wtedy, gdy brakuje zasięgu komórkowego.

O zużyciu: około tysiąca trzystu rekordów dziennie daje trzynaście sektorów dziennie. Na partycji jednomegabajtowej, czyli dwustu pięćdziesięciu sześciu sektorach, pierścień wykonuje okrążenie mniej więcej co dwadzieścia dni. Wychodzi mniej niż dwadzieścia kasowań na sektor rocznie, przy wytrzymałości stu tysięcy cykli. Ograniczenie po prostu nie leży tutaj: jesteśmy w skali, której sprzęt nigdy nie zobaczy. Sprawdza się to raz w arkuszu kalkulacyjnym i potem przestaje się na to patrzeć.

Warto o tym powiedzieć, bo to rodzaj rachunku, który zawsze opłaca się zrobić wcześniej: oszczędził mi optymalizowania pod problem, który nie istnieje, i pozwolił wybrać bezpieczniejsze zachowanie bez wyrzutów sumienia.

Reguła, która czyni to wszystko uczciwym: kasuje się dopiero po potwierdzeniu

Ostatni element to ten, który zamienia bufor w gwarancję.

Gdy komputer się budzi, mikrokontroler nie wręcza mu danych po to, by się ich pozbyć. Podaje mu je, czytając z ogona, a ogon przesuwa się tylko wtedy, gdy przyjdzie potwierdzenie, że te dane zostały przyjęte po drugiej stronie, w chmurze. Żadnego kasowania przed potwierdzeniem. Nigdy.

Rezultatem jest dostarczanie at-least-once, czyli co najmniej raz: w najgorszym wypadku ta sama wiadomość dociera dwa razy, a niżej w łańcuchu zostaje rozpoznana jako duplikat i usunięta, bo każda wiadomość niesie ze sobą stabilny identyfikator. Akceptowalnym najgorszym przypadkiem jest duplikat. Utrata nie jest.

Obie kolejki pracują więc szeregowo i każda ma swoje zadanie. Ta we flashu mikrokontrolera gromadzi w długim horyzoncie i przeżywa wyłączony komputer. Ta na komputerze, która w naszym systemie już istnieje i jest sprawdzona, obsługuje pojedynczą sesję w stronę chmury ze swoimi ponownymi połączeniami i próbami. To ta sama filozofia, z której narodził się cały łańcuch transportowy bjtOS, o czym pisałem w zbudowałem drogę przed czujnikami: dana nigdy się nie gubi, gromadzi się, dopóki nie ma jak jej dostarczyć.

Co zostaje otwarte

Byłbym nieuczciwy, opowiadając to jako rzecz zrobioną. To projekt spisany, zwymiarowany i uargumentowany, a nie jeszcze kod działający w pasiece. To, czego brakuje, znam dokładnie i jest to część najbardziej pouczająca:

  • protokół zrzutu, czyli sposób, w jaki oba mózgi rozmawiają podczas przelewania danych, ze znacznikami początku i końca sesji oraz przesuwaniem ogona powiązanym z potwierdzeniem;
  • czas, który dziś mikrokontroler dostaje od komputera, a ten pobiera go z sieci. Przy wyłączonym komputerze musi utrzymywać go sam między jednym przebudzeniem a drugim i wyrównywać się, gdy tamten wraca do życia;
  • ścieżka pilna: alarm antywłamaniowy nie może czekać na nocne okno, musi natychmiast obudzić komputer. A żeby odbiornik mógł go rozpoznać, nie otwierając zawartości wiadomości, potrzebny jest bit priorytetu czytelny w nagłówku, co trzeba skoordynować z firmware węzłów, a więc z wersjonowanym kontraktem wire;
  • watchdog: co się dzieje, gdy komputer się włączy i nigdy nie potwierdzi, bo zawiesił się przy starcie albo przy zaczepianiu się o sieć.

Kryterium: co się dzieje w najgorszym momencie

Decyzja co do podstaw jest jednak podjęta i broni się prostym kryterium, którego często używam przy projektowaniu: co się dzieje w najgorszym momencie?

Najgorszy moment to tutaj lutowa noc, z niską baterią, modemem, który nie łapie zasięgu, i tańczącym napięciem. W tej chwili gdzieś musi leżeć zeszyt, w którym wszystko jest już zapisane i który nie kasuje się, gdy gaśnie światło.

To ta sama zasada, która prowadzi całą resztę systemu: cyfrowego z natury ula projektuje się, wychodząc od ograniczeń, a nie od czujników. Jeśli interesuje cię całościowy obraz, jest tekst o tym, jak działa inteligentny ul, a o tym, na czym budujemy, mówią strony poświęcone technologii i produktowi.