DbDeploy - wersjonowanie schematu bazy danych

2011-04-10 21:36:36 2011-04-10 21:48:36

Większość projektów z jakimi mam do czynienia wykorzystuje bazę danych. Baza zazwyczaj jest SQLowa, i rozwija się wraz z projektem. Dodawane są nowe tabele, modyfikowane i dodawane pola w tabelach, powstają klucze, procedury składowane i wiele innych.

Nadchodzi jednak etap, kiedy w projekcie pojawia się gałąź produkcyjna lub testowa. Aplikacja ma teraz instancje w środowisku testowym, i/lub produkcyjnym. Jak od tego momentu zapanować nad odpowiednim wrażaniem zmian na wersję produkcyjną ? Jak panować nad zbiorem poprawek, i ich wdrażaniu w odpowiedniej kolejności ? Jeszcze niedawno zadowalało mnie proste zrzucanie struktury, i wrzucanie jej na wersję produkcyjną. Powstają jednak problemy z ponownym wrzucaniem danych, czasem niedostępności aplikacji i nie tylko.

Wymagania

Problem utrzymywania aktualnej wersji jest już szeroko znany przy utrzymywaniu kodu. Powstało więc wiele systemów kontroli wersji (CVS,SVN,Git,Mercurial ...).

Struktura bazy danych to jednak nie do końca to samo. W bazie danych nie mamy kodu który w danej wersji jest zawsze taki sam. Jest coś więcej. Są dane. Problem polega więc nad tym, aby przy aktualizacji schematu bazy danych do najnowszej wersji nie "zepsuć" bazy. Na czym więc nam dodatkowo zależy :

  • Aby schemat otrzymał nowe elementy(pola, tabele, klucze, triggery) z aktualnej wersji
  • Aby nie utracić danych
  • Aby dane nadal były spójne

Dodatkowo, na samym środowisku docelowym wykonywane są czasami jakieś dodatkowe zmiany. Dodawane są klucze, czasami jakieś małe zmiany w polach. Zależy nam więc jak zwykle na zmniejszeniu liczby zależności przy wprowadzaniu zmian Zależy nam też na uodpornieniu procesu aktualizacji na błędy związane z rozbieżnościami oczekiwanego i otrzymanego na początku schematu.

Podejście chałupnicze

Przy małej ilości zmian lub rzadkich wdrożeniach na wersję produkcyjną możemy to załatwić ręcznie.

  • Możemy to zrobić porównując ręcznie schemat z bazy z tym z pliku schematu
  • Możemy przy zmianach tworzyć proste pliki tworzące wykonujące zmiany i podsyłać wraz z kodem
  • Możemy zrzuć dane do pliku, wyczyścić bazę, wrzucić nowy schemat i dane z pliku

Wszystkie metody powyższe dają efekty. Są jednak wrażliwe na błędy, i często wymagają odłączenia aplikacji od bazy.

Opis narzędzia

Narzędzie, którego od nie dawna używam, to DbDeploy. Pozwala ono na zarządzanie schematem bazy danych w oparciu o zbiór skryptów zmieniających ją. Narzędzie napisane jest w Javie, można podpiąć je do narzędzia ANT.

Skrypty te numerowane są na podstawie ich nazwy. 3 pierwsze znaki oznaczają numer, a zarazem kolejność wprowadzania ich do bazy. Są to w zasadzie zwykłe skrypty SQL, jednak przy pomocy odpowiednich komentarzy mamy możliwość tworzenia skryptów cofania zmian.

Narzędzia można używać jako target do narzędzia ant, jak i jako narzędzie command line

Workflow

Przy DbDeploy przyjmuję następujący tryb pracy :

  • Wykonanie poprawki do bazy w języku SQL (nie koniecznie ręcznie, z pomocą mogą przyjść mysql-workbench lub inne narzędzia okienkowe)
  • Zapisanie jej w katalogu ze skryptami używanego przez DbDeploy
  • Nałożenie poprawki na bazę danych przy pomocy DbDeploy

Należy tutaj podać następujące założenia :

  • Po umieszczeniu pliku poprawki i nałożeniu jej nie powinno się jej zmieniać lub usuwać
  • Kolejne zmiany to koniecznie kolejne pliki z poprawkami

Cały katalog wraz z zbiorem skryptów, samym DbDepoy, sterownikiem bazy danych i konfiguracją (build.xml) trzymam pod kontrolą bazy danych. Pozwala to na jednoczesne aktualizowanie kodu i schematu na serwerach docelowych. Przy aktualizacji tych repozytoriów natychmiast można zauważyć pojawiające się pliki z poprawkami do bazy danych. Uruchomienie ant w odpowiednim katalogu, i baza jest już aktualna.

Przykładowa zmiana

Załóżmy, że w bazie danych przechowujemy tabelę z użytkownikami, której struktura zapisana jest w pliku 001-base.sql

CREATE  TABLE IF NOT EXISTS User (
	idUser INT NOT NULL AUTO_INCREMENT ,
	email TINYTEXT NOT NULL ,
	pasword CHAR(40) NOT NULL ,
	salt CHAR(23) NOT NULL ,
	meta TEXT NOT NULL ,
	created DATETIME NOT NULL ,
	lastLogin DATETIME NOT NULL ,
	PRIMARY KEY (idUser) )
	ENGINE = InnoDB|

