Stapje voor stapje data migreren
Als er één constante is in softwareontwikkelland, dan is het wel dat software constant verandert. Nieuwe inzichten noodzaken ontwikkelaars om hun code aan te passen om bepaalde use cases (beter) te kunnen ondersteunen. Soms betekent dat dat bepaalde algoritmen moeten worden vervangen, andere keren betekent dat dat er een aanpassing moet plaatsvinden in het model waar de algoritmen gebruik van maken.
Modelwijzigingen
Die laatste situatie kan problemen opleveren. De meeste applicaties maken gebruik van data die is opgeslagen in een database. Hoe ga je bij modelwijzigingen om met data die is opgeslagen volgens een inmiddels verouderd model?
Er zijn twee opties: ofwel je beschouwt alle oude data als verloren, ofwel je vindt een manier om de verouderde data om te zetten naar het nieuwe model. De eerste optie is in de meeste gevallen onaanvaardbaar. Uitzonderingen zijn: privéprojecten of applicaties die zich nog in een pilotfase bevinden.
In alle andere gevallen zal er een datamigratie plaats moeten vinden. Ook hier bestaan twee smaken in: ofwel je migreert alle data in één keer - de zogenaamde big bang -, ofwel je doet dat stapje voor stapje.
Big bang
Mijn team heeft jarenlang van dat eerste smaakje mogen proeven in onze legacy-applicatie. We hadden een aparte tool ontwikkeld die gebruik maakte van de applicatiecode om de database te benaderen. In die tool gaven we aan welke eigenschappen sinds de modelwijziging waren veranderd. Vervolgens drukten we op een knop, en - tada! - de data was weer up to date.
Deze oplossingsrichting is relatief eenvoudig, maar niet zonder nadelen. Ten eerste speelt er een coördinatievraagstuk. Wanneer je de nieuwe code uitrolt zonder het migratiescript op de productieomgeving te draaien, wordt de applicatie onbruikbaar. Andersom geldt dat, als je geen boze gebruikers wil, je het migratiescript niet kunt draaien zonder de nieuwe code uit te rollen.
Dit maakt een uitrol een heikel punt, dat goede afstemming vergt met gebruikers. Je kunt niet elke nieuwe wijziging zomaar meer doorzetten naar je productieomgeving. Als gevolg daarvan hopen codewijzigingen zich op. Wanneer er dan een bug wordt ontdekt, valt moeilijk na te gaan bij welke wijziging deze erin is geslopen.
Een tweede probleem is schaalbaarheid. In de loop der jaren was de database van onze applicatie enorm gegroeid. Hierdoor was de migratie geen kwestie van seconden meer, zoals op onze ontwikkellaptops, maar minuten. Als er halverwege de migratie een probleem ontstond, dan was onze data in corrupte staat geraakt en moest de boel terug worden gedraaid. Vervolgens begon het proces weer van voren af aan.
Dit is het soort ervaringen waar horrorverhalen uit worden geboren.
Proof of concept
Met deze ervaringen in het achterhoofd, besloot het team te onderzoeken of het tweede smaakje misschien niet een aantrekkelijker optie was.
Twee factoren speelden daarbij in ons voordeel. (1) Onze nieuwe applicatie maakt gebruik van RavenDB, een NoSQL-database die objecten opslaat in JSON-formaat. (2) Er bestaat open source tooling in de vorm van Migrations.Json.Net, die één of meerdere migratiescripts uit kan voeren bij het serialiseren of deserialiseren van JSON naar objecten.
Tijdens de proof of concept (POC) waarin we deze technieken met elkaar combineerden, deden we de volgende inzichten op:
-
Het werkt! Het is mogelijk om on the fly nieuwe properties aan een object toe te voegen, of bestaande properties te wijzigen, wanneer deze opgehaald worden uit onze database. De oude data wordt omgezet naar het nieuwe model wanneer dat nodig is, niet wanneer het ontwikkelteam dat forceert.
-
Pas op voor botsende properties. RavenDB kent zijn eigen versioneringssysteem, waar we als ontwikkelteam dankbaar gebruik van maken. Onze objecten kennen dan ook een Version-property waarmee we de versie van een object naar de eindgebruiker communiceren. Migrations.Json.Net veronderstelt echter ook een Version-property om te kunnen bepalen welke migratiescripts er allemaal uitgevoerd dienen te worden. Een versie 1-object dat wordt opgehaald via een versie 3-model, zal migraties moeten ondergaan van versie 1 naar 2 naar 3.
Zoals te verwachten valt, is dat een verwarrende situatie voor beide tools. Om hiermee om te kunnen gaan, moet het team deze ambiguïteit oplossen. Dat kan bijvoorbeeld door onze eigen Version om te zetten naar een property met een andere naam. (Merk op dat het gebruik van Migrations.Json.Net niet de geschikte kandidaat is om dit voor elkaar te krijgen, hiervoor moet een migratiescript oude stijl worden geschreven.) Een andere optie is om een pull request te doen op Migrations.Json.Net om de naam van die property configurabel te maken.
-
Migraties laten zich niet integratietesten. Ik had een test geschreven waarin ik met wat trucage een verouderd object opsloeg in RavenDB, om te zien of ik hem als geüpdate object terug zou krijgen, maar dat vond de database niet zo leuk. Binnen de POC bleef dit ongemak echter beperkt: de functionaliteit liet zich net zo goed handmatig bewijzen.
Maar voor de daadwerkelijke implementatie van deze techniek is het wel belangrijk om deze beperking in het achterhoofd te houden. Het haalt een stukje zekerheid weg voor de ontwikkelaar wiens taak het is de migratie te schrijven, en legt extra daarnaast verantwoordelijkheid bij de tester neer.
Stapje voor stapje
Door verouderde data stapje voor stapje te migreren naar een nieuw model, worden de nadelen van onze oorspronkelijke oplossingsrichting verholpen. De modelwijziging en migratiecode worden tegelijkertijd uitgerold en vergen vervolgens geen handmatige actie meer van het ontwikkelteam. Het coördinatieprobleem is dus van de baan.
Hetzelfde geldt voor de schaalbaarheidsproblematiek. Het kritieke moment waarop alle data moest worden gemigreerd, is uitgesmeerd over een veelvoud aan niet-kritieke momenten. In tegenstelling tot de bulkmigratie, kan de migratie van individuele objecten snel en pijnloos gebeuren.
Bovendien is de oplossing eenvoudig te bouwen door gebruik te maken van onze bestaande tooling in combinatie met open source-software.
Kleven er dan helemaal geen nadelen aan deze oplossingsrichting? Ongetwijfeld wel. Maar daar zullen we in de loop van de tijd pas achter komen, stapje voor stapje. En dan zal ik vast en zeker een blog schrijven over de zegeningen van big bang-datamigraties.
databases · datamigratie · leermoment · nosql · open source · procesverbetering · proof of concept · software ontwikkelen · testen