Apps opvangen die direct na publiceren crashen
Op 25 september werden drie publicaties als geslaagd gemarkeerd, terwijl de API van de app bij elke start na enkele seconden crashte. Omdat ze "geslaagd" waren, draaide er geen rollback; een achtergrondcontrole keurde de deployments enkele minuten later alsnog af, en de hele site ging naar een 404. De health check telde crashes in de eventlijst van het hostingplatform — die maar de laatste vijf events per machine bevat, dus een crash-loop houdt de lijst vol en de telling groeit nooit. Nu vergelijkt hij het tijdstip van de nieuwste crash, en een publicatie die in een crash-loop zit, faalt en zet de vorige versie terug.
Waar een publicatie op wacht
Wanneer je een app met een backend op Mythex publiceert, wordt de nieuwe versie uitgerold naar machines op ons hostingplatform, en een health check beslist of de publicatie live is gegaan of is mislukt. Als die mislukte, zet een rollback de vorige versie terug.
Het lastige is dat "gestart" niet hetzelfde is als "bedient verzoeken". Het platform meldt een machine als gestart zodra hij draait. Een app die een seconde na het opstarten crasht, wordt meteen opnieuw gestart, en even later staat hij weer als gestart genoteerd. De health check had daarom in de loop van de tijd een paar lagen gekregen:
- Kijk twee keer. Nadat een machine als gestart is gemeld, wacht je even en kijk je opnieuw. Een crash bij het opstarten faalt vaak bij die tweede kijk.
- Let op crash-loops. Als het platform meldt dat een machine herhaaldelijk crashte en het herstarten opgaf, faal je.
- Tel de crashes sinds het begin van de publicatie. Twee "gestart"-kijkbeurten kunnen een herstart omspannen. Als er sinds de start van deze publicatie crashes zijn geweest, kijk dan een derde keer; als er meer crashes zijn dan bij de vorige kijkbeurt, blijft de app niet overeind.
De derde controle is degene die hier faalde.
Waarom de telling vlak bleef
De crashinformatie komt uit de eventlijst van elke machine: starts, exits, herstarts. Het platform bewaart alleen de laatste vijf events per machine.
Stel je een app voor die 2,5 seconde na elke start crasht, zoals een van hen deed. Elke cyclus voegt een crash en een start toe. Na een paar cycli is de lijst vol. Vanaf dan duwt elk nieuw event een oud event eruit. De lijst schuift mee, maar bevat altijd ongeveer dezelfde mix: een paar crashes, een paar starts.
Dus "hoeveel crashes sinds het begin van de publicatie" gaf bij elke kijkbeurt hetzelfde getal terug. De tweede kijkbeurt zag bijvoorbeeld twee crashes. De derde kijkbeurt zag ook twee — andere crashes, maar hetzelfde aantal. De controle concludeerde dat het crashen was gestopt, en liet de publicatie door.
Dat gebeurde drie keer op 25 september, bij twee apps. Elke keer:
- werd de publicatie als live gemarkeerd,
- draaide geen rollback, omdat er niets was mislukt,
- faalde onze reconciler, een achtergrondtaak die deployments achteraf controleert, de deployment enkele minuten later,
- en gaf de hele site een 404.
Een publicatie die op tijd faalt, is herstelbaar: de rollback zet de vorige versie terug. Een publicatie die een paar minuten te laat faalt, nadat hij als geslaagd was gemeld, heeft de rollback al overgeslagen.
Hoe we het hebben gevonden
We hebben het lokaal gereproduceerd: een werkende API genomen, hem laten slapen, en daarna opnieuw gepubliceerd met een wijziging die bij het opstarten crashte. De health check markeerde hem als live.
Dat kwam overeen met productie: bij elk van de drie publicaties crashte de app seconden na elke start, maar het aantal crashes dat de check vergeleek, steeg tussen de kijkbeurten nooit.
De oplossing: vergelijk de nieuwste crash, niet het aantal
In plaats van crashes te tellen, kijkt de check nu naar het tijdstip van de meest recente crash sinds het begin van de publicatie.
Als de tweede kijkbeurt een crash sinds de start van de publicatie laat zien, wacht de check en kijkt hij een derde keer. Als de nieuwste crash bij de derde kijkbeurt later is dan de nieuwste crash bij de tweede, is de machine tussendoor opnieuw gecrasht. Hij blijft dan niet overeind, en de publicatie faalt met een melding dat de machine sinds de publicatie herhaaldelijk crashte en nog steeds opnieuw opstart.
Dit werkt ongeacht hoe kort de eventlijst is. Een glijdend venster van vijf events kan verbergen hoeveel crashes er waren, maar het kan niet verbergen dat er zojuist een nieuwe is gebeurd — het nieuwste event blijft altijd bewaard.
Met deze wijziging gedraagt de lokale reproductie zich correct: de publicatie faalt en de vorige image wordt hersteld. Er is ook een test die het gedrag van het platform simuleert — elke kijkbeurt voegt een crash en een start toe en snoeit de lijst tot vijf — en controleert dat het oordeel een mislukking is, niet een slagen.
Wat we ervan geleerd hebben
- Ken de vorm van de data die je telt. Een telling over een begrensde lijst betekent niets meer zodra de lijst vol is. De begrenzing was niet verborgen; we hadden de check alleen niet op die begrenzing ontworpen.
- Geef de voorkeur aan "is er iets nieuws gebeurd?" boven "hoeveel is er gebeurd?" Het nieuwste tijdstempel overleeft het afsnijden. Een totaal niet.
- Een late fout is erger dan een vroege. Dezelfde kapotte build is onschadelijk als de publicatie faalt en terugrolt, en brengt een site neer als hij eerst als geslaagd wordt gemeld.
- Reproduceer onder de echte omstandigheden. De bug vereiste een machine die al vaak genoeg was gecrasht om zijn eventlijst te vullen. Eén snelle crash in een test had dat niet getoond.
Deze fix past bij een andere van dezelfde dag, die ervoor zorgde dat de rollback de oude versie terugzet in plaats van die te verwijderen: een mislukte herpublicatie verwijdert nooit een werkende backend. Als je eigen app niet publiceert omdat hij crasht bij het starten, zijn de publicatie-probleemoplossing in de docs en onze gids over een door AI gebouwde app debuggen goede plekken om te beginnen.