← Tutte le storie

Un ring buffer in flash: come un gateway off-grid non perde dati mentre dorme

In un apiario non c'è corrente, quindi il gateway deve stare spento quasi tutta la notte. Come un ring buffer non volatile in flash tiene da parte la telemetria delle arnie, sopravvive ai riavvii e non cancella niente prima della conferma dal cloud.

Un ring buffer in flash: come un gateway off-grid non perde dati mentre dorme

In un apiario non c’è una presa di corrente. È una frase banale, e decide gran parte dell’architettura di bjtOS. Ogni tanto costringe anche a riaprire scelte che sembravano chiuse: è quello che è successo quando ho affrontato il buffering del gateway, cioè la scatola che raccoglie i dati di tutte le arnie di un apiario e li spedisce al cloud.

Il risultato è un ring buffer non volatile in flash: un buffer circolare scritto in memoria permanente, che sopravvive allo spegnimento e ai riavvii. In questo articolo racconto perché serve, come è fatto, e la cosa che progettandolo mi ha sorpreso di più: che l’istinto diffuso di “non scrivere troppo spesso in flash, la consumi” è vero solo a metà, e la metà che manca è proprio quella che serve per decidere.

Il problema: il pezzo più capace è anche il più affamato

Il gateway ha dentro due cervelli molto diversi.

Uno è un piccolo computer Linux. Sa parlare con la rete cellulare, cifrare, gestire code, riprovare quando qualcosa va storto. È comodo, ed è anche affamato: acceso ventiquattr’ore su ventiquattro chiede quasi dieci wattora in una notte invernale.

L’altro è un microcontrollore con la radio, quello che sta in ascolto dei nodi delle arnie. Consuma una quarantina di milliwatt, un ordine di grandezza in meno, e può restare acceso per sempre senza pesare sul bilancio energetico.

Il conto è presto fatto. Se voglio un gateway alimentato da un pannello solare di taglia ragionevole, come ho raccontato parlando di quanto dura un’arnia a batteria, il computer non può stare acceso tutta la notte. Deve stare spento quasi sempre, e accendersi due o tre volte tra il tramonto e l’alba per spedire tutto in blocco.

Bene. Ma i sensori delle arnie non sanno che il computer dorme. I messaggi radio continuano ad arrivare per tutta la notte. Qualcuno deve prendere nota.

Perché non basta la memoria volatile

La prima risposta ovvia è tenere i dati in RAM sul microcontrollore, che tanto è sempre acceso. È anche la risposta sbagliata, e ci si arriva solo pensando a com’è fatta davvero una notte off-grid.

Di notte il pannello non produce. La batteria flette, soprattutto al freddo, e soprattutto quando il modem cellulare chiede corrente per agganciare la rete. Un brownout, cioè un abbassamento di tensione che fa ripartire il microcontrollore, non è un incidente esotico da mettere fra le eventualità remote: è la condizione normale che il sistema deve reggere.

E un riavvio azzera la RAM. Una notte intera di dati sparita, in silenzio, senza che nessuno se ne accorga fino alla mattina dopo.

Quindi il buffer deve stare in memoria non volatile, cioè in flash: una memoria che conserva quello che ha scritto anche a corrente staccata.

Program ed erase non sono la stessa operazione

Qui arriva la parte che mi ha cambiato il modo di progettare la cosa.

Chi lavora con l’embedded si porta dietro un riflesso condizionato: non scrivere troppo in flash, la consumi. È un’indicazione sana, ma se ci si ferma lì si finisce per progettare attorno al problema invece che attraverso.

Nella flash NOR, quella integrata nei microcontrollori che usiamo, ci sono due operazioni ben distinte:

  • Program, la scrittura vera e propria. Mette a zero i bit che servono, si fa a pezzetti piccoli, anche molte volte dentro lo stesso settore, purché si scriva su spazio ancora vergine. Non logora in modo apprezzabile.
  • Erase, la cancellazione. Riporta un intero settore, tipicamente 4 KB, a tutti uno. È questa l’operazione con l’endurance limitata, dell’ordine dei centomila cicli per settore.

La conseguenza pratica ribalta l’istinto. Scrivere ogni singolo messaggio appena arriva non è uno spreco: è un program, costa poco, e rende il buffer resistente alle cadute di corrente, perché se il microcontrollore muore un millisecondo dopo il dato è già lì. Quello che va contato non è il numero di scritture, ma il numero di giri che il buffer compie sulla memoria, perché è a ogni giro che si paga una cancellazione sui settori riciclati.

È una distinzione che permette di scegliere il comportamento più prudente, cioè scrivo subito e sempre, senza pagarlo in durata dell’hardware.

Un anello, due indici, e la capacità di ricostruirsi

La struttura del buffer è la più vecchia del mestiere: un buffer circolare, o ring buffer. Una testa che avanza scrivendo, una coda che avanza leggendo, un anello di caselle su cui girano entrambe. Quando la testa arriva in fondo riparte dall’inizio, riusando lo spazio già letto. È una FIFO a dimensione fissa: non cresce mai, non frammenta mai, e ha un limite superiore di occupazione noto da subito. Su un dispositivo che deve funzionare da solo per anni, la prevedibilità vale più dell’eleganza.

Ogni record scritto porta un’intestazione minima: un marcatore per riconoscerlo, la lunghezza, e un CRC, cioè un codice di controllo che dice se il contenuto è integro. Servono per il momento peggiore, che è il riavvio.

