Săriți la conținut

Blog ·

Două runde, un singur număr de câmp

Două ramuri au adăugat o variantă la același mesaj protobuf în aceeași zi și amândouă au ales același număr. Foarte puțin din asta eșuează zgomotos.

De Provibr

Două lucrări au rulat în paralel pe același depozit. Una a adăugat posibilitatea de a întreba un hipervizor ce s-a ales de o sarcină de fundal. Cealaltă a adăugat posibilitatea de a reinstala un container. Amândouă aveau nevoie de o variantă nouă în același mesaj protobuf, amândouă s-au uitat la fișier, amândouă au văzut că cel mai mare număr în uz era treizeci și trei și amândouă au scris treizeci și patru.

Niciuna nu a fost neglijentă. Fiecare a citit fișierul corect în momentul în care s-a ramificat. Coliziunea există numai într-o a treia stare pe care niciuna dintre ele nu a compilat-o vreodată: îmbinarea.

Unde apare coliziuneaCe se întâmplă
Cele două ramuri, separatNimic. Fiecare este corectă față de propriul ei punct de plecare
Îmbinarea, în fișierul de schemăUn conflict, iar apoi o eroare dură de compilare dacă se păstrează amândouă
Îmbinarea, în codul generat aflat în depozitUn conflict, iar apoi absolut nimic dacă se păstrează amândouă
Verificatorul de compatibilitateVerde în ambele ramuri, pentru că el compară fiecare ramură cu propria ei referință de bază
Un agent și un gateway puse în producție care nu sunt de acordNu s-a ajuns niciodată aici: o comandă care ar sosi goală, raportată la mai multe straturi distanță de cauză

Ce transportă de fapt firul

Numele unui câmp protobuf este o comoditate pentru oameni. Ce trece pe conexiune este numărul, deplasat cu trei biți la stânga, cu un indicator de tip în biții de jos, așa că un câmp treizeci și patru cu o sarcină utilă prefixată cu lungimea se scrie ca secvența de octeți pentru două sute șaptezeci și patru. Redenumiți un câmp și nu se schimbă nimic pe firul binar. Schimbați-i numărul și fiecare cititor pus în producție decodează acum altceva cu aceeași cheie.

De aceea un număr, odată ce a ieșit într-o versiune publicată, este înghețat. Nu este o regulă de stil. Este singurul identificator pe care cele două capete îl împart.

Ce spun uneltele și ce nu spun

Git a observat. Conflictul a apărut în fișierul de schemă și în codul TypeScript generat aflat în depozit, de ambele dăți ca adăugări suprapuse în același loc. Rezolvarea ispititoare este cea care pare evident corectă pentru două funcționalități independente: să le păstrați pe amândouă.

În fișierul de schemă asta este prinsă. Compilarea lui produce un refuz sec, care numește mesajul și câmpul ce deține deja numărul, ceea ce a fost măsurat și nu presupus: un mesaj de test cu două variante, amândouă pe treizeci și patru, este respins cu o linie și o coloană și cu o sugestie pentru următorul număr liber.

Codul TypeScript generat este altă poveste, iar el este motivul pentru care merită notate toate acestea. Acel fișier se află în depozit, pentru că munca de zi cu zi nu ar trebui să ceară instalarea lanțului de unelte pentru schemă. Prețul acelei alegeri este că el ia parte la îmbinări ca orice alt fișier sursă și că este cod obișnuit. O ramură duplicată într-o instrucțiune switch este TypeScript valid. Compilarea unui fișier mic cu două etichete de caz identice, amândouă purtând verificarea de etichetă pentru câmpul treizeci și patru, nu produce niciun diagnostic și se încheie curat. La a doua ramură pur și simplu nu se ajunge niciodată.

Așa că un fișier generat și îmbinat manual vă dă un codificator care scrie aceeași etichetă pentru două mesaje diferite și un decodificator care o rezolvă întotdeauna către cel listat primul. Nimic nu eșuează la compilare, nimic nu eșuează la pornire și nimic nu eșuează la primul mesaj. Una dintre cele două funcționalități este pur și simplu greșită, într-un fel care iese la iveală numai pe mașina de la celălalt capăt al conexiunii.

De ce verificarea de compatibilitate era verde

Schema este păzită de un verificator de modificări incompatibile, iar el și-a făcut treaba. El compară o schemă propusă cu o referință de bază. Adăugarea unei variante noi la o alegere existentă nu este o modificare incompatibilă, iar în niciuna dintre ramuri nu a fost, pentru că referința de bază a fiecărei ramuri chiar nu avea treizeci și patru în uz.

Aceasta este forma generală a problemei și merită enunțată separat de protobuf: o verificare ce compară o ramură cu propriul ei punct de plecare nu poate vedea un conflict care există numai între două ramuri. Aceeași clasă de defect apare oriunde lucrul paralel alege o valoare dintr-un spațiu ordonat și comun. Migrările din baza de date sunt alese aici după marca de timp din jurnalul lor, iar acele mărci de timp sunt numere rotunde scrise de mână și nu citite de pe un ceas adevărat, așa că două runde care scriu fiecare câte una și-o aleg singure; o migrare a cărei marcă de timp ajunge sub una care a fost deja aplicată este sărită definitiv, iar migratorul raportează în continuare că schema este la zi. Alt mecanism, aceeași tăcere, aceeași cauză.

