Naar de inhoud

Blog ·

Een join in plaats van een reconcile

Een automatiseringsstap bevroor op de stand die hij zag. De reparatie was geen worker: die rij verklaart een beslissing, en repareren vernietigt de uitleg.

Door Provibr

Een automatiseringsflow kan een commando naar een machine sturen en erop wachten. Loopt de wachttijd af, dan faalt de stap en neemt de uitvoering haar foutuitgang. Dat is juist: de flow vroeg om te wachten tot het werk klaar was, en het is niet klaar, dus niemand weet of de volgende knoop zo meteen handelt op een machine die nog aan het afsluiten is.

De stap legt vast wat hij op dat moment zag. Gemeten in een eerdere ronde tegen een echte agent legde hij sent vast, en zestig milliseconden later werd het commando een timeout. De stap zei nog steeds sent, een dag later, voor altijd.

De voor de hand liggende lezing is dat de rij verouderd is en gerepareerd moet worden. Die lezing is fout, en uitzoeken waarom kostte het meeste denkwerk van deze ronde.

De rij is een uitleg, geen statusveld

De bevroren waarde is geen kopie van de commandostand die uit de pas is gaan lopen. Het is het antwoord op een andere vraag: wat wist de flow toen hij besloot de foutuitgang te nemen.

Schrijf je er timeout overheen, dan zegt de rij nu dat de flow op een timeout reageerde. Dat deed hij niet. Hij reageerde op een aflopende wachttijd terwijl het commando nog onderweg was, een andere gebeurtenis met een andere oorzaak en een andere reparatie. Een maand later zou wie die uitvoering leest de verkeerde conclusie trekken uit een rij die behulpzaam gecorrigeerd was.

De bevroren waarde blijft dus bevroren. Wat werkelijk mis was, was de commentaarregel erboven, die beloofde dat je aan deze rij kon zien of het commando uiteindelijk slaagde. Dat kon je niet, en een belofte in een commentaarregel heeft een houdbaarheid die loopt tot het moment dat iemand erop vertrouwt.

De echte vraag, "is het alsnog goed gekomen", wordt beantwoord door de huidige stand van het commando te lezen op het moment dat iemand kijkt. De uitvoeringspagina toont ze allebei, naast elkaar: wat de flow toen zag, en wat er nu waar is.

Waar het vandaan komtWat het beantwoordt
ToenDe auditrij, bevroren op het moment van beslissenWaarom de uitvoering de uitgang nam die hij nam
NuDe commandotabel, gelezen bij het openen van de paginaOf het werk uiteindelijk klaar kwam
Rij wegGeen van beide; als eigen stand getoondHet commando staat niet meer op de rol

Die laatste regel telt. Is de commandorij opgeruimd, dan valt de weergave niet terug op het herhalen van de bevroren waarde. "Staat niet meer op de rol" en "zegt nog steeds sent" zijn twee verschillende feiten, en een pagina die het tweede rendert terwijl het eerste waar is liegt met een uitgestreken gezicht.

Drie redenen waarom een worker het van een join verliest

Het alternatief was een achtergrondworker die de eindstand terugschrijft in de auditrij zodra het commando beslecht is. Wij hebben elders workers met precies die vorm, dus dit was een echte keuze en geen stroman.

Een worker overschrijft een uitleg. Zie hierboven. De uitweg is een tweede veld ernaast schrijven, en dat is wel eerlijk, maar het is een tweede waarheid die op precies één plek gelezen zou worden: de plek die nu de join doet.

Een worker repareert alleen wat er ná hem gebeurt. De rij waar dit hele onderzoek mee begon bestaat al. Een worker die vandaag wordt uitgerold zou hem nooit raken, en evenmin een uitvoering uit de twee rondes daarvoor, want hun commando's zijn allang beslecht en geen toekomstige gebeurtenis komt er nog langs. Een join toont ze allemaal vanaf het moment dat hij live gaat, inclusief degene waar wij naar op zoek waren.

Een worker brengt een heel apparaat mee. Rijen op de achtergrond bijwerken vraagt om een frequentie, een claim zodat twee instanties niet botsen, een driftwacht, een retentieantwoord en een eigen faalpad, en elk daarvan kan om drie uur 's nachts fout staan. Een handvol identificatoren op primaire sleutel opzoeken als iemand een pagina opent vraagt niets van dat alles.

De algemene regel eronder: lees liever de bron dan dat je hem kopieert, tenzij de kopie een vraag beantwoordt die de bron niet kan. Hier beantwoordt de kopie een andere vraag en wil je ze allebei, en dat is precies het geval waarin je ze uit elkaar houdt.

De identificator kwam uit een JSON-kolom, dus de query is begrensd

Eén detail in die join is geen optimalisatie en geen netheid.

De commando-identificator staat in een JSON-blok op de auditrij. JSON-kolommen hebben geen refererende sleutels, dus niets garandeert dat de identificator daarbinnen bij dit account hoort. Vandaag hoort hij dat wel, omdat onze eigen code hem daar neerzette. Dat is geen grens, dat is een gewoonte.

De opzoeking is daarom naast de identificator ook op de organisatie begrensd. Zonder die grens zou een met de hand bewerkte waarde in dat blok de stand van andermans commando uitlezen. Klein lek, makkelijk te missen, en van de soort die alleen gevonden wordt door wie ernaar zocht.

Eén flow per moment, met het moment ín de sleutel

Een hostingplan kon één automatiseringsflow koppelen, in één kolom. Die kolom betekende "draai dit als een machine wordt uitgerold", en dat stond nergens opgeschreven behalve in het gedrag van de functie die hem las.

