Salta al contenuto

Blog ·

Una giunzione invece di una riconciliazione

Un passo di automazione si è congelato sullo stato che aveva visto. La soluzione non era un processo di fondo: quella riga spiega una decisione presa.

Di Provibr

Un flusso di automazione può inviare un comando a una macchina e attenderlo. Se l’attesa scade, il passo fallisce e l’esecuzione prende la sua uscita di errore. È corretto: il flusso ha chiesto di attendere finché il lavoro non fosse concluso, e concluso non è, quindi nessuno sa se il nodo successivo stia per agire su una macchina che si sta ancora spegnendo.

Il passo registra ciò che ha visto in quell’istante. Misurato in una tornata precedente contro un agent vero, ha registrato sent, e sessanta millisecondi dopo il comando è passato a scadenza. Il passo continuava a dire sent, un giorno dopo, per sempre.

La lettura ovvia è che la riga sia superata e vada riparata. Quella lettura è sbagliata, e capire perché ha assorbito la maggior parte del ragionamento di questa tornata.

La riga è una spiegazione, non un campo di stato

Il valore congelato non è una copia dello stato del comando andata alla deriva. È la risposta a un’altra domanda: che cosa sapeva il flusso quando ha deciso di prendere l’uscita di errore.

Sovrascrivetelo con timeout e la riga dice ora che il flusso ha reagito a una scadenza. Non lo ha fatto. Ha reagito a un’attesa che finiva mentre il comando era ancora in volo, un evento diverso con altra causa e altra riparazione. Un mese dopo, chi leggesse quell’esecuzione trarrebbe la conclusione sbagliata da una riga corretta con le migliori intenzioni.

Il valore congelato resta dunque congelato. Ciò che era davvero sbagliato era il commento sopra di esso, che prometteva di dedurre da questa riga se il comando avesse infine avuto successo. Non lo si poteva, e una promessa in un commento ha una durata che arriva fino al giorno in cui qualcuno vi si affida.

La domanda vera, se alla fine sia andata bene, si risolve leggendo lo stato attuale del comando nel momento in cui qualcuno guarda. La pagina dell’esecuzione mostra entrambi, affiancati: ciò che il flusso vedeva allora e ciò che è vero adesso.

Da dove vieneA che cosa risponde
AlloraLa riga di verifica, congelata al momento della decisionePerché l’esecuzione ha preso l’uscita che ha preso
AdessoLa tabella dei comandi, letta all’apertura della paginaSe il lavoro alla fine si è concluso
Riga assenteNessuna delle due; mostrata come stato proprioIl comando non figura più nei registri

Quest’ultima riga conta. Se la riga del comando è stata ripulita, la vista non ripiega sulla ripetizione del valore congelato. «Non figura più nei registri» e «dice ancora sent» sono due fatti diversi, e una pagina che rende il secondo mentre è vero il primo mente senza battere ciglio.

Tre ragioni per cui un processo di fondo perde contro una giunzione

L’alternativa era un processo di fondo che riscrivesse lo stato finale nella riga di verifica appena il comando fosse deciso. Altrove abbiamo processi esattamente di quella forma, quindi era una scelta vera e non un fantoccio.

Un processo di fondo sovrascrive una spiegazione. Come sopra. La via d’uscita è scrivere un secondo campo accanto al primo, ed è onesto, ma è una seconda verità che verrebbe letta in esattamente un punto: quello che ora fa la giunzione.

Un processo di fondo ripara solo ciò che accade dopo di lui. La riga da cui è partita tutta questa indagine esiste già. Uno distribuito oggi non la toccherebbe mai, né alcuna esecuzione delle due tornate precedenti, perché i loro comandi sono decisi da tempo e nessun evento futuro ripasserà di lì. Una giunzione le mostra tutte dal giorno in cui entra in servizio, compresa quella che cercavamo.

Un processo di fondo porta con sé un intero apparato. Aggiornare righe in secondo piano richiede una cadenza, una rivendicazione perché due istanze non si scontrino, un’attesa contro la deriva, una risposta sulla conservazione e un percorso di guasto proprio, e ciascuno di essi può essere sbagliato alle tre di notte. Cercare una manciata di identificativi per chiave primaria quando qualcuno apre una pagina non richiede nulla di tutto ciò.

La regola generale sottostante: meglio leggere la fonte che copiarla, a meno che la copia non risponda a una domanda a cui la fonte non può rispondere. Qui la copia risponde a un’altra domanda e si vogliono entrambe, che è proprio il caso in cui si tengono separate.

L’identificativo veniva da una colonna JSON, quindi la query è delimitata

Un dettaglio di quella giunzione non è né ottimizzazione né cortesia.

L’identificativo del comando sta dentro un blocco JSON sulla riga di verifica. Le colonne JSON non hanno chiavi esterne, quindi nulla garantisce che l’identificativo lì dentro appartenga a questo account. Oggi vi appartiene, perché ce lo ha scritto il nostro stesso codice. Non è un confine, è un’abitudine.

La ricerca è perciò delimitata all’organizzazione oltre che all’identificativo. Senza quel confine, un valore modificato a mano in quel blocco leggerebbe lo stato del comando di qualcun altro. Piccola fuga, facile da mancare, e del tipo che trova solo chi la cercava.

Un flusso per momento, con il momento nella chiave

Un piano di hosting poteva collegare un flusso di automazione, in una sola colonna. Quella colonna significava «esegui questo quando una macchina viene predisposta», e non era scritto da nessuna parte se non nel comportamento della funzione che la leggeva.

I clienti volevano anche un flusso alla reinstallazione e uno all’eliminazione. La riparazione ovvia sono tre colonne. È sbagliata per una ragione che merita di essere nominata: ciascuna di quelle tre colonne potrebbe contenere un flusso il cui attivatore dice altro. Un flusso che gira all’eliminazione, seduto nella colonna che scatta alla predisposizione. L’invariante «l’attivatore dice quando gira» diventa violabile in tre punti anziché in nessuno, e lo schema non obietta da nessuna parte.

Il collegamento è dunque diventato una tabella propria, con il piano e il momento insieme come chiave unica. Un flusso per momento e per piano, imposto da un indice anziché dalla cura.

Due dettagli di quella migrazione meritano di essere conservati.

Il momento è conservato come testo con un vincolo di controllo che elenca tre valori, e non come l’enumerazione di attivatori esistente. Quell’enumerazione ha cinque valori, e due di essi sono attivatori che questo meccanismo non sceglie mai: un’esecuzione manuale la avvia una persona, e una da webhook una richiesta in entrata. Una colonna enumerata accetterebbe volentieri l’uno o l’altro, ed esisterebbe allora una riga di collegamento con cui non accade mai nulla. Un’impostazione che in silenzio non fa niente è peggio di un’impostazione mancante.

E la vecchia colonna è stata rimossa nella stessa migrazione che ne ha copiato il contenuto. Tenerla come comoda lettura derivata è esattamente la seconda verità che questa tabella viene a sopprimere. Il compilatore ha trovato ogni lettore non appena la colonna è uscita dallo schema; erano sette. La copia gira prima della rimozione, in una sola transazione, quindi non esiste istante in cui la configurazione di un cliente non stia da nessuna parte.

La copia prende inoltre l’attivatore proprio di ogni flusso anziché presumere il senso della vecchia colonna. Le righe il cui flusso portava un altro momento non avrebbero mai girato con il vecchio codice; ora il collegamento fatto da una persona fa davvero ciò che lei credeva.

Posta per adesione, nel grafo e non nelle impostazioni

Un flusso può scrivere una notifica, e in questa tornata quella notifica può uscire anche per posta. Si è rivelata una questione di valori predefiniti e non di invio.

Le categorie di posta della piattaforma sono un elenco di esclusione: ciò che non è spento espressamente è acceso. Va bene per le categorie già esistenti, tutte cose che un cliente deve sapere. Non va bene per una notifica il cui testo qualcuno ha scritto in un editor di automazione, perché un flusso che gira a ogni ordine scriverebbe a tutta la squadra ogni volta, finché qualcuno non trova l’impostazione.

Anziché invertire l’elenco, il valore predefinito vive nel grafo: la casella sul nodo è spenta, e accenderla è un atto deliberato di chi ha scritto il testo. La categoria continua a esistere perché la posta si possa spegnere nel posto di sempre, esattamente come le altre. Due meccanismi, ciascuno per ciò in cui è bravo.

Un’esecuzione di prova non invia mai posta. Scrive la notifica del pannello, che è interna e si chiude con un clic, e non manda nulla all’esterno, per la stessa ragione per cui un’esecuzione di prova non tocca mai una macchina: la posta arriva nelle caselle di persone vere, e un pulsante di prova che sorprende qualcuno non è un pulsante di prova.

Ancora una piccola cosa dallo stesso lavoro. Si dava per scontato che il lato invio fosse irraggiungibile da dove girano i flussi, ed è per questo che la cosa era rimandata. Misurato, era il contrario: la funzione che volevamo aggirare esiste proprio perché l’altro processo non sa comporre posta, e chiede al processo in cui i flussi già girano di comporla. La deviazione era più lunga della via diretta, e la via diretta ha un salto HTTP, una firma e un percorso di guasto in meno.

Che cosa annoteremmo

  1. Una riga di verifica spiega una decisione. Non va riparata. La domanda su che cosa sia vero adesso ha un’altra risposta e merita un posto proprio.
  2. Una riparazione in secondo piano ripara solo il futuro. Se la riga che ha originato il lavoro non verrebbe toccata, è una risposta di progetto e non un dettaglio.
  3. Leggere la fonte, a meno che la copia non risponda a ciò che la fonte non può.
  4. Un identificativo uscito da una colonna JSON non ha chiave esterna. Delimitare la query all’account; che il valore lo abbia scritto il nostro codice non è un confine.
  5. Mettere la distinzione nella chiave e non nel nome della colonna. Tre colonne sono tre posti in cui riporre la cosa sbagliata; una chiave è nessuno.
  6. Rimuovere la vecchia colonna nella stessa migrazione. Una lettura derivata lasciata indietro è la seconda verità che si voleva eliminare.
  7. Un elenco di esclusione è il predefinito sbagliato per un testo scritto da un cliente. L’adesione va dove il testo si scrive.

Per chiarezza: tutto questo è costruito e funzionante. L’invio in sé è stato dimostrato con un driver che si limita a registrare e non contro un server di posta vero, e il collegamento al piano non è ancora stato esercitato con un ordine vero.

Dal lavoro di piattaforma su Provibr. Come si comportano l’attesa e l’uscita di errore per cominciare si trova in un filo che si trascina, ed esattamente un attivatore, e perché una scadenza non è un pulsante di arresto in un timeout non è un pulsante di arresto.

Inizi con un host e un agente

Crei un account, installi l’agente con un solo comando e colleghi il suo primo host Proxmox. Il periodo di prova dura 14 giorni e non chiede alcun dato di pagamento.