De ce tăcerea este partea scumpă

Dacă amândouă numerele ar fi supraviețuit până într-o versiune publicată, eșecul ar fi fost la distanță și năucitor. Protobuf aruncă un câmp de alegere pe care nu îl recunoaște, așa că un agent construit dintr-o ramură care primește o comandă din cealaltă nu primește o eroare. Primește o comandă fără nimic în ea și raportează ceva de genul „mesajul nu purta nicio instrucțiune”. Cauza se află la mai multe straturi distanță de propoziție, pe o mașină care aparține altcuiva, iar cel care o citește nu are niciun motiv să bănuiască un număr.

Platforma refuză deja să trimită instrucțiuni către agenții care nu au anunțat capabilitatea potrivită, tocmai pentru că acest mod de eșec este atât de nefolositor: un agent care este doar prea vechi produce măcar un refuz scris de platformă, care numește funcționalitatea și versiunea. Garda aceea acoperă decalajul de versiuni. Nu acoperă două numere care înseamnă două lucruri, pentru că, din partea platformei, nimic nu este depășit.

Regula care a ieșit din asta

Un număr de câmp care a ieșit într-o versiune publicată este înghețat, așa că runda care nu a fost încă livrată este cea care se mută. Treizeci și patru ieșise deja într-o versiune de agent drept varianta de stare a sarcinii, așa că la îmbinare varianta de reinstalare a containerului a devenit treizeci și cinci, iar codul generat a fost regenerat din schemă în loc să fie îmbinat manual. Regenerarea este jumătatea importantă: o îmbinare manuală a unei ieșiri generate este un codificator scris de mână pe care nimeni nu l-a recenzat ca atare.

Instrucțiunea practică pentru cine îl adaugă pe următorul este scurtă. Citiți cel mai mare număr din linia principală în momentul în care faceți commit, nu în momentul în care v-ați ramificat. Iar la fiecare îmbinare care atinge schema asta se verifică deliberat, de către un om, pentru că nicio unealtă din lanț nu se uită la ea din acel unghi.

Rezervările și de ce merită respectate

Aceeași schemă arată cum arată disciplina atunci când funcționează. Trei numere stau rezervate pentru integrări care au fost planificate și niciodată construite, așa că nimeni nu le poate refolosi în tăcere. Alte două au fost ținute pentru două panouri de control anume, iar amândouă au fost până la urmă umplute de rundele cărora le fuseseră promise. Un al treilea panou, care s-a nimerit să fie gata mai devreme, nu a luat niciunul dintre ele; a luat în schimb următorul număr liber, mai încolo.

Acesta este întreg rostul unei rezervări. A mânca una pentru că s-a nimerit să fiți gata primul este exact felul în care o schemă de numerotare încetează să mai însemne ceva, iar comentariul din fișier o spune chiar în acești termeni.

Umplerea unui număr rezervat așa cum era intenționat este totuși raportată de verificatorul de compatibilitate, ca eliminare a unei rezervări. Acel raport este așteptat și corect de acceptat: niciun câmp nu a purtat vreodată acel număr, așa că niciun cititor pus în producție nu poate avea o părere despre el. Merită știut dinainte, pentru că prima dată pare alarmant, iar ispita este să alegeți alt număr ca să faceți unealta să tacă, ceea ce aruncă la gunoi chiar rezervarea pe care o respectați.

Ce am nota

  1. Tratați un număr livrat drept imuabil și tratați „cel mai mare număr pe care îl văd” drept o afirmație despre ramura dumneavoastră și nu despre proiect.
  2. Citiți spațiul comun în momentul commitului, nu în momentul ramificării. Acela este singurul moment în care valoarea este cu adevărat revendicată.
  3. Regenerați codul generat, nu îl îmbinați niciodată. Un conflict într-o ieșire generată este un semnal să rulați generatorul, nu o invitație de a edita.
  4. Aflați care dintre verificările dumneavoastră sunt raportate la o referință de bază. Ele nu pot vedea coliziuni între ramuri surori, iar acel gol are nevoie de un om la îmbinare.
  5. Rezervați numerele promise și pe cele planificate, cu un comentariu care spune cui aparțin, și acceptați zgomotul pe care îl produce umplerea corectă a uneia.
  6. Căutați aceeași formă și în altă parte. Orice valoare atribuită manual într-un spațiu ordonat comun, inclusiv migrările, are acest eșec și de obicei îl are în tăcere.

Pentru claritate: nimic nu a fost livrat cu coliziunea în el. A fost prinsă la îmbinare, varianta de mai târziu s-a mutat, codul generat a fost reconstruit din schemă, iar amândouă funcționalitățile au ieșit ca versiuni de agent separate, la câteva ore distanță. Ce a făcut-o de prins a fost că cineva a citit conflictul în loc să îl rezolve, ceea ce nu este un proces care se poate automatiza, dar este unul care se poate nota.

Din lucrul la platformă de la Provibr. Versiunea din baza de date a aceluiași eșec se află în migrarea care nu a rulat niciodată, iar de ce o actualizare semnată de agent se refuză singură în loc să aibă încredere în expeditor se află în cum se refuză singură o actualizare semnată.

Î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ă.