All’accensione il microcontrollore non si fida di niente: riscandisce la partizione e ricostruisce testa e coda da zero, leggendo i record uno per uno. Se la corrente è caduta a metà di una scrittura, quel record fallisce il controllo CRC, viene scartato, e la coda resta consistente. Il buffer si ripara da solo, senza aver bisogno che qualcuno gli abbia lasciato detto cosa stava facendo un attimo prima di morire.

Sopra la flash grezza si può appoggiare un filesystem pensato per i microcontrollori, che porta in dote il wear leveling, cioè la distribuzione dell’usura su tutti i settori, e la resistenza alla perdita di corrente già risolte. Si paga un po’ di scrittura in più per i metadati, che ai nostri volumi è irrilevante, e in cambio si evita di riscrivere a mano una classe di bug che altri hanno già pagato. La gestione manuale della partizione grezza resta l’opzione di riserva, se un giorno servisse un’efficienza più deterministica.

I numeri, che sono la parte rassicurante

Prendiamo un apiario da venti arnie, il nostro caso di studio tipo.

Un record sta in una quarantina di byte, quindi in un settore da 4 KB ne stanno un centinaio. Una notte invernale lunga quattordici ore, a ritmo di trasmissione fitto, produce qualcosa come cinquecentosessanta messaggi: poco più di venti kilobyte.

Venti kilobyte. Su una partizione che ne dedichiamo da cinquecento a mille.

E allora perché così larga, se il picco è così piccolo? Non per la notte normale. Per la notte in cui il modem non aggancia la rete. In quel caso il computer si sveglia, prova, fallisce, e il buffer non viene svuotato: i dati restano lì e ripartono alla finestra successiva. Il margine non serve a contenere il traffico, serve a comprare giorni di autonomia quando la copertura cellulare manca.

Sull’usura: circa milletrecento record al giorno fanno tredici settori al giorno. Su una partizione da un megabyte, cioè duecentocinquantasei settori, l’anello compie un giro ogni venti giorni scarsi. Fanno meno di venti cancellazioni per settore all’anno, contro un’endurance di centomila cicli. Il vincolo, semplicemente, non è questo: siamo su una scala che l’hardware non vedrà mai. Lo si verifica una volta con un foglio di calcolo e poi si smette di guardarlo.

Vale la pena dirlo perché è il tipo di conto che vale sempre la pena fare prima: mi ha risparmiato di ottimizzare contro un problema che non esiste, e mi ha lasciato scegliere il comportamento più sicuro senza sensi di colpa.

La regola che rende tutto onesto: si cancella solo dopo la conferma

L’ultimo pezzo è quello che trasforma un buffer in una garanzia.

Quando il computer si sveglia, il microcontrollore non gli consegna i dati per poi liberarsene. Glieli passa leggendoli dalla coda, e la coda avanza soltanto quando arriva la conferma che quei dati sono stati accettati dall’altra parte, in cloud. Nessuna cancellazione prima della conferma. Mai.

Il risultato è una consegna at-least-once, almeno una volta: nel caso peggiore lo stesso messaggio arriva due volte, e a valle viene riconosciuto come duplicato ed eliminato, perché ogni messaggio porta con sé un identificatore stabile. Il caso peggiore accettabile è il duplicato. La perdita non lo è.

Le due code lavorano quindi in serie, e ognuna ha il suo mestiere. Quella in flash sul microcontrollore accumula sul lungo periodo e sopravvive al computer spento. Quella sul computer, che nel nostro sistema esiste già ed è collaudata, gestisce la singola sessione verso il cloud con le sue riconnessioni e i suoi tentativi. È la stessa filosofia con cui è nata tutta la catena di trasporto di bjtOS, che ho raccontato in ho costruito la strada prima dei sensori: il dato non si perde mai, si accumula finché non c’è modo di consegnarlo.

Cosa resta aperto

Sarei disonesto a raccontarla come cosa fatta. Questo è un progetto scritto, dimensionato e argomentato, non ancora codice che gira in un apiario. Quello che manca lo so con precisione, ed è la parte più istruttiva:

  • il protocollo di scarico, cioè come i due cervelli si parlano durante il travaso, con i marcatori di inizio e fine sessione e l’avanzamento della coda legato alla conferma;
  • l’ora, che oggi il microcontrollore riceve dal computer, il quale la prende dalla rete. Con il computer spento deve mantenersela da solo tra un risveglio e l’altro, e riallinearsi quando l’altro torna acceso;
  • il percorso urgente: un allarme antifurto non può aspettare la finestra notturna, deve accendere il computer subito. E perché il ricevitore possa riconoscerlo senza aprire il contenuto del messaggio, serve un bit di priorità leggibile nell’intestazione, che va coordinato con il firmware dei nodi e quindi con il contratto wire versionato;
  • il watchdog: cosa succede se il computer si accende e non conferma mai, perché è rimasto bloccato in avvio o nell’aggancio alla rete.

Il criterio: cosa succede nel momento peggiore

La scelta di fondo però è presa, e regge a un criterio semplice che uso spesso quando progetto: cosa succede nel momento peggiore?

Il momento peggiore qui è una notte di febbraio, con la batteria bassa, il modem che non trova campo e la tensione che balla. In quel momento, da qualche parte, deve esserci un quaderno su cui è già scritto tutto, e che non si cancella quando si spegne la luce.

È lo stesso principio che guida tutto il resto del sistema: un’arnia nativa digitale si progetta partendo dai vincoli, non dai sensori. Se ti interessa il quadro d’insieme c’è come funziona un’arnia smart, e su cosa stiamo costruendo trovi la pagina dedicata alla tecnologia e quella al prodotto.