Klanten wilden ook een flow bij herinstallatie, en een bij verwijdering. De voor de hand liggende reparatie is drie kolommen. Die is fout om een reden die het benoemen waard is: in elk van die drie kolommen zou een flow kunnen staan waarvan de eigen trigger iets anders zegt. Een flow die bij verwijdering draait, in de kolom die bij uitrol afgaat. De invariant "de trigger zegt wanneer hij draait" wordt op drie plekken schendbaar in plaats van op nul, en het schema protesteert nergens.

De koppeling werd dus een eigen tabel, met het plan en het moment samen als unieke sleutel. Eén flow per moment per plan, afgedwongen door een index in plaats van door zorgvuldigheid.

Twee details in die migratie zijn het bewaren waard.

Het moment staat als tekst met een controlebeperking die drie waarden opsomt, en niet als de bestaande triggeropsomming. Die opsomming heeft vijf waarden, en twee daarvan zijn triggers die dit mechanisme nooit kiest: een handmatige uitvoering wordt door een mens gestart en een webhookuitvoering door een binnenkomend verzoek. Een opsommingskolom zou allebei vrolijk aannemen, en dan bestaat er een koppelrij waar nooit iets mee gebeurt. Een instelling die stil niets doet is erger dan een instelling die ontbreekt.

En de oude kolom is verwijderd in dezelfde migratie die zijn inhoud overzette. Hem als handige afgeleide lezing laten staan is precies de tweede waarheid die deze tabel moet opheffen. Elke lezer werd door de compiler gevonden zodra de kolom uit het schema was; het waren er zeven. Het overzetten draait vóór het verwijderen, in één transactie, dus er is geen moment waarop de instelling van een klant nergens staat.

Het overzetten neemt bovendien de eigen trigger van elke flow over in plaats van de betekenis van de oude kolom aan te nemen. Rijen waarvan de flow een ander moment droeg zouden onder de oude code nooit gedraaid hebben; nu doet de koppeling die iemand maakte werkelijk wat hij dacht.

Post die opt-in is, in de graaf en niet in de instellingen

Een flow kan een melding schrijven, en deze ronde mag die melding ook per post uitgaan. Dat bleek een vraag over standaardwaarden en niet over verzenden.

De mailcategorieën van het platform zijn een weigerlijst: wat niet uitdrukkelijk uitstaat, staat aan. Dat klopt voor de categorieën die er al zijn, allemaal dingen die een klant moet horen. Het klopt niet voor een melding waarvan iemand de tekst in een automatiseringseditor schreef, want een flow die bij elke bestelling draait zou het hele team elke keer mailen tot iemand de instelling vond.

In plaats van de lijst om te keren staat de standaardwaarde in de graaf: het vinkje op de knoop staat uit, en hem aanzetten is een bewuste handeling van degene die de tekst schreef. De categorie bestaat nog steeds, zodat post op de gewone plek uitgezet kan worden, precies als bij de andere. Twee mechanismen, elk voor waar het goed in is.

Een proefrun verstuurt nooit post. Hij schrijft de paneelmelding, die intern is en weg te klikken, en hij stuurt niets naar buiten, om dezelfde reden dat een proefrun nooit een machine aanraakt: post komt in de postbus van echte mensen terecht, en een testknop die iemand verrast is geen testknop.

Nog één klein ding uit hetzelfde werk. De verzendkant zou onbereikbaar zijn vanaf de plek waar flows draaien, en daarom was dit uitgesteld. Nagemeten was het andersom: de functie waar wij omheen wilden bestaat juist omdat het andere proces geen post kan renderen, en zij vraagt het proces waarin flows toch al draaien om dat renderen te doen. De omweg was langer dan de rechte weg, en de rechte weg is één HTTP-sprong, één handtekening en één faalpad minder.

Wat wij zouden opschrijven

  1. Een auditrij verklaart een beslissing. Repareer hem niet. De vraag "wat is er nu waar" heeft een ander antwoord en verdient een eigen plek.
  2. Een reparatie op de achtergrond repareert alleen de toekomst. Zou de rij die het werk uitlokte niet geraakt worden, dan is dat een ontwerpantwoord en geen detail.
  3. Lees de bron, tenzij de kopie iets beantwoordt wat de bron niet kan.
  4. Een identificator uit een JSON-kolom heeft geen refererende sleutel. Begrens de query op het account; dat onze eigen code de waarde schreef is geen grens.
  5. Zet het onderscheid in de sleutel en niet in de kolomnaam. Drie kolommen zijn drie plekken om het verkeerde te bewaren; één sleutel is er nul.
  6. Verwijder de oude kolom in dezelfde migratie. Een achtergelaten afgeleide lezing is de tweede waarheid die je aan het opheffen was.
  7. Een weigerlijst is de verkeerde standaardwaarde voor tekst die een klant schreef. Zet de opt-in daar waar de tekst geschreven wordt.

Voor de duidelijkheid: dit alles is gebouwd en draait. Het verzenden zelf is bewezen met een logstuurprogramma en niet tegen een echte mailserver, en de plankoppeling is nog niet via een echte bestelling uitgeoefend.

Uit het platformwerk aan Provibr. Hoe de wachttijd en de foutuitgang zich om te beginnen gedragen staat in een draad die je sleept, en precies één trigger, en waarom een timeout geen stopknop is staat in een timeout is geen stopknop.

Begin met één host en één agent

Maak een account aan, installeer de agent met één commando en koppel je eerste Proxmox-host. De proefperiode loopt 14 dagen en vraagt geen betaalgegevens.