Tips for databasemigreringer
En kollega spurte i dag om mine topp tips når det gjelder databaserefactorings. Her var mitt svar:
- Ha en organisert struktur med at man gjennomfører navngitte migreringer (a la Ruby-on-Rails sine migrations eller dbdeploy). Typisk er det vanlig og velfungerende å navngi scripts med løpenummer (001, 002, …) eller timestamp (20091124071300, …) og ha en tabell i databasen som holder styr på hva som har blitt kjørt
- Bruk views og materialiserte views for å støtte tilbakekompabilitet (NB: Oracle er veldig sterk på dette, andre databaser kan slite)
- Om mulig, gjør hver migrering bakoverkompatibel på en versjon av programvaren. Dette er lettere å få til jo hyppigere du releaser programvaren
- Skill endringer i skjema (for eksempel: legg på en kolonne) fra migrering av data (for eksempel: populere kolonnen). Feilene vil typisk ligge i #2 av disse, og den er lett å gjøre transaksjonell, mens skjemaendringer ikke er transaksjonelle i de fleste baser.
Har jeg dekket det viktigste da?
Comments:
[anderssv] - Nov 25, 2009
Supert!
Som Zmalltalker sier: Nummer kan gi problemer i forhold til branching. Håndterbart med få brancher, men allikevel. Timestamps er ikke helt feilfritt det heller, men bedre.
Besnærende tanke med å gjøre migreringer før selve utrullingen av kode og kjøre på dette en periode. Men tror jeg foretrekker å la kode og migreringer høre slavisk sammen. Mindre omatte-dersomatte. ;)
Det finnes vel to forskjellige grunner til å beholde bakoverkompatibilitet: Rollback av kode og bakoverkompatibilitet i forhold til konsumenter av “grensesnittet”.
Har du konsumenter av grensesnittet bør du ha paralell støtte for X “versjoner” av gangen med et deprecation regime. Da må du også ha tester for å verifisere bakoverkompatibiliteten, ikke bare på nyeste versjon.
Støtter du bakoverkompatibilitet for å kunne rulle tilbake applikasjonen trenger du ikke like strengt regime. Men da lurer jeg litt på om det er verdt det?
Uansett, knall innspill fra alle. Er på tide at vi blir proffe på denne delen. ;)
[anderssv] - Nov 26, 2009
Galskap er det nok ikke, men besnærende. ;) Bare usikker på verdien, og at man må holde tunga rett i munnen.
Men dette er spennende. Erfaringer burde samles, kanskje man kunne kjørt en XP Meetup rundt temaet?
[tfnico] - Nov 26, 2009
Vi deployer applikasjon og databaseendring samtidig. Vi har mye live trafikk på databasen, så vi tør nesten ikke annet. Uansett, det er veldig kjedelig å ha nedetid, både for kunder og ansatte (man skal gjerne ha det utenfor primetime også). Hvis vi kunne ha mestret teknikkene i boka til Ambler så kunne vi oppgradert databasen først.. Det hadde vært gull. Men per i dag så virker det så komplisert (mye arbeid å være bakoverkompatibel!) at jeg ikke kommer noen vei når jeg tar det opp i teamet.
Johannes Brodwall - Nov 30, 2009
Det stemmer at med denne løsningen vil v2 måtte være forberedt på at data ligger i gammelt format. Dette er mer automatisk med triggere, men også litt mer magisk. Jeg ville valgt løsning avhengig av hvor omfattende testing jeg hadde av databasefunksjonalitet.
[anderssv] - Nov 29, 2009
Da må V2 støtte begge kolonner da? Og holde de oppdatert, og lese begge? Hvis ikke vil jo V1 skrive til gammel kolonne som ikke blir vist/fanget opp i V2. Tror jeg ville gått for triggerløsningen i dette tilfellet, men løsning i kode kan være bedre for en annen situasjon.
Som Ferris sier så må man ha regime på dette med disiplin. F.eks. 6 måneder etter at en kolonne blir deprecated så slettes den og det kommuniseres ut til alle.
Fant stoff på denne bloggen og: http://exortech.com/blog/2009/02/01/weekly-rele… . De har et eget rammeverk med expansion/contraction skille. Virker nok best i tilfellene hvor du ikke har noen eksterne forbrukere du må vente lenge på.
[zmalltalker] - Nov 25, 2009
Bra innlegg!
Et lite innspill ref pkt 1: Rails hadde ganske lenge løpenummer på sine script, noe som ganske ofte ga problemer ved parallell utvikling på branches:
- Team A jobber i en topic branch og trenger en migrering. Mandag lager de en ny migrering som får løpenummer 4 og navn “AddingCompanyIdToUsers”
- Team B trenger en migrering samme dag, men jobber i mainline. De lager en ny migrering som får løpenummer 4 og navn “AddingLastNameToUsers”
Man får altså to migreringsscript med samme versjonsnummer. Versjonskontrollsystemet klager ikke, ettersom dette er to forskjellige filer.
Da Rails gikk over til å bruke timestamps istedet for løpenummer slapp man å håndtere disse konfliktene manuelt.
[Bjørn Nordlund] - Nov 25, 2009
Det er sikkert mange grunner til å deploye applikasjon og databaseendringer samtidig, men jeg har veldig god erfaring med å gjøre det gradvis.
Vi har også gjort gradvise endringer hvor for eksempel en versjon starter med å skrive data i nye tabeller/kolonner, mens først neste versjon starter å benytte disse dataene. Da har vi en todelt (egentlig 3 først database, deretter applikasjon, og deretter ny versjon av applikasjon).
Noen mener sikkert dette er galskap, jeg mener det kan være lurt :)
[geirhedemark] - Nov 25, 2009
Mnah, kan bli littegrann bedre:
“Les database refactoring av Ambler, Sadalage”
“Skriv tester som tester mot databasen, ellers vet du ikke hva du gjør”
“Unngå mest mulig av triggere, prosedyrer, views. Bruk mest mulig tabeller med minst mulig features - det er enklest å teste.”
“Sørg for at applikasjonen tester at den kjører mot riktig versjon av skjemaet”
Johannes Brodwall - Nov 26, 2009
Alternativ for rename/redefine column:
- Sett i produksjon en databaseendring med den nye kolonnen.
- Kjør migreringsscript av selve data til den nye kolonnen (kan gjentas som ofte man vil)
- Produksjonsett ny versjon (v2) av systemet parallelt med gammel versjon. Ny versjon må takle at kolonnen er NULL
- Når man er helt sikker på at man vil gå videre, skru av gammel versjon (v1) og kjør migreringsscript for data en siste gang
- Ny versjon av programmet (v3) som ignorerer gammel kolonne (kan kjøres i parallell med forrige versjon - v2)
- Skru av v2
- Dropp gammel kolonne
Det krever litt oppfølgning. Men alle stegene er reverserbare. Jeg foretrekker en slik strategi dersom frykt for feil gjør at man ikke vil produksjonsette.
[niklasbjrnerstedt] - Nov 25, 2009
Et problem er at for DB der skjemaendringer ikke er transaksjonelle så er for eks. et kolonnesplitt umulige å gjøre ferdig uten å ha to pass med skjemaendringer med datamigrering i mellom.
husk å sjekk for korrupt data i produksjon. Korrupt data kan få “korrekte” script til å feile.
jhannes - Nov 26, 2009
Bakoverkompatible databaseendringer er jo et tema vi kunne diskutert mer.
Som et trivielt eksempel: Det å legge til en ny tabell som er uavhengig av resten av skjema er alltid bakoverkompatibelt. Det å legge til en kolonne er nesten alltid kompatibelt.
Hvilke databaseendringer er vanlige og vanskelig å gjøre bakoverkompatible? Eksempler, plz! :-)
Thomas Ferris Nicolaisen - Nov 26, 2009
Her er det lettest å peke på “boken” igjen. Jeg tror jo lenger uti du blar, jo vanskeligere blir det. Her er en fin oversikt forresten: http://www.agiledata.org/essays/databaseRefacto…
Det å legge til nye tabeller og kolonner er temmelig rett frem, men det er typisk knyttet til nyutvikling. Når det gjelder refaktorering, så må man typisk gjøre ting i flere faser. F.eks Rename Column: (1) opprett ny kolonne, (2) lag trigger som oppdaterer gammel kolonne i en overgangsperioden, (3) rull ut nye applikasjoner som bruker ny kolonne, (4) fjern gammel kolonne.
Hele roundtripen tar lang tid, krever tunga rett i munnen, og mye disiplin for spesielt å være sikker på at (3) og (4) blir fulgt opp. Overgangstiden kan være alt fra noen minutter (i et cluster som vi har), eller åresvis (klientapplikasjoner i andre team).
Det er 60 teknikker i “boka”, og man må beherske databasen sin godt. Man må øve mye, og ha gode regresjonstester, som Anders sier. Mye jobb for mitt lille team, altså :)
[Bjørn Nordlund] - Nov 24, 2009
Her er noen.. Men først og fremst, ikke vær redd for å gjøre db endringer, mye værre å la være, jo oftere og mindre endringer jo bedre..
- Gjør migreringen på tilsvarende versjoner i test først!
- Hvis endringene er bakoverkompatible (og det bør de) gjør db migreringen først, deretter deploy den nye koden - gjerne med litt tid mellom hvis mulig.
- Ta en db backup rett før - spar på input hvis du kan for evt rekjøring
[Lars Reed] - Nov 24, 2009
Ad 0: Og på reelle volumer. De ekleste feilene er der alt er “riktig”, men basen ikke takler volumet (på rimelig tid)
Ad 1: Dersom man har en applikasjon som egner seg, og endringene IKKE er kompatible, vurder å slå opp endringsnivå og avbryt/advar om mismatch mellom applikasjon og base (fail-fast…)
Ad 6: Nevnte noen backup?
[tfnico] - Nov 24, 2009
Om du ikke har views kan du bruke triggers for bakoverkompatibilitet (fyll data inn i gammel tabell for eksempel).
Tenk at en refaktorering er noe som går over uker/måneder. Pass på at du setter og vedlikeholder ferdigstillingsdatoer for å fullføre dem (fjerne gammel tabell i juli 2010).
Mitt beste tips er nok å lese “Refactoring Databases: Evolutionary Database Design”, og hva Anders Sveen har skrevet om saken på http://blog.f12.no/wp/tag/database/