Blog ·
Om drie uur 's nachts bijgewerkt, met opzet
Auto-update staat standaard aan om 03:00. De besluiten die telden: welke situatie een slot verbruikt, hoe ver een inhaalslag reikt, en de DST-nacht.
Door Provibr
De agent die in het eigen netwerk van een klant draait krijgt om de paar dagen een nieuwe build. Tot deze ronde was bijwerken een knop waar iemand op moest drukken, dus in de praktijk gebeurde het wanneer iemand toevallig keek. De wens was één zin: iedereen kan bij zijn agent een updateschema instellen, meerdere tijdstippen per dag, standaard aan om drie uur 's nachts.
De zin is makkelijk. Wat hier een ronde van maakte is dat een geplande herstart van infrastructuur veel manieren heeft om stil fout te zijn, en dat ze er van buitenaf bijna allemaal goed uitzien.
De standaard is een kolomdefault, en dat is de hele wens
"Standaard aan voor iedereen" heeft twee lezingen. In applicatiecode geldt het voor agents die vanaf nu aangemaakt worden, en dat sluit precies de klanten uit die er al een hebben. Als kolomdefault in de migratie geldt het voor élke bestaande rij in één opdracht.
Het is dus een kolomdefault: een boolean die op waar begint en een JSON-array die begint met één regel 03:00. Gemeten nadat de migratie op de ontwikkeldatabase draaide: elke bestaande agent aan, elke agent om drie uur, en een verse agent die geen van beide waarden meekreeg kreeg dezelfde stand. Die laatste controle is de controle die bewijst dat de default het werk doet en niet een seedscript.
De tijdstippen zijn wandtijd in de tijdzone van het platform, niet UTC. Er bestaat geen tijdzone per organisatie en het paneel rendert alles in die ene zone, dus een schema in UTC zou in de zomer een uur verschuiven en een klant die drie uur intypte zou om vijf uur onderbroken worden.
Twee lagen op één reguliere expressie
Een tijdstip is HH:MM op een raster van vijf minuten, en dat patroon bestaat twee keer: één keer in de schemavalidator die een formulierveld met een leesbare melding weigert, en één keer als databaseconstraint die alles weigert wat nooit langs dat formulier kwam.
Een controlebeperking mag geen subquery bevatten, dus "elk element van deze array is een geldig tijdstip" kan geen bestaanstoets over de uitgepakte array zijn. Wat wél kan is een jsonpath-predicaat dat precies de elementen selecteert die fout zijn, verpakt in een toets dat die selectie leeg is.
De eerste versie van dat predicaat was subtiel nutteloos, en hij zag er compleet uit:
$[*] ? ( !(@ like_regex "^([01][0-9]|2[0-3]):(00|05|10|15|20|25|30|35|40|45|50|55)$") )Een match van een reguliere expressie tegen iets dat geen string is, is in jsonpath niet onwaar. Hij is Unknown. En de ontkenning van Unknown is óók Unknown, en een filter houdt alleen elementen over waarvoor het predicaat werkelijk waar is. De selectie "foute elementen" kwam dus leeg terug voor een array met getallen, en de constraint accepteerde hem. Gemeten tegen PostgreSQL 17.10 gingen [3], [null] en [{"a":1}] er alle drie doorheen.
De reparatie is één clausule ervoor: elementen waarvan het type geen string is zijn per definitie fout. Alle drie worden nu geweigerd. Dit is precies de klasse fout die ons rondes blijft kosten: een bewaking die niets afvangt en er compleet uitziet, en geen enkele test die faalt, want er was niets om tegen te falen.
Vijf stappen, en de volgorde is de ronde
Elke minuut vraagt een worker wie er een schema heeft, geeft de rijen aan een pure planner die beslist welk slot aan de beurt is, en loopt dan vijf stappen per agent. De claim is stap vier en niet stap één, en elke stap ervóór staat er om een eigen reden.
| Stap | Als het antwoord nee is |
|---|---|
| Is er een nieuwere build? | Het slot wordt tóch geclaimd, gemarkeerd als niets te doen |
| Is de agent verbonden? | Er wordt niets geclaimd, en de volgende minuut probeert het opnieuw |
| Loopt er al een update? | Er wordt niets geclaimd; de eerste maakt het slot alsnog waar |
| Claim het slot | Een unieke index beslist; de verliezer stuurt niets |
| Stuur het commando | Dezelfde functie als de knop in het paneel |
De eerste twee regels zijn bewuste tegenpolen, en die asymmetrie is het interessantste besluit van de ronde.
Niets te doen verbruikt het slot, want het antwoord kan binnen één slot niet meer veranderen: het releasemanifest op schijf is voor het hele venster hetzelfde, dus elke minuut opnieuw beoordelen kost queries en levert niets op. En het geeft de klant iets om te zien. Een agentpagina die zegt "03:00, al bij" is te onderscheiden van een schema dat niet draait. Een lege lijst is dat niet.
Offline verbruikt niets, want een slot dat geclaimd zou zijn terwijl de host net herstart is voorgoed verbruikt, en dan mist die klant stil een nacht. De volgende minuut probeert het opnieuw, tot het inhaalvenster dicht is; daarna pakt de agent gewoon zijn volgende slot.
Vijftien minuten, en niet zes uur
Backups hebben in dit platform een inhaalvenster van zes uur. Dit is er een van vijftien minuten, en het verschil is wat er misgaat als je ruim bent.
Een backup die om negen uur 's ochtends alsnog gemaakt wordt is een backup. Een agent die om negen uur 's ochtends alsnog herstart is een onderbreking midden op de werkdag, en drie uur 's nachts is precies wat de klant koos om dat te vermijden. Vijftien minuten dekt waar dit venster werkelijk voor is, een gateway die net herstart is of een paar overgeslagen rondes, en verder niets.
De planner kijkt twee kalenderdagen terug en niet één, en dat is geen verschrijving. Een tijdstip van 23:55 met een inhaalvenster valt vlak ná middernacht in de kalenderdag van gisteren; met alleen vandaag in beeld zou dat slot per constructie nooit aan de beurt komen.
Twee remmen, om twee verschillende redenen
Vijf agents per organisatie per ronde, en vijfentwintig in totaal.
De rem per organisatie gaat over de bandbreedte van de klant en niet over die van ons. De binary is ongeveer tweeëntwintig megabyte, een hoster met veertig agents heeft ze doorgaans alle veertig op 03:00 staan omdat dat de default is, en veertig gelijktijdige downloads vertrekken uit één uitgaande verbinding. Vijf per minuut spreidt een vloot van veertig over acht minuten, ruim binnen het venster van vijftien minuten.
De globale rem bestaat omdat de eerste per organisatie werkt: met honderd organisaties zou dat vijfhonderd updates in één minuut betekenen. De rest komt de volgende minuut, nog steeds binnen het venster.
Er zit geen rolloutvlag op dit alles, wat voor ons ongebruikelijk is. De schakelaar staat al bij de klant, op de kolom; een tweede, platformbrede vlag zou een belofte doen die de kolom al doet. Wat er wél is, is een noodrem voor ons: het pollinterval op nul zetten stopt de timer zonder dat er één klantinstelling verandert.
De nacht met twee keer drie
Op de laatste zondag van oktober bestaat de wandtijd 03:00 twee keer, en op de laatste zondag van maart bestaat 02:30 helemaal niet. Allebei worden ze beantwoord door één gedeelde hulpfunctie, niet gekopieerd naar deze module, juist omdat een zomertijdalgoritme dat op twee plekken onderhouden wordt op één dag per jaar uit elkaar loopt.
Die hulpfunctie beantwoordt een dubbelzinnige wandtijd met de eerste van de twee momenten, en een niet-bestaande met het eerstvolgende moment dat wél bestaat. De gevolgen zijn precies wat je wilt. Op de lange nacht draait 03:00 één keer en niet twee; gemeten: één slot. Op de korte nacht wordt 02:30 gewoon 03:30 lokaal in plaats van over te slaan, want niet overslaan is de veilige kant van die munt. Allebei liggen ze vast in tests.
Dagen worden als kalenderdagen afgelopen en niet door een vast aantal milliseconden op te tellen: vierentwintig uur optellen over een overgang slaat een dag over of doet er één dubbel.
Wie wordt het verteld, en hoe vaak
De reconcile die uitkomsten meldt zit in de worker en niet op het dispatchpad, en alleen het schema meldt. Bij een klik met de hand stond er al iemand naar te kijken.
Meldingen worden per organisatie gegroepeerd, want veertig agents op releasedag horen geen veertig rijen in een postvak te worden. Een mislukking wordt hoogstens één keer per agent per kalenderdag gemeld. En het tijdstempel dat vastlegt "we hebben overwogen te melden" wordt ook geschreven als het besluit was om te zwijgen, zodat stilte te onderscheiden is van een worker die er nooit kwam.
Een geplande update verschijnt bewust niet als root-operation in de takenlade: om drie uur 's nachts wacht er niemand, en een lade vol nachtelijke rijen is nutteloos voor waar hij voor bestaat. Het spoor is een runrij, een gebeurtenis en een melding. Die gebeurtenis is een eigen soort, want de tijdlijn mag niet beweren dat de klant om drie uur zelf iets startte.
Wat wij zouden opschrijven
- "Standaard voor iedereen" is een kolomdefault. In applicatiecode betekent het "voor iedereen vanaf nu", en dat sluit precies de mensen uit aan wie je dacht.
- Unknown is niet onwaar. In jsonpath is een predicaat op een waarde van het verkeerde type noch waar noch onwaar, en ontkennen helpt niet. Toets eerst het type.
- Beslis per situatie of hij het slot verbruikt. Niets te doen en niet te bereiken zijn tegengestelde antwoorden, en allebei zijn ze onzichtbaar als je ze omdraait.
- Een inhaalvenster is een belofte over onderbreking, niet over volledigheid. Neem het getal over van een systeem met een ander faalgedrag en je hebt het verkeerde.
- Rem waar het werkelijk gevoeld wordt. Veertig agents in één klantnetwerk delen één uplink, en dat is de grens, niet onze downloadroute.
- Los zomertijd één keer op. Twee kopieën van dezelfde redenering zijn het eens tot de laatste zondag van oktober.
Voor de duidelijkheid over wat dit is: het schema staat live en standaard aan. Meerdere dingen zijn onbewezen. De worker is niet in een werkelijk draaiende gateway beproefd, geen echte agent heeft nog een geplande update uitgevoerd, de mislukkingsmelding met zijn dagontdubbeling is niet van begin tot eind waargenomen, en het scherm is in twee van de negen talen nagelopen.
Uit het platformwerk van Provibr. Waarom een agent een update weigert die hij niet kan verifiëren staat in hoe een getekende agent-update zichzelf weigert, en waarom de agent uitgaand verbindt in plaats van te luisteren staat in een uitgaande agent in plaats van portforwarding.