Săriți la conținut

Blog ·

O îmbinare în locul unei reconcilieri

Un pas de automatizare a înghețat pe starea pe care a văzut-o. Reparația nu era un proces de fundal: rândul acela explică o decizie, iar repararea o distruge.

De Provibr

Un flux de automatizare poate trimite o comandă către o mașină și o poate aștepta. Dacă așteptarea expiră, pasul eșuează, iar execuția ia ieșirea sa de eroare. Este corect: fluxul a cerut să aștepte până când lucrarea este încheiată, iar ea nu este, deci nimeni nu știe dacă nodul următor este pe cale să lucreze pe o mașină care încă se oprește.

Pasul consemnează ce a văzut în clipa aceea. Măsurat într-o rundă anterioară împotriva unui agent real, a consemnat sent, iar șaizeci de milisecunde mai târziu comanda a trecut în timp expirat. Pasul spunea încă sent, o zi mai târziu, pentru totdeauna.

Citirea evidentă este că rândul este învechit și trebuie reparat. Citirea aceea este greșită, iar înțelegerea motivului a ocupat cea mai mare parte a gândirii din runda aceasta.

Rândul este o explicație, nu un câmp de stare

Valoarea înghețată nu este o copie a stării comenzii care ar fi derivat. Este răspunsul la altă întrebare: ce știa fluxul când a hotărât să ia ieșirea de eroare.

Scrieți timeout peste ea și rândul spune acum că fluxul a reacționat la un timp expirat. Nu a făcut asta. A reacționat la o așteptare care se sfârșea în timp ce comanda era încă în zbor, un eveniment diferit, cu altă cauză și altă reparație. O lună mai târziu, cine ar citi execuția aceea ar trage concluzia greșită dintr-un rând corectat cu bune intenții.

Valoarea înghețată rămâne așadar înghețată. Ce era cu adevărat greșit era comentariul de deasupra ei, care promitea că din rândul acesta se poate afla dacă a reușit comanda în cele din urmă. Nu se putea, iar o promisiune dintr-un comentariu are o valabilitate care ține până în ziua în care cineva se bizuie pe ea.

Întrebarea adevărată, dacă în cele din urmă a ieșit bine, se rezolvă citind starea actuală a comenzii în clipa în care cineva se uită. Pagina execuției le arată pe amândouă, alăturate: ce vedea fluxul atunci și ce este adevărat acum.

De unde vineLa ce răspunde
AtunciRândul de audit, înghețat în clipa decizieiDe ce a luat execuția ieșirea pe care a luat-o
AcumTabelul comenzilor, citit la deschiderea paginiiDacă lucrarea s-a încheiat până la urmă
Rând lipsăNiciuna dintre cele două; arătat ca stare proprieComanda nu mai figurează în registre

Rândul acela din urmă contează. Dacă rândul comenzii a fost curățat, afișarea nu recade pe repetarea valorii înghețate. „Nu mai figurează în registre” și „spune încă sent” sunt două fapte diferite, iar o pagină care îl redă pe al doilea când adevărat este primul minte fără să clipească.

Trei motive pentru care un proces de fundal pierde în fața unei îmbinări

Alternativa era un proces de fundal care să rescrie starea finală în rândul de audit de îndată ce comanda este soluționată. Avem în alte părți procese exact de forma aceea, deci a fost o alegere reală și nu o sperietoare.

Un proces de fundal suprascrie o explicație. Ca mai sus. Scăparea este scrierea unui al doilea câmp alături, iar asta este cinstit, dar este un al doilea adevăr care ar fi citit în exact un loc: acela care face acum îmbinarea.

Un proces de fundal repară doar ce se întâmplă după el. Rândul care a pornit toată cercetarea aceasta există deja. Unul lansat astăzi nu l-ar atinge niciodată, nici vreo execuție din cele două runde dinainte, fiindcă comenzile lor sunt soluționate de mult, iar niciun eveniment viitor nu va mai trece pe acolo. O îmbinare le arată pe toate din ziua în care intră în serviciu, inclusiv pe cea pe care o căutam.

Un proces de fundal aduce cu el un aparat întreg. Actualizarea rândurilor în fundal cere o cadență, o revendicare pentru ca două instanțe să nu se ciocnească, o așteptare împotriva derivei, un răspuns despre păstrare și o cale de eșec proprie, iar fiecare dintre ele poate fi greșită la trei dimineața. Căutarea câtorva identificatori după cheia primară atunci când cineva deschide o pagină nu cere nimic din toate acestea.

Regula generală de dedesubt: mai bine citiți sursa decât să o copiați, dacă nu cumva copia răspunde la o întrebare la care sursa nu poate răspunde. Aici copia răspunde la altă întrebare și le vreți pe amândouă, iar acesta este tocmai cazul în care le țineți separate.

Identificatorul venea dintr-o coloană JSON, deci interogarea este mărginită

Un amănunt din îmbinarea aceea nu este nici optimizare, nici politețe.

Identificatorul comenzii stă într-un bloc JSON de pe rândul de audit. Coloanele JSON nu au chei străine, deci nimic nu garantează că identificatorul de acolo aparține contului acestuia. Astăzi aparține, fiindcă l-a scris chiar codul nostru. Aceea nu este o graniță, este un obicei.

Căutarea este de aceea mărginită la organizație pe lângă identificator. Fără granița aceea, o valoare modificată manual în blocul acela ar citi starea comenzii altcuiva. Scurgere mică, ușor de ratat, și de felul celei pe care o găsește doar cine o căuta.

Un flux pe moment, cu momentul în cheie

Un plan de găzduire putea lega un flux de automatizare, într-o singură coloană. Coloana aceea însemna „execută asta când o mașină este aprovizionată”, iar asta nu era scris nicăieri în afară de comportamentul funcției care o citea.

