Blog ·
Projecteren, nooit vervangen
Eén voortgangsscherm over migraties, backups, restores en herinstallaties, zonder een tweede administratie te worden. Twee storingen waren volkomen stil.
Door Provibr
Een hostingpaneel verzamelt langlopend werk. Een migratie tussen twee beheerpanelen, een backup, een restore naar een nieuwe machine, een container die vervangen wordt. Elk kreeg zijn eigen tabel, zijn eigen statuswoorden en zijn eigen scherm, en dat werkt tot iemand de voor de hand liggende vraag stelt: wat loopt er nu op mijn account, en staat er iets van vast.
Het verleidelijke antwoord is een takensysteem. Eén tabel, één statusopsomming, al het andere schrijft erin. Het verleidelijke antwoord is ook hoe je twee bronnen van waarheid krijgt voor hetzelfde feit, en een jaar later een supportgesprek waarin de taak zegt dat het mislukte en de backuptabel zegt dat het archief op de storage staat.
Het operationsscherm is daarom een projectie. Het leest de bestaande rijen en schrijft er een samenvatting bovenop. Er gaat nooit iets de andere kant op: geen enkele functie in de spiegelmodule raakt de backupruns, de restoreruns, de migratieruns of de herinstallaties aan. Die tabellen blijven de waarheid, en de samenvatting mag verouderd, fout of afwezig zijn zonder dat er verderop iets breekt.
Eén spiegel per tabel, en een test die het schema leest
Vier tabellen liggen op deze naad, en elk heeft precies één functie die hem projecteert.
| Brontabel | Statuskolom | Soort operation |
|---|---|---|
backup_runs | status | backup.run |
backup_restore_runs | status | backup.restore |
migration_runs | status | migration.run |
container_reinstall_intents | state | container.reinstall |
De laatste regel is de reden dat de kolomnamen als tekst opgeschreven staan en niet aangenomen worden. Die tabel noemt de kolom state, en een spiegel die status leest krijgt niets terug en valt zonder klagen door naar de mislukkingstak.
Die tabel is geen documentatie. Een test leidt uit het databaseschema af welke van die tabellen een statuskolom hebben, eist per tabel een regel hier, eist dat de genoemde functie ook werkelijk bestaat, en eist, en dat is het deel dat het echte werk doet, dat élk bestand dat zo'n statuskolom schrijft ook zijn spiegel noemt. Een vijfde tabel die zonder projectie op de naad komt is daarmee een rode test in plaats van een lade die stil stopt met bewegen.
Zo streng moet het omdat de schrijvers verspreid staan: een backuprun verandert van status op zes plekken in één bestand, waarvan drie op het pad dat de antwoorden verwerkt van de verzoening die 's nachts draait. Elke schrijver zelf de projectie laten aanroepen betekent dat de zesde het vergeet, en vergeten is onzichtbaar. De rij klopt. Alleen het scherm werkt nooit bij.
De sink die twee keer bestond
Het spiegelen woont in het pakket dat de databaseschrijvingen bezit, want zowel een server-action in de webapp als een gatewayworker roept dezelfde functies aan. Dat pakket kán geen live gebeurtenis publiceren: het hangt aan de database en aan gedeelde code en aan verder niets, en de berichtenbus is aan elke kant iets anders.
Het biedt dus een sink aan. Elke applicatie registreert er één, en het pakket weet niet wie hem invulde. Doet niemand dat, dan gaat de databaseschrijving gewoon door en vertrekt er geen live gebeurtenis, en werkt de lade bij de volgende paginalading bij. Dat is eerlijke degradatie, en de functie gooit met opzet nooit, want een ontbrekende registratie mag geen backup omvertrekken.
De sink was een modulevariabele, en dat was fout op een manier die helemaal geen fout opleverde. Next draait zijn instrumentatiebestand als een eigen bundel, los van de route-workers. De variabele bestond daardoor twee keer: de registratie landde in het ene exemplaar en de verzendende functie las het andere. De rijen klopten, de statussen liepen door, de lade stond stil, en er stond geen enkele regel in welk log dan ook.
Dit is niet gevonden door erover na te denken. Het is gevonden door een luisteraar op de berichtenbus te zetten en te tellen: er kwam één gebeurtenis aan waar er drie hoorden te zijn. De reparatie is een slot op globalThis achter een Symbol.for, dat een bundelgrens binnen één proces overleeft, dezelfde vorm en dezelfde reden als de gedeelde databaseverbinding en de gedeelde Redis-verbinding.
Twee lessen die meer waard zijn dan de fout. Een storing zonder foutregel vraagt om een instrument buiten de code die je verdenkt. En "deze module heeft één exemplaar" is een aanname over een bundelaar, geen eigenschap van de taal.
Dezelfde ronde vond een tweede stille. Het afronden en het blokkeren van een operation lieten allebei de stagekolom met rust, dus een backup die perfect slaagde bleef voor altijd op "archiveren" staan. Er mislukte niets; de laatste stap kwam alleen nooit aan.
Annuleren is een verzoek, en één UPDATE is het bewijs
Bij annuleren is een projectie het meest geneigd te liegen. De lade kent de operation, de operation heeft een annuleerknop, en de goedkoopste uitvoering zet een vlag en sluit het paneel.
Elke soort verklaart daarom of annuleren überhaupt betekenis heeft. Een lopende vzdump kunnen wij niet afbreken: het commando is bij de agent, de taak bij de hypervisor, en niemand weet wat een half archief op de storage van de klant voorstelt. Een restore is hetzelfde, met een reden erbovenop: de restorerij houdt een lease op de doel-vmid zolang hij openstaat, en hem vroeg sluiten geeft die vmid vrij terwijl er misschien net een gast op landt. Allebei zijn ze niet-ondersteund verklaard, en er is geen weg die dat omzeilt.
Waar annuleren wél kan, beslist de rij nog steeds. Een migratie kan geannuleerd worden zolang hij gepland is en niet meer zodra er een hulpprogramma servers staat te schrijven op de host van iemand anders. Een containerherinstallatie kan tot en met de voorbeschouwing, en niet meer zodra de agent begonnen is met vervangen.
Als het lukt, is het bewijs dat er niets gebeurd is één voorwaardelijke bijwerking: zet het commando in de wachtrij op geannuleerd wáár zijn status nog in afwachting is. Is het al verstuurd, dan raakt dat statement nul rijen en blijft het verzoek eerlijk op "gevraagd" staan. Kijken en dan schrijven zijn twee statements en dus twee kansen om ertussen te komen.
En dan doet de zin ertoe. De gebeurtenis zegt dat het commando uit de wachtrij gehaald is voordat de agent hem kreeg, en niet dat het geannuleerd is. Er is niets onderbroken, want er was niets begonnen. Die rij gaat in het algemene gebeurtenissenlog en niet in de operationvoortgang, want voortgang wordt na negentig dagen opgeruimd en "een mens greep in de infrastructuur in" is geen voortgang.
Vier blokkades, en geen ervan is een mislukking
Vier situaties zetten een operation op aandacht nodig in plaats van mislukt.
Een vzdump meldde OK en het archief staat niet in de index. Een backuppoging liep in een timeout en wij weten niet of het archief bestaat. Een restore is niet terug te lezen of de ontstane gast kon niet geadministreerd worden. Een containerherinstallatie strandde nadat de oude container al weg was.
Een van deze mislukt noemen zou een leugen met een gevolg zijn. Mislukt nodigt de klant uit het nog eens te doen, en een backup nog eens draaien is opnieuw tientallen gigabytes op iemands storage voor een archief dat er misschien al staat. Een herinstallatie nog eens draaien is een tweede container. De verzoeningslus blijft kijken tot de index uitsluitsel geeft, en de operation staat ondertussen zichtbaar vast, wat precies de juiste hoeveelheid alarm is.
De blokkade draagt ook wie hem bezit. Drie hiervan zijn van ons; een commando dat werkelijk aan de kant van de klant faalde zegt dat.
Een dry-run eindigt bij plannen, en dat is geen gat
Een migratie in dry-run heeft vijf stappen; een echte heeft er acht, waarvan de drie extra het schrijven en natellen zijn. De stagelijst kent alle zes stages, dus een geslaagde dry-run toont zijn laatste twee als niet gedaan.
Dat ziet eruit als een fout in een voortgangsbalk en het is de waarheid: een dry-run schrijft niet en telt niet na. De alternatieven waren een tweede soort, wat negen catalogusregels in negen talen verdubbelt voor hetzelfde werk, of een stagelijst die per rij verschilt, waardoor de lade en de detailpagina het oneens worden over hoeveel stappen er zijn. De waarheid tonen was goedkoper dan allebei.
Wat wij zouden opschrijven
- Een projectie projecteert. Kan jouw samenvattingslaag terugschrijven in de bron, dan is het een tweede administratie en heb je er nu twee.
- Zet de naad in een tabel die een test kan lezen, afgeleid uit het schema, en laat hem controleren dat élke schrijver van een statuskolom zijn spiegel noemt. Een vergeten aanroep geeft geen fout.
- Neem niet aan dat een module één exemplaar heeft. Een raamwerk dat een deel van je code als aparte bundel draait geeft je er twee, en het symptoom is stilte.
- Meet buiten de verdachte. Een busluisteraar vond in minuten wat lezen helemaal niet vond.
- Zeg wat er werkelijk gebeurde. "Uit de wachtrij gehaald voordat de agent hem kreeg" is langer dan "geannuleerd" en het is de enige versie die klopt.
- Houd mislukt voor mislukking. Onbekende uitkomsten hebben een eigen stand nodig, anders herhalen mensen dure handelingen die misschien al geslaagd zijn.
Voor de duidelijkheid over wat dit is: de hier beschreven soorten zitten achter uitrolschakelaars die standaard uit staan, dus migraties en klantbackups zijn vandaag niet te koop. Meerdere dingen zijn onbewezen. De live voortgangsgebeurtenis is alleen voor migraties gemeten. De gatewaykant van de sinkregistratie is niet met een werkelijk draaiende gateway beproefd. De agentketen is met SQL nagebootst. En een containerherinstallatie annuleren vanaf het operationsscherm is nooit in een browser gedreven.
Uit het platformwerk aan Provibr. Waarom een voortgangsbalk weigert een eindtijd te schatten staat in een voortgangsbalk die weigert te gokken, en waarom een migratie die stil oversloeg erger is dan een die faalde staat in de migratie die nooit gedraaid heeft.