Blog ·
Migrarea care nu a rulat niciodată
Un migrator care raportează succes sărind peste un fișier este mai rău decât unul care se oprește brusc. Cum pierde ordonarea după marcă de timp migrări.
De Provibr
Un migrator de schemă care se oprește brusc înseamnă o după-amiază bună. Primiți o urmă de stivă, un număr de linie și o bază de date care este încă într-o stare pe care o înțelegeți.
După-amiaza rea este cea în care migratorul tipărește că schema este la zi, iese cu codul zero și greșește. Am avut după-amiaza aceea de mai multe ori, din mai multe cauze, iar cea mai gravă dintre ele s-a încheiat cu un cod de producție care citea o coloană inexistentă.
Iată ce am învățat, scris pentru oricine rulează migrări Postgres dintr-un director de fișiere, indiferent de unealta care a generat fișierele.
Decizia se ia pe baza unui singur număr
Majoritatea migratoarelor bazate pe fișiere țin un tabel cu ce s-a aplicat. Al nostru la fel: o schemă, un tabel, un rând pentru fiecare migrare, cu o amprentă și o marcă de timp a creării.
Greșeala este să presupuneți că tabelul acela este o mulțime pe care migratorul o consultă. Nu este. Citiți codul și găsiți o interogare care selectează exact un rând, cel mai nou după marca de timp, iar apoi o singură comparație pentru fiecare fișier de migrare: rulează-l dacă ultima marcă de timp aplicată este mai mică decât marca de timp a acestui fișier.
Trei lucruri decurg imediat de aici și niciunul nu se află în modelul mental al cuiva.
Amprenta nu joacă niciun rol în decizie. Ea se scrie atunci când o migrare rulează și nu mai este citită niciodată pentru a decide ceva. O amprentă din tabel care nu se mai potrivește cu niciun fișier de pe disc înseamnă de obicei că acel fișier a fost modificat după ce a fost aplicat, ceea ce este neîngrijit, dar inofensiv. Nu este unealta care observă ceva.
Contează numai cel mai nou rând. Nu mulțimea fișierelor aplicate, nu numărul lor. Un singur rând.
Ordonarea se face după marca de timp, nu după numele fișierului. Numerele din fața numelor dumneavoastră de fișiere sunt pentru oameni. Dacă unealta dumneavoastră consemnează o valoare de ordonare într-un manifest, acea valoare este adevărul, iar numărul este decor.
Puneți-le laolaltă și obțineți proprietatea care costă după-amiezi: o migrare a cărei marcă de timp nu este mai mare decât cea mai nouă marcă de timp aplicată nu va rula niciodată, pe acea bază de date, deloc. Nici la această punere în producție, nici la următoarea, nici după zece reîncercări. Nu apare nicio eroare, pentru că din punctul de vedere al uneltei nu este nimic în neregulă.
Cum ajungeți de fapt la una
Sună a ceva ce nu i s-ar putea întâmpla unei echipe atente. Iată cum ni s-a întâmplat nouă, de două ori, în feluri care nu aveau nimic de-a face cu neatenția.
Două ramuri, același număr. Doi oameni adaugă fiecare o migrare în paralel. Ambele unelte dau următorul număr liber, amândoi primesc același număr, amândoi scriu o marcă de timp. Ramura care se îmbină a doua este redenumită cu numărul următor, ceea ce este remediul evident și nu este remediul întreg: dacă marca ei de timp este în continuare mai mică decât cea deja aplicată, redenumirea a schimbat numele fișierului și nu decizia. Migrarea este sărită. Simptomul, în cazul nostru, a fost un tip enumerat căruia pur și simplu îi lipsea o valoare, pe o bază de date unde orice alt obiect era prezent și corect.
Mărci de timp scrise de mână care nu erau în ordine. Ale noastre sunt numere rotunde puse de mână și nu de un ceas adevărat, ceea ce le face lizibile și ușor de greșit. O migrare a primit o marcă anterioară altor două deja aplicate. Migratorul a raportat succes. În producție asta ar fi însemnat o eroare de tip pe fiecare rând de integrare, iar acesta nu este genul de lucru pe care vreți să îl aflați de la un client.
Ambele cazuri au o formă comună care merită numită. Evidența este cheiată pe o valoare de ordonare, nu pe identitate. Un registru cheiat pe identitate („am aplicat această migrare?”) nu poate sări nimic, în orice ordine ar sosi lucrurile. Un registru cheiat pe ordonare („până unde am ajuns?”) poate, iar acesta este un model pe care majoritatea migratoarelor îl împart, pentru că face din traseul obișnuit o singură citire de rând.
Contorul nu este verificarea
După primul incident am adăugat garda evidentă: după migrare, ne uităm la numărul de rânduri din tabelul de migrări și confirmăm că a crescut.
Garda aceea este necesară și nu este suficientă, lucru pe care l-am aflat privind-o cum trece în timp ce ceea ce ne trebuia lipsea. O rulare de migrare a urcat numărul de rânduri de la nouăzeci și opt la o sută patru. Șase migrări au intrat. Cea care ne interesa nu era printre ele. Numărul a crescut, totul a raportat succes, iar fiecare pagină autentificată din produs întorcea un 500 pe o coloană inexistentă.
Un număr nu vă spune nimic despre care migrări au rulat. Dacă vreți să știți dacă un obiect există, singura verificare sinceră este să întrebați baza de date dacă acel obiect există: enumerați tabelele, descrieți tabelul, selectați valorile enumerării. Noi comparăm lista de tabele a mediului pe care tocmai l-am migrat cu cea în care avem încredere.
Există o variantă a acestui eșec care nu are nimic de-a face cu mărcile de timp și care produce aceeași ieșire verde. Migratorul nostru călătorește în interiorul uneia dintre imaginile noastre de serviciu. Cineva a reconstruit numai celălalt serviciu, a rulat migrarea din imaginea învechită și a primit un voios „schema este la zi” de la un container care nu auzise niciodată de cele două migrări în cauză. Unealta nu mințea. Răspundea la o întrebare despre fișierele pe care le avea, iar ea avea fișiere vechi.
Totul sau nimic, într-o singură tranzacție
Un detaliu din implementarea migratorului nostru merită știut, pentru că schimbă felul în care vă refaceți: fiecare migrare în așteptare rulează într-o singură tranzacție, cu inserarea de evidență a fiecăreia înăuntrul ei.
Aceasta este o veste bună și o constrângere. Vestea bună este că un eșec la jumătatea unui lot de șapte vă lasă cu niciuna dintre cele șapte aplicate, în loc de trei și jumătate. Nu există o stare parțial migrată despre care să raționați, ceea ce este exact ce vă doriți în momentul în care sunteți cel mai puțin în stare să raționați.
Constrângerea este că o instrucțiune pe care Postgres nu o poate rula într-un bloc tranzacțional va doborî întreg lotul cu ea și că o migrare foarte lungă ține zăvoarele pe durata întregului lot, nu doar pe durata ei proprie. Niciuna dintre ele nu ne-a mușcat încă. Amândouă sunt genul de lucru pe care vreți să îl fi citit înainte să se întâmple.
Repararea uneia, fără a o înrăutăți pe următoarea
Să zicem că o găsiți: o migrare din directorul dumneavoastră pe care această bază de date nu a rulat-o niciodată. Instinctul este să reparați manifestul, să coborâți sau să ridicați marca de timp până când unealta o ia în seamă, și să rulați din nou.
Nu o coborâți, și fiți atenți când o ridicați.
Motivul este chiar regula care a provocat problema. Dacă coborâți marca de timp a unei migrări deja aplicate în altă parte, nu ați făcut nimic să ruleze; ați făcut un fișier a cărui marcă se află acum sub cota maximă atinsă pe bazele de date unde chiar a rulat, iar următoarea migrare pe care o scrieți cu o marcă între cele două va fi sărită acolo. Ați reparat un mediu și ați armat următorul eșec în altul.
Ce funcționează este să aplicați dumneavoastră codul SQL, într-o singură tranzacție, și apoi să vă asigurați că evidența reflectă realitatea. Migratorul nu poate face asta în locul dumneavoastră, pentru că din partea lui nu este nimic de făcut.
Ce facem acum
Cinci reguli, toate ieftine, toate scrise pentru că nu le-am urmat câte o dată pe fiecare.
- Punerea în producție și migrarea sunt o singură operațiune, nu două. Intervalul dintre ele este o fereastră în care un cod nou citește o schemă veche. Al nostru ne-a costat o pagină de administrare pentru fiecare administrator și o sarcină de facturare care a eșuat în tăcere o după-amiază întreagă, pe coloane care încă nu existau.
- Construiți totul înainte de a migra, nu doar serviciul pe care l-ați modificat. Migratorul călătorește într-o imagine, iar o imagine pe care nu ați reconstruit-o conține migrările pe care nu le-ați adăugat.
- Verificați valorile de ordonare, nu numele fișierelor, ori de câte ori doi oameni au lucrat în paralel. Redenumirea unui fișier nu schimbă ce compară unealta.
- Verificați obiectul, nu contorul. După o migrare care adaugă o coloană, cereți coloana. Asta ia o singură comandă și este singura verificare ce nu poate trece în gol.
- Nu coborâți niciodată o valoare de ordonare care a fost deja aplicată undeva. Arată ca un remediu local și este o mină la distanță.
Firul incomod care străbate toate acestea este că unealta s-a purtat corect de fiecare dată. Nu s-a oprit brusc niciodată, nu a corupt nimic niciodată, nu a mințit niciodată despre ce a făcut. A răspuns la o întrebare mai îngustă decât cea pe care credeam că o punem și a tipărit o propoziție adevărată despre acea întrebare mai îngustă.
Aceasta este clasa de eșec împotriva căreia merită să vă construiți obiceiuri, la migrări și peste tot altundeva. O unealtă care eșuează zgomotos vă învață ce face. O unealtă care reușește îngust nu vă învață nimic până în ziua în care contează.