Clienții voiau și un flux la reinstalare și unul la ștergere. Reparația evidentă sunt trei coloane. Este greșită dintr-un motiv care merită numit: fiecare dintre cele trei coloane ar putea cuprinde un flux al cărui propriu declanșator spune altceva. Un flux care rulează la ștergere, așezat în coloana care se declanșează la aprovizionare. Invariantul „declanșatorul spune când rulează” devine încălcabil în trei locuri în loc de niciunul, iar schema nu obiectează nicăieri.

Legătura a devenit așadar un tabel propriu, cu planul și momentul împreună drept cheie unică. Un flux pe moment și pe plan, impus de un index în loc de grijă.

Două amănunte din migrarea aceea merită păstrate.

Momentul este păstrat ca text cu o restricție de verificare care enumeră trei valori, și nu ca enumerarea de declanșatoare existentă. Enumerarea aceea are cinci valori, iar două dintre ele sunt declanșatoare pe care mecanismul acesta nu le alege niciodată: o execuție manuală este pornită de un om, iar una prin webhook de o cerere de intrare. O coloană enumerată ar primi bucuroasă oricare dintre ele, iar atunci ar exista un rând de legătură cu care nu se întâmplă nimic vreodată. O setare care în tăcere nu face nimic este mai rea decât o setare care lipsește.

Iar vechea coloană a fost înlăturată în aceeași migrare care i-a copiat conținutul. Păstrarea ei ca citire derivată comodă este tocmai al doilea adevăr pe care tabelul acesta vine să îl șteargă. Compilatorul a găsit fiecare cititor de îndată ce coloana a ieșit din schemă; erau șapte. Copierea rulează înaintea înlăturării, într-o singură tranzacție, deci nu există clipă în care configurația unui client să nu se afle nicăieri.

Copierea preia în plus declanșatorul propriu al fiecărui flux, în loc să presupună înțelesul vechii coloane. Rândurile al căror flux purta alt moment nu ar fi rulat niciodată cu vechiul cod; acum legătura pe care a făcut-o cineva face cu adevărat ce credea el.

Poștă prin aderare, în graf și nu în setări

Un flux poate scrie o notificare, iar în runda aceasta notificarea aceea poate pleca și prin poștă. S-a dovedit a fi o întrebare despre valori implicite și nu despre trimitere.

Categoriile de poștă ale platformei sunt o listă de excludere: ce nu este oprit anume este pornit. Este corect pentru categoriile care există deja, toate lucruri pe care un client trebuie să le afle. Nu este corect pentru o notificare al cărei text l-a scris cineva într-un editor de automatizare, fiindcă un flux care rulează la fiecare comandă ar scrie întregii echipe de fiecare dată, până când cineva ar găsi setarea.

În loc să întoarcem lista, valoarea implicită trăiește în graf: căsuța de pe nod este oprită, iar pornirea ei este un act deliberat al celui care a scris textul. Categoria există în continuare, ca poșta să poată fi oprită în locul obișnuit, exact ca celelalte. Două mecanisme, fiecare pentru ce se pricepe.

O execuție de probă nu trimite niciodată poștă. Scrie notificarea din panou, care este internă și se închide cu un clic, și nu trimite nimic în afară, din același motiv pentru care o execuție de probă nu atinge niciodată o mașină: poșta ajunge în cutiile unor oameni adevărați, iar un buton de test care surprinde pe cineva nu este un buton de test.

Încă un lucru mic din aceeași muncă. Se presupunea că partea de trimitere este de neatins de acolo de unde rulează fluxurile, și de aceea fusese amânat. Măsurat, era pe dos: funcția pe care voiam să o ocolim există tocmai fiindcă celălalt proces nu știe să compună poștă, iar ea cere procesului în care fluxurile rulează oricum să o compună. Ocolul era mai lung decât calea dreaptă, iar calea dreaptă are un salt HTTP, o semnătură și o cale de eșec mai puțin.

Ce am nota

  1. Un rând de audit explică o decizie. Nu îl reparați. Întrebarea despre ce este adevărat acum are alt răspuns și merită un loc propriu.
  2. O reparație în fundal repară doar viitorul. Dacă rândul care a stârnit lucrarea nu ar fi atins, acesta este un răspuns de proiectare și nu un amănunt.
  3. Citiți sursa, dacă nu cumva copia răspunde la ceva ce sursa nu poate.
  4. Un identificator ieșit dintr-o coloană JSON nu are cheie străină. Mărginiți interogarea la cont; faptul că valoarea a scris-o codul nostru nu este o graniță.
  5. Puneți deosebirea în cheie și nu în numele coloanei. Trei coloane sunt trei locuri în care să păstrezi lucrul greșit; o cheie nu este niciunul.
  6. Înlăturați vechea coloană în aceeași migrare. O citire derivată lăsată în urmă este al doilea adevăr pe care voiați să îl ștergeți.
  7. O listă de excludere este valoarea implicită greșită pentru un text scris de un client. Aderarea stă acolo unde se scrie textul.

Pentru claritate: toate acestea sunt construite și funcționează. Trimiterea în sine a fost dovedită cu un driver care doar jurnalizează și nu împotriva unui server de poștă adevărat, iar legătura cu planul nu a fost încă exersată printr-o comandă reală.

Din munca de platformă la Provibr. Cum se poartă așteptarea și ieșirea de eroare de la bun început se află în un fir pe care îl trageți și exact un declanșator, iar de ce un timp expirat nu este un buton de oprire se află în un timeout nu este un buton de oprire.

Începeți cu o gazdă și un agent

Creați un cont, instalați agentul cu o singură comandă și conectați primul dumneavoastră host Proxmox. Perioada de probă durează 14 zile și nu cere date de plată.