Migrer son application Rails de MySQL à PostgreSQL
“migrer son application Rails d’une base de donnée à une autre c’est simple ! il suffit de changer le fichier de configuration kivabien : config/database.yml”
Oui, mais non. C’est aussi simple que ça si et seulement si l’application en question n’est pas encore en production, et que les données dans la bases ne sont pas importante. Sinon il faut aussi migrer les données, et c’est là que ça fait mal.
TODO
Grâce à active record on peut faire une appli très portable sans coder une ligne de SQL, et ça on aime. Donc si on a été sage, pas besoin de toucher au code de notre application, Il n’y a “que” les données à migrer.
J’ai essayé plusieurs solutions (des mysqldump --compatible=postgresql sensé être compatible avec PostgreSQL, des dumps XML etc.) sans succès, jusqu’à ce que je tombe sur ar-backup (ActiveRecordBackup) qui permet de sauver/restorer les données d’une application Rails.
Préparer le terrain
- Tout d’abord, on fait un backup de la base MySQL, “juste au cas où” (remplaçez user , secret et db par votre nom d’utilisateur MySQL, votre mot de passe pour cet utilisateur et le nom de la base de donnée)
$ mysqldump --user=user --pasword=secret db > mysqldump.sql- Puis on crée notre user pour utiliser PostgreSQL et la base de donnée.
$ createuser --no-createdb --no-createrole --login --pwprompt --encrypted user
$ createdb --encoding=utf8 --owner=user dbInstaller ar-backup
On va installer la version de shingara, car j’ai eu le problème suivant avec la version “officielle” :
[...]
rake aborted!
PGError: ERROR: syntax error at or near "ENABLE" at character 27
: ALTER TABLE "schema_info" ENABLE TRIGGER ALL;
ALTER TABLE "contents" ENABLE TRIGGER ALL;
ALTER TABLE "events" ENABLE TRIGGER ALL;
[...]installation :
$ ruby script/plugin install http://ar-backup.googlecode.com/svn/branches/ar-backup-shingara/et après ceci, un petit rake --tasks nous montre :
rake backup:db:dump
rake backup:db:extract_content
rake backup:db:extract_schema
rake backup:db:load
[...]Utiliser ar-backup
(Si votre application est en train de tourner, il vaut mieux la stopper jusqu’à la fin de la manipulation).
Avant de sauver le contenu de notre base de donné, on va faire un peu de ménage. Si vous avez choisi d’avoir les sessions Rails dans la base de donnée plutôt que sur le système de fichier, il faut nettoyer la table des sessions (inutile de les sauver). adapter le RAILS_ENV=production si nécessaire :
rake db:sessions:clear RAILS_ENV=productionSans changer config/database.yml on lance :
$ rake backup:db:dump RAILS_ENV=productionOn a maintenant un dossier backup/production/build_x (ou build_svnrev si vous utiliser Subversion pour gérer votre application) rempli de fixture en YAML (Miam!) avec les données de votre base de donnée MySQL.
Ok good, maintenant on change le fichier config/database.yml pour switcher à PostgreSQL :
production:
adapter: postgresql
database: db
username: user
password: secret
encoding: utf8puis on load le backup dans PostgreSQL (ne pas oublier de démarrer le démon si c’est pas déjà fait)
$ rake backup:db:load RAILS_ENV=productionEt pouf ! On a fini ! On relance le serveur de notre application et là … patatra. Si on a les sessions dans la base de donné, ça va craquer tout de suite. Sinon, il est impossible de créer (enfin, de sauver) de nouveaux objets dans la base de donnée. On voit dans les logs :
ActiveRecord::StatementInvalid (PGError: ERROR:
duplicate key violates unique constraint blablabla...Pas joli. En faite, on a bien toutes les données en place, mais le compteur des id n’est pas à jour.
Pour motivés il faut voir comment cela fonctionne avec PostgreSQL, regardez du coté des séquences etc. J’ai des objets user dans ma db, inspectons un peu :
$ psql -U user db
Welcome to psql 8.0.13, the PostgreSQL interactive terminal.
Type: \copyright for distribution terms
\h for help with SQL commands
\? for help with psql commands
\g or terminate with semicolon to execute query
\q to quit
db=> SELECT last_value FROM users_id_seq ;
last_value
------------
1
(1 row)
db=> SELECT max(id) FROM users;
max
-----
7
(1 row)
db=>\qOuch. pour fixer cela, une simple commande suffirait : ALTER SEQUENCE users_id_seq RESTART WITH 8 mais le vrai problème, c’est qu’il peut y’avoir beaucoup de séquences à changer.
Comme j’avais pas envie de tout faire à la main (et que ça serait pas très utile et sympa de vous dire maintenant qu’il faut tout faire à la main), j’ai gribouillé un petit script en Ruby pour faire tout ça à ma place (il faut installer la gem ruby-postgres pour pouvoir utiliser le script. De toute façon il faut cette gem pour utiliser Rails avec PostgreSQL, donc si ce n’est déjà fait : gem install ruby-postgres).
$ ruby pgsql_id_fix.rb --help
usage : psql_migration.rb [opts] dbname
-h, --help display this help
-u, --user USER PostgreSQL username
-w, --password PASS PostgreSQL password
-v, --verbose verbose mode
-c, --change alter dbRien de très complexe : Il faut rentrer les paramètres de connexion à la base de donnée, on peut activer le flag verbose pour qu’il parle beaucoup, et surtout le script ne change rien dans la base de donnée à moins que l’option -c|--change soit activée.
On le lance une fois pour voir ce qu’il veut changer :
$ ruby pgsql_id_fix.rb -u user -w secret db
-- max(id) of contents > last_value of contents_id_seq : fix needed
-- max(id) of events > last_value of events_id_seq : fix needed
-- max(id) of forums > last_value of forums_id_seq : fix needed
[...]
-- max(id) of youtubelinks > last_value of youtubelinks_id_seq : fix neededSi tout semble ok on le lance pour de bon :
-- max(id) of contents > last_value of contents_id_seq : fix needed
-- fixed contents_id_seq
[...]
-- max(id) of youtubelinks > last_value of youtubelinks_id_seq : fix needed
-- fixed youtubelinks_id_seqVoilà ! ça devrait jouer maintenant !
Limitations
- Même avec cette technique j’ai eu des problèmes avec des données binaires (Blob) dans la base de donnée. J’ai du sortir les données binaires et les réinserer à coups de script/console. Si vous avez des données binaires, bonne chance.
- le script de migration que j’ai fais ne marche qu’avec PostgreSQL >= 8.2. A l’heure où j’écris on dirait qu’il n’y a que gentoo qui a un version antérieur à 8.2 en “stable”. Si vous êtes sous gentoo, la migration à PostgreSQL 8.2 est simple : on dump, on update, on restore.
# DUMP
$ pg_dumpall -U postgres > dump.sql
$ /etc/init.d/postgresql stop
$ mv /var/lib/postgresql /var/lib/postgresql_old
# UPDATE (adapter ~x86 à votre architecture)
$ echo '=dev-db/postgresql-8.2.4-r1 ~x86' >> /etc/portage/package.keywords
$ echo '=dev-db/libpq-8.2.4 ~x86' >> /etc/portage/package.keywords
$ emerge --unmerge libpq postgresql
$ emerge postgresql
$ emerge --config =postgresql-VERSION
# on rebuild tout ce qu'on a cassé, ruby-postres surtout :)
$ revdep-rebuild
# RESTORE (la config + les données)
$ /etc/init.d/postgresql start
$ psql -U postgres < dump.sql
$ cp /var/lib/postgresql_old/data/pg_hba.conf /var/lib/postgresql/data/pg_hba.conf
$ cp /var/lib/postgresql_old/data/postgresql.conf /var/lib/postgresql/data/postgresql.conf
$ /etc/init.d/postgresql restart
# OUF ;)Posted in admin | 2 comments |