Tips for databasemigreringer

En kollega spurte i dag om mine topp tips når det gjelder databaserefactorings. Her var mitt svar:

  1. 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
  2. Bruk views og materialiserte views for å støtte tilbakekompabilitet (NB: Oracle er veldig sterk på dette, andre databaser kan slite)
  3. Om mulig, gjør hver migrering bakoverkompatibel på en versjon av programvaren. Dette er lettere å få til jo hyppigere du releaser programvaren
  4. 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?

Creative Commons License
Tips for databasemigreringer by Johannes Brodwall, unless otherwise expressly stated, is licensed under a Creative Commons Attribution 3.0 Unported License.

About Johannes Brodwall

Johannes is the director of software development for the MRM product company BrandMaster. In his spare time he likes to coach teams and developers on better coding, collaboration, planning and product understanding.
This entry was posted in Norsk, Software Development. Bookmark the permalink.
  • Bjørn Nordlund

    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..

    0. Gjør migreringen på tilsvarende versjoner i test først!
    5. 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.
    6. Ta en db backup rett før – spar på input hvis du kan for evt rekjøring

  • Lars Reed

    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

    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/

  • zmalltalker

    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.

  • anderssv

    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. ;)

  • niklasbjrnerstedt

    4. 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.

    5. husk å sjekk for korrupt data i produksjon. Korrupt data kan få “korrekte” script til å feile.

  • Bjørn Nordlund

    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

    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”

  • anderssv

    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

    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.

  • http://www.brodwall.com/johannes/blog/ jhannes

    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! :-)

  • http://tfnico.blogspot.com Thomas Ferris Nicolaisen

    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å :)

  • http://www.johannesbrodwall.com/ Johannes Brodwall

    Alternativ for rename/redefine column:

    1. Sett i produksjon en databaseendring med den nye kolonnen.
    2. Kjør migreringsscript av selve data til den nye kolonnen (kan gjentas som ofte man vil)
    3. Produksjonsett ny versjon (v2) av systemet parallelt med gammel versjon. Ny versjon må takle at kolonnen er NULL
    4. 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
    5. Ny versjon av programmet (v3) som ignorerer gammel kolonne (kan kjøres i parallell med forrige versjon – v2)
    6. Skru av v2
    7. 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.

  • anderssv

    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å.

  • http://www.johannesbrodwall.com/ Johannes Brodwall

    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

    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å.

  • http://www.johannesbrodwall.com/ Johannes Brodwall

    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.