Aby rozpocząć pracę, uaktualniamy plik build.xml o dane do bazy danych, wykonujemy ant clean w celu utworzenia tabeli do przechowywania stanu schematu.

ert16@mert dbdeploy> ant clean
Buildfile: /home/ert16/dbdeploy/build.xml

create-changelog-table:
		[sql] Executing resource:
 /home/ert16/dbdeploy/lib/createSchemaVersionTable.mysql.sql
		[sql] 2 of 2 SQL statements executed successfully

clean:

BUILD SUCCESSFUL
Total time: 5 seconds

Następnie ustawiamy aktualną wersję bazy danych.

ert16@mert dbdeploy> ant Buildfile: /home/ert16/dbdeploy/build.xml

update-database:
 [dbdeploy] dbdeploy 3.0M3
 [dbdeploy] Reading change scripts from directory /home/ert16/dbdeploy...
 [dbdeploy] Changes currently applied to database:
 [dbdeploy]   (none)
 [dbdeploy] Scripts available:
 [dbdeploy]   1
 [dbdeploy] To be applied:
 [dbdeploy]   1
 [dbdeploy] Applying #1: 001-base.sql...
 [dbdeploy]  -> statement 1 of 11...
 [dbdeploy]  -> statement 2 of 11...
 [dbdeploy]  -> statement 3 of 11...
 [dbdeploy]  -> statement 4 of 11...
 [dbdeploy]  -> statement 5 of 11...
 [dbdeploy]  -> statement 6 of 11...
 [dbdeploy]  -> statement 7 of 11...
 [dbdeploy]  -> statement 8 of 11...
 [dbdeploy]  -> statement 9 of 11...
 [dbdeploy]  -> statement 10 of 11...
 [dbdeploy]  -> statement 11 of 11...

default:

BUILD SUCCESSFUL
Total time: 3 seconds

Chciałbym do tabeli dołożyć pole emailHash, które będzie skrótem sha1 pola email. Realizacja skrótu będzie wykonywana na poziomie aplikacji piszącej do bazy danych. Tworzę więc następującą poprawkę :

ALTER TABLE User 
	ADD COLUMN emailHash CHAR(32)
		CHARACTER SET 'ascii'
		COLLATE 'ascii_general_ci'
		NOT  NULL AFTER lastLogin ,
	ADD UNIQUE INDEX emailHash_UNIQUE (emailHash ASC) |

UPDATE User 
	SET emailHash=md5(email)|

Umieszczam ją w katalogu dbDeploy pod nazwą 002-emailHashField.sql i wykonuję ant

ert16@mert dbdeploy> ant
Buildfile: /home/ert16/dbdeploy/build.xml

update-database:
 [dbdeploy] dbdeploy 3.0M3
 [dbdeploy] Reading change scripts from directory /home/ert16/dbdeploy...
 [dbdeploy] Changes currently applied to database:
 [dbdeploy]   1
 [dbdeploy] Scripts available:
 [dbdeploy]   1, 3
 [dbdeploy] To be applied:
 [dbdeploy]   3
 [dbdeploy] Applying #3: 002-emailHashField.sql...

default:

BUILD SUCCESSFUL
Total time: 3 seconds

Po pewnym czasie, przypominam sobie jednak, że md5 jest już przestarzałe, i warto by było, aby zmienić tą sumę na sha1. Co więc należy zrobić ? Oczywiście, kolejna poprawka.

ALTER TABLE User 
	CHANGE COLUMN emailHash emailHash CHAR(40)
		CHARACTER SET 'ascii'
		COLLATE 'ascii_general_ci'
		NOT  NULL |
UPDATE User SET emailHash=sha1(email)|

Po zapisaniu jako 003-emailhashField-fix.sql i wykonaniu ant baza schemat jest już aktualny.

ert16@mert dbdeploy> ant
Buildfile: /home/ert16/dbdeploy/build.xml

update-database:
 [dbdeploy] dbdeploy 3.0M3
 [dbdeploy] Reading change scripts from directory /home/ert16/dbdeploy...
 [dbdeploy] Changes currently applied to database:
 [dbdeploy]   1, 3
 [dbdeploy] Scripts available:
 [dbdeploy]   1, 3, 4
 [dbdeploy] To be applied:
 [dbdeploy]   4
 [dbdeploy] Applying #4: 003-emailhashField-fix.sql...
 [dbdeploy]  -> statement 1 of 2...
 [dbdeploy]  -> statement 2 of 2...

default:

BUILD SUCCESSFUL
Total time: 4 seconds

Napotkane problemy

Dokumentacji do tego narzędzia nie oceniam dobrze. Czasami jest mi ciężko się w niej odnaleźć.

Konfiguracja DbDeploy jest dość problematyczna. Dołączony skrypt build.xml był dla mnie mało przeźroczysty, co zmusiło mnie do jego przebudowania. Chodziło mi głównie o wyciągnięcie parametrów dostępu do bazy danych na początek pliku i usunięcie niepotrzebnych celów. Do działania wymagana jest również konfiguracja sterownika do bazy danych. Trzeba umieścić ją w odpowiednim miejscu i zmienić konfigurację.

Miałem również pewne problemy przy umieszczaniu w bazie triggerów. Zmusiło mnie to do zmiany parametru delimiter co widać w powyższych skryptach.

Przydatne materiały