Blog ·
Un timeout non è un pulsante di arresto
Una scadenza termina l’attesa, non il lavoro. Che cosa fa un tetto di un’ora a un backup lungo, perché due orologi non concordano e come registrare l’ignoto.
Di Provibr
Ogni comando remoto ha bisogno di una scadenza. Su questo non si discute: un processo che può attendere per sempre prima o poi attenderà per sempre, e una sola richiesta bloccata tratterrà un worker, una connessione e una riga nella coda finché qualcuno non riavvia qualcosa.
Molto meno ovvio è che cosa faccia davvero quella scadenza. Termina l’attesa dalla sua parte. Non termina, nel caso generale, il lavoro dall’altra parte. Tutto ciò che è difficile nelle operazioni di lunga durata vive dentro quello scarto.
Noi facciamo girare un agente sugli hypervisor dei clienti e gli inviamo comandi. Il backup di una macchina virtuale è il comando che ci ha costretti a prendere sul serio quello scarto, perché un backup può onestamente richiedere un’ora, ed è esattamente lì che si trovano i nostri limiti.
Il tetto non è una preferenza
Il nostro agente riduce ogni scadenza di comando a un’ora. La piattaforma può chiedere di più; non lo otterrà. Il limite è un unico min() sul valore che arriva nella richiesta, con lo zero interpretato come «non impostato» e un ripiego di due minuti quando il campo manca.
Quel limite ha una conseguenza che sfugge a chi imposta i timeout in un file di configurazione. Se la piattaforma crede in una scadenza in cui l’agente non crede, si hanno due sistemi in disaccordo su quando un comando è morto, e il disaccordo si presenta con il sintomo peggiore possibile: l’agente rinuncia, riferisce e passa oltre, mentre la piattaforma tiene ancora aperta la riga e attende una risposta che non arriverà mai nella forma che si aspetta.
Quindi la regola a cui siamo arrivati è banale e vale comunque la pena metterla per iscritto. Ogni timeout sul lato della piattaforma viene scelto tenendo presente il tetto dell’agente. Le nostre scadenze di backup e di ripristino sono di un’ora proprio perché quello è il tetto, e fra diverse decine di scadenze di comando sono le uniche due che lo toccano.
Le scadenze non sono una scelta di stile
Ecco l’aspetto reale delle nostre scadenze di comando, e perché ciascuna è quella che è.
| Comando | Scadenza | Perché quel numero |
|---|---|---|
| Lettura dell’hardware, lettura delle regole del firewall | 30 s | Una lettura. Più a lungo di così significa che è rotto qualcos’altro. |
| Scrittura delle regole del firewall | 120 s | Una chiamata API per regola, in entrambe le direzioni, al tetto delle regole. |
| Elenco dei punti di ripristino | 60 s | Una chiamata per ogni storage di backup sul nodo, e uno storage a directory con migliaia di archivi legge il proprio indice dal disco. |
| Autoaggiornamento dell’agente | 300 s | Scaricare, calcolare l’hash, scrivere. Generoso per una linea lenta dal cliente. |
| Creazione di un backup | 3600 s | Il tetto dell’agente. |
| Ripristino di un backup | 3600 s | Lo stesso tetto, raggiunto prima: un ripristino sposta all’incirca il doppio dei byte spostati dal backup. |
| Preparazione di una nuova macchina cloud | 1200 s | Aggiornamenti dei pacchetti su un’immagine che può avere settimane, e poi l’installazione. |
| Esecuzione di una migrazione di pannello | 1800 s | Due pannelli letti per intero, scritti e ricontati. |
Due di quelle righe sono storie più che numeri.
Quella della macchina cloud ricadeva sul valore predefinito di due minuti. Due minuti bastano per un’immagine che per caso è aggiornata, e non bastano per nient’altro, così la piattaforma segnalava un fallimento mentre l’installazione dentro la macchina proseguiva e finiva qualche minuto dopo. L’ordine veniva contrassegnato come fallito. La macchina era in funzione. È il tipo peggiore di errore: non uno sbaglio, una contraddizione, ed è il cliente a doverla risolvere.
Quella della migrazione è la scadenza più lunga che non sia il limite massimo dell’agente, e il ragionamento che vi sta dietro è un conteggio anziché una sensazione. Il migratore effettua una chiamata API per ogni utente e una per ogni server verso il pannello di destinazione. Con mille server sono mille chiamate in uscita e mille risposte di ritorno, con una lettura completa di entrambi i lati prima e un riconteggio dopo.
I costi dell’errore non sono simmetrici
Questo è il principio che sta sotto entrambe le storie. Una scadenza troppo breve e una scadenza troppo lunga falliscono in modi completamente diversi, e trattarle come i due lati della stessa manopola porta ogni volta al numero sbagliato.
Troppo lunga: un’operazione che è davvero morta tiene aperta una riga ancora per un po’. Qualcuno aspetta. Nulla viene corrotto, nulla viene duplicato, e la pulizia successiva non ha nulla di drammatico.
Troppo breve: si registra un fallimento per un lavoro riuscito. Ora i registri e la realtà sono in disaccordo, e ogni percorso di recupero disponibile (riprova, ricreazione, rimborso, avviso) punta a un problema che non esiste e ne creerà un secondo.
Non sono ugualmente gravi, quindi non devono ricevere margini di uguale ampiezza. Per tutto ciò che crea, sposta o elimina risorse reali, prendiamo deliberatamente il lato generoso e mettiamo per iscritto il perché.
Due orologi che partono in momenti diversi
C’è una sottigliezza in qualunque sistema di comandi in coda, facile da trascurare e facile da misurare. La nostra piattaforma calcola la scadenza come un istante assoluto, nel momento in cui la riga del comando viene scritta. L’agente calcola la propria scadenza come una durata, che comincia quando comincia l’esecuzione.
Sono orologi diversi. Se l’agente è offline, o occupato, o il comando resta in coda per trenta secondi, la piattaforma ha già speso trenta secondi del proprio budget e l’agente no. La piattaforma rinuncia per prima.
È il verso giusto, e vale la pena essere espliciti sul perché. La scadenza della piattaforma risponde alla domanda che il cliente sta davvero ponendo: quanto tempo è passato da quando ho fatto clic? Quella dell’agente ne risponde un’altra: per quanto tempo può girare questo pezzo di lavoro? Il tempo reale dal clic è il numero di cui parla una conversazione con l’assistenza. Renderli lo stesso numero sarebbe più ordinato e risponderebbe alla domanda sbagliata.
Lì dentro si nasconde anche un terzo orologio. Sul nostro agente, l’attesa di un permesso di concorrenza avviene fuori dalla scadenza di esecuzione. Un comando in coda dietro un altro comando sulla stessa integrazione non consuma il proprio budget mentre aspetta. Se le sue scadenze sono strette, controlli quale dei suoi orologi comprende l’attesa in coda; la risposta è raramente quella che sta nella documentazione.
Che cosa annulla davvero la scadenza
Quando la scadenza dell’agente si esaurisce, l’agente segnala l’annullamento e smette di attendere. Ciò che quell’annullamento raggiunge è il suo stesso codice: il ciclo di polling che stava chiedendo all’hypervisor come procedeva il task.
Non raggiunge l’hypervisor. Il backup continua a girare. Finirà, oppure fallirà, del tutto senza di noi. Nulla dell’annullamento è arrivato fino all’array di storage.
Non è un limite da eliminare a forza di ingegneria. È la forma onesta del problema, e la mossa utile è registrare il risultato con verità invece di fingere. La tassonomia sopravvissuta al contatto con la produzione ha tre esiti e non due.
- Fallito. Sappiamo che non è successo nulla. Il cliente può riprovare e non c’è nulla da ripulire.
- Sconosciuto. Abbiamo smesso di ascoltare prima che ci fosse una risposta. Qualcosa potrebbe esistere.
- Da riconciliare. Abbiamo motivi positivi per ritenere che esista qualcosa di cui non sappiamo rendere conto.
Un ripristino che va in timeout è il caso più netto. Potrebbe esserci ora una macchina in funzione sull’hypervisor per cui il nostro database non ha alcuna riga. Contrassegnarlo come fallito sarebbe una bugia che nasconde una macchina; contrassegnarlo come riuscito sarebbe una bugia che ne inventa una. Viene contrassegnato come da verificare, e il pannello chiede al cliente di controllare invece di offrirgli un pulsante di riprova che costruirebbe una seconda macchina accanto alla prima.
Quella regola l’abbiamo messa nel codice come un elenco dei casi certi, non di quelli incerti. Quattro specifici codici di errore significano che il lavoro non è mai cominciato, perché tutti e quattro provengono da controlli che l’agente esegue prima di effettuare la prima chiamata. Tutto il resto, compreso qualunque codice di errore aggiunto in seguito e dimenticato nella classificazione, ricade per impostazione predefinita sull’incerto. La direzione conta: un codice dimenticato che si registra in silenzio come fallito fa sparire dai registri una macchina esistente, mentre un codice dimenticato che si registra come incerto costa soltanto una verifica a qualcuno.
Un risultato incerto ha bisogno di un modo per diventare certo
Uno stato che significa «qualcuno dovrebbe controllare» è onesto solo se qualcuno, o qualcosa, controlla davvero. Il nostro è un worker che chiede periodicamente a ogni macchina con uno storico di backup di elencare di nuovo i propri punti di ripristino, così un archivio svanito in silenzio presso il fornitore diventa una notifica anziché un elenco che si è accorciato nel giorno in cui un cliente per caso lo guarda.
Su quel ciclo grava un vincolo deliberato di parsimonia, perché ogni elencazione è un comando che gira sull’host di qualcun altro. Visita solo le macchine che hanno una politica o almeno un punto di ripristino noto, e prende un lotto limitato per ogni giro. Quando questo smetterà di bastare, la correzione giusta è un cursore, non un numero più grande.
Il ciclo deve anche sopravvivere a due dei suoi server che decidono di eseguirlo nello stesso secondo. Lo facciamo con una chiave di idempotenza durevole composta dalla macchina e dal numero della finestra, non da un timestamp: due server non scattano mai sullo stesso millisecondo, ma sono di sicuro dentro la stessa finestra di sei ore, quindi un timestamp non deduplicherebbe nulla.
Che cosa le diremmo di mettere per iscritto
Se sta scegliendo le scadenze per il lavoro remoto, quattro domande la portano quasi a destinazione.
- Qual è il tetto rigido e chi lo applica? Se due componenti sono in disaccordo, nella pratica vince il più piccolo e il più grande è finzione.
- Quando parte ciascun orologio? Il tempo in coda o rientra nel budget o non ci rientra, ed entrambe le risposte sono difendibili. Non saperlo non lo è.
- Che cosa annulla la scadenza? Se la risposta è «la nostra attesa», lo dica ad alta voce, perché il resto della progettazione discende da lì.
- Da che parte cade un esito non classificato? Faccia dell’incertezza l’impostazione predefinita, e lasci che sia la certezza la cosa da mettere per iscritto.
Niente di tutto questo rende più breve un backup lungo. Rende vero il suo resoconto, che è la parte che si troverà a leggere alle due di notte.