Volledige cloud-outages opvangen met Monzo Stand-in
Dit blogbericht was accuraat op het moment van publicatie – bezoek monzo.com of de Monzo-app voor de meest actuele informatie.
Onze klanten mogen er redelijkerwijs op vertrouwen dat ze 24 uur per dag, 365 dagen per jaar met hun kaart kunnen betalen, bankoverschrijvingen kunnen doen en hun rekeningen kunnen betalen. Hun leven kent geen downtime voor onderhoud, en dat zou onze dienstverlening ook niet mogen hebben. We besteden veel engineering-inspanningen aan het minimaliseren van het risico op downtime tijdens technische migraties en andere dagelijkse operaties, maar onvoorziene incidenten die leiden tot onverwachte outages zijn onmogelijk volledig uit te sluiten.
We nemen betrouwbaarheid serieus bij Monzo, daarom hebben we een volledig aparte back-up banking-infrastructuur gebouwd, genaamd Monzo Stand-in. Dit vormt een extra verdedigingslaag zodat klanten essentiële diensten kunnen blijven gebruiken. We beschouwen Monzo Stand-in als een back-up in laatste instantie, niet als ons primaire mechanisme voor het leveren van een betrouwbare service.
Monzo Stand-in Architectuur
Monzo Stand-in is een onafhankelijke set systemen die draaien op het Google Cloud Platform (GCP) en in staat is het over te nemen van ons Primary Platform, dat draait in Amazon Web Services (AWS), in het geval van een groot incident. Het ondersteunt de belangrijkste functies van Monzo, zoals:
- Betalen met kaarten;
- Geld opnemen;
- Bankoverschrijvingen verzenden en ontvangen;
- Saldo's en transacties controleren;
- Kaarten blokkeren of ontblokkeren.
Ons Primary- en Stand-in-platform draaien onafhankelijk van elkaar. Beide bestaan uit Kubernetes-clusters die een unieke set services draaien bovenop typische platformcomponenten zoals een database, wachtrijsystemen (queueing systems) en locking-mechanismen. De services in elk platform zijn uniek; services in het Stand-in-platform draaien nooit in het Primary-platform, en vice versa, zelfs niet voor functionaliteiten die in beide voorkomen, zoals het verwerken van een kaartbetaling.
Elk platform is in staat om eigen beslissingen te nemen over het goedkeuren of weigeren van transacties en kan eigen verbindingen tot stand brengen met betalingsnetwerken via meerdere fysieke datacenters.
Monzo Stand-in draait daarnaast een beperkte set API-endpoints die zijn afgestemd op de beperkte functionaliteit die beschikbaar is tijdens het gebruik van Stand-in. De Monzo-app controleert periodiek op de achtergrond of Monzo Stand-in is ingeschakeld. Als dat het geval is, schakelt de app over naar een vereenvoudigde gebruikersinterface (UI) die onze belangrijkste functies ondersteunt.
Risicobeperking door verschillende systemen
Het lijkt wellicht vreemd om volledig nieuwe services vanaf nul op te bouwen voor Monzo Stand-in in plaats van dezelfde services te implementeren als op het Primary Platform. Hier zijn verschillende redenen voor:
Vermijden van data-consistentie problemen
Als we exact dezelfde set services zouden draaien, zouden we alle data tussen de twee platforms moeten repliceren. Om dit goed te doen, zouden we sterke consistentie van onze data moeten handhaven. Dit zou betekenen dat schrijfacties naar onze database pas als succesvol worden beschouwd als de data op beide platforms is geschreven. Als een van beide platforms onbeschikbaar zou worden, zouden we niets kunnen schrijven zonder de consistentie op te offeren, wat onze algehele beschikbaarheid juist zou verminderen in plaats van verbeteren.
In plaats van sterke data-consistentie accepteren we dat de replicatie non-blocking is en dat deze eventually consistent is. Systemen zoals ons grootboek (ledger) kunnen echter niet omgaan met eventually consistent data.
Vermindering van de kans op dezelfde fouten
Ons Primary Platform opereert over verschillende AWS-availability zones en draait meerdere replica's van al onze services. We ontwerpen systemen om schaalbaar te zijn en ze degraderen gracieus wanneer niet-kritieke afhankelijkheden een fout geven. Ondanks deze ingebouwde veerkracht kunnen complexe systemen op verrassende manieren falen.
Er zijn talloze redenen waarom ons Primary Platform kan uitvallen. Hoewel het makkelijk is om te denken dat we alleen een outage van de cloudprovider willen mitigeren, is het minstens zo waarschijnlijk dat een bug in onze code of processen de oorzaak is van een outage.
Traditionele Disaster Recovery-systemen richten zich voornamelijk op hardwarefalen, ervan uitgaande dat een netwerkstoring of schijffout het grootste risico is. Cloudproviders zoals AWS, GCP en Azure hebben hardwarefalen grotendeels opgelost, maar disaster recovery is niet echt mee geëvolueerd. Tegenwoordig maakt het niet uit hoeveel datacenters je hebt als je overal dezelfde software draait.
Hoe groter de onafhankelijkheid tussen de Stand-in-omgeving en het Primary Platform, hoe kleiner het risico dat hetzelfde probleem beide platforms raakt. In ons geval draaien het Primary- en Stand-in-platform hun eigen onafhankelijke code voor de verwerking van kaartuitgiften, waarbij beide in staat zijn transacties te autoriseren. Hoewel de platforms op een vergelijkbare manier worden verwacht te werken, implementeren we ze apart en streven we ernaar de afhankelijkheid van gedeelde code zoveel mogelijk te minimaliseren.
Kostenbeheersing
We monitoren de kosten van onze platforms zeer nauwkeurig. Monzo Stand-in kost ongeveer 1% van de kosten van ons Primary Platform om op de achtergrond te laten draaien, en we verwachten dat dit slechts marginaal zou toenemen als we het tijdens een groot incident inschakelen. Als we in plaats daarvan alle systemen zouden draaien en alle data zouden repliceren, zouden de kosten voor rekenkracht en het benodigde personeel veel groter zijn, wat onze totale platformkosten potentieel zou verdubbelen.
Synchronisatie van data tussen platforms
Monzo Stand-in bevat alleen de minimale status die nodig is om de beperkte functies te ondersteunen. Dit omvat zaken als saldo en beperkte historische transactie-informatie, voldoende details over kaarten en rekeningen om betalingen te verwerken, en een lijst met zaken als 'pots' en begunstigden.
Wanneer deze data in het Primary Platform wijzigt, schrijft onze Stand-in Data Syncer deze updates naar het Stand-in-platform. Alle verwerkingsresultaten, statusovergangen en andere effecten die in het Primary Platform worden gecreëerd, worden gepubliceerd in ons eventsysteem. De Stand-in Data Syncer consumeert een subset van deze events om het proces van status-updates in het Stand-in-platform te triggeren.
Alle data die we synchroniseren van Primary naar Stand-in wordt behandeld als immutable (onveranderlijk). We verwachten niet dat het Stand-in-platform draait op een perfect consistent beeld van de wereld, maar in de praktijk is dit beeld zeer dicht bij perfecte consistentie vanwege de real-time aard van de synchronisatie. We monitoren de lag van dit eventually consistent synchronisatieproces zeer nauwgezet en sturen meldingen wanneer de lag in zeldzame gevallen onze tolerantiegrens overschrijdt.
Onze getokeniseerde data (bijv. kaart-PAN's die we versleutelen) volgt een vergelijkbare, maar iets andere synchronisatiestroom, waarbij we data die is versleuteld voor verschillende keysets (aangeduid als A en B in het diagram) uitwisselen tussen de tokenisatiesystemen in elk platform.
Synchronisatie van status vanuit Monzo Stand-in
Wanneer Monzo Stand-in is ingeschakeld, produceert het vergelijkbare verwerkingsbeslissingen en statusovergangen als we doen in het Primary Platform. Omdat dit echter niet ons primaire platform is, zijn deze resultaten alleen gezaghebbend voor de duur dat we in Stand-in-modus opereren.
We slaan nieuwe status in Monzo Stand-in apart op van de immutable data die vanuit het Primary Platform is gesynchroniseerd. We leggen een logboek bij van alle effecten in een duurzame wachtrij, zodat het Primary Platform deze kan consumeren zodra dat mogelijk is. Als het Primary Platform slechts gedeeltelijk onbeschikbaar is, kan het deze wachtrij mogelijk direct consumeren; bij een volledige outage gebeurt dit op een later tijdstip.
De records van effecten in dit logboek worden aangeduid als Monzo Advices. Deze informeren ons Primary Platform over een gecreëerd effect (bijvoorbeeld een goedgekeurde kaartbetaling) en we verwachten dat het Primary Platform de effecten van deze Advices letterlijk toepast.
Het Primary Platform is ons system of record en beheert de waarheid. Op geen enkel moment neemt Monzo Stand-in deze rol over. Dit impliceert dat we, door Monzo Advices letterlijk toe te passen op basis van een potentieel inconsistente weergave van een klantsaldo in Monzo Stand-in, een transactie kunnen hebben goedgekeurd terwijl het Primary Platform vindt dat de klant onvoldoende saldo had. In dat geval zou de klant in een niet-goedgekeurd overdraft-saldo terechtkomen. We hanteren verschillende controles in ons Primary Platform om dit risico te beperken, maar in de praktijk is het uiterst onwaarschijnlijk dat dit gebeurt.
Correlatie van data in beide platforms
Wanneer we een Monzo Advice van het Stand-in-platform toepassen op het Primary Platform, verwachten we dat downstream-systemen een equivalente status creëren. Een Advice voor een kaartbetaling in Stand-in zal bijvoorbeeld geld verplaatsen in het Ledger in het Primary Platform.
Dit creëert een klein probleem: we hebben nu status in zowel het Stand-in- als het Primary-platform die dezelfde betaling vertegenwoordigt. Wanneer geld wordt verplaatst in het Ledger in het Primary Platform, wordt er namelijk ook een transactie gesynchroniseerd naar Stand-in via onze Data Syncer, zoals eerder beschreven.
Om te voorkomen dat transacties en andere effecten in Stand-in dubbel worden geteld, genereren we een Correlation ID (aangeduid als A in het diagram) voor StandinTransactions en SyncedTransactions. We voegen onze weergave van gecorreleerde transacties samen wanneer we deze in het Stand-in-platform gebruiken.
Monzo Stand-in inschakelen
We maken gebruik van een Stand-in Configuration-service in zowel het Primary- als het Stand-in-platform. Deze zijn grotendeels API-compatibel en coördineren welk platform is ingeschakeld, welke componenten van het Stand-in-platform actief zijn en voor welke gebruikers dit geldt. Wanneer we een outage detecteren in een service die we kritiek belangrijk achten voor onze klanten, kunnen we de overeenkomstige delen van Monzo Stand-in inschakelen via dit configuratiesysteem.
Momenteel updaten we de configuratie via onze Stand-in Platform CLI-tooling, maar met verdere ontwikkeling kunnen we dit proces volledig automatiseren door het configuratiesysteem te triggeren op basis van dezelfde heuristieken die onze engineers handmatig gebruiken.
Dit systeem werkt zelfs als het Primary Platform volledig onbeschikbaar is. Zoals eerder vermeld, controleert de Monzo-app beide platforms periodiek op de achtergrond om te bepalen of de beperkte ervaring van Monzo Stand-in moet worden getoond. Het uitschakelen van Monzo Stand-in is een bewuste beslissing die engineers handmatig nemen. Hierdoor gaat het app-verkeer niet onmiddellijk terug naar het Primary Platform op het moment dat de API weer beschikbaar is; we kunnen de Monzo-app geleidelijk terugzetten naar het Primary Platform.
Betalingsverkeer routeren via het Primary Platform
Wanneer we Monzo Stand-in inschakelen voor de verwerking van betalingen, blijft het systeem in eerste instantie het betalingsverkeer via het Primary Platform routeren, dat deze vervolgens proxy't naar het Stand-in-platform. Hoewel dit contra-intuïtief klinkt, geeft het ons veel meer controle over hoeveel klanten (of specifiek welke klanten) overgaan naar Monzo Stand-in, terwijl de betalingen van de rest van onze klanten in het Primary Platform blijven. Dit helpt ons ook om zeer snel te herstellen van een outage.
Ons systeem voor het bepalen of betalingsverkeer naar de Primary- of Stand-in-processor moet worden gerouteerd, neemt per ontvangen betalingsbericht een besluit.
Dit werkt voor de meeste incidenten, maar bij betalingsverwerking is dit niet effectief als ons Primary Platform volledig plat ligt. In dat geval kunnen we het Stand-in-platform direct verbinden met de betalingsnetwerken via onze datacenters. Dit is een drastischere optie, omdat we veel minder controle hebben over welke klanten of hoeveel verkeer naar het Stand-in-platform wordt geleid, maar zonder deze optie zouden we niet veerkrachtig zijn.
Beide routes — het proxyen van betalingen naar Monzo Stand-in via het Primary Platform of het direct ontvangen ervan in het Stand-in-platform — worden rigoureus en continu getest in productie om te bewijzen dat het systeem werkt wanneer we het nodig hebben.
Praktijkvoorbeeld: Monzo Stand-in in actie
In augustus 2024 hadden we een groot platformincident dat de meeste van onze systemen beïnvloedde, inclusief ons vermogen om betalingen te verwerken en de Monzo-app te bedienen. De outage zelf duurde ongeveer één uur, maar we schakelden Monzo Stand-in kort na de detectie van het probleem in om ervoor te zorgen dat onze klanten nog steeds toegang hadden tot hun geld.
Dit was niet de eerste keer dat we Monzo Stand-in hadden gebruikt; voor testdoeleinden hebben we altijd kleine aantallen klanten die het gebruiken. Het was echter de eerste keer dat we alle componenten van Monzo Stand-in voor al onze klanten hadden ingeschakeld. Als je de Monzo-app op dat moment had geopend, zou je duidelijke verschillen hebben gemerkt ten opzichte van de gebruikelijke ervaring, maar de belangrijkste taken — zoals het controleren van je saldo, het verzenden en ontvangen van bankoverschrijvingen en het gebruik van kaarten — bleven werken.
Slotbeschouwingen
Monzo Stand-in is een groot succes geweest en heeft ons geholpen te bewijzen dat we veerkrachtig zijn tegen kritieke platformoutages, zowel in beleid als in praktijk. Met operationele veerkracht hoog op de agenda van technische leiders en toezichthouders, en met de inwerkingtreding van EU-regelgeving zoals DORA, geloven we dat we voorop lopen met een praktische, kosteneffectieve en minder belastende benadering van veerkracht dan traditionele Disaster Recovery-oplossingen.
Er is nog veel meer over Monzo Stand-in te vertellen dan we in dit bericht hebben aangestipt. We zijn van plan in deze serie dieper in te gaan op de complexiteit van specifieke componenten, waaronder de werking van de betalingsverwerking, de technische details van het configuratiesysteem en de innovatieve manieren waarop we dit testen.
***
Auteurs:
- Daniel Chatfield, Distinguished Engineer
- Andrew Lawson, Senior Staff Engineer
Groetjes,