
Imaginez Oracle DBA. Il a déjà plus de trente ans, il est un peu en surpoids, porte un gilet, il a un jeton d'accès secret à toutes les bases accrochées à son cou, et dans un résumé d'une demi-page des certifications qu'il a passées. Samedi Grande journée de sortie. Climax. Il est temps de reporter les modifications dans la base de données. Il tape sqlplus, appuie sur ENTRÉE, et quelque part sur l'écran noir dans le vide, des kilomètres de commandes SQL se précipitent. Tout comme dans les guerres des étoiles. Cinq minutes plus tard, tout est prêt. Une heure plus tard, la sortie est terminée. Le travail est fait, la journée a été un succès. Maintenant, vous pouvez avoir quelques bières.
Une autre chose est lundi. Il s'avère que certaines commandes n'ont pas été exécutées en raison d'erreurs, ce qui n'a toutefois pas arrêté le script dans sa poursuite effrénée du vide noir. La tâche déjà difficile de déterminer ce qui est brisé est compliquée par une certaine pression de la part des dirigeants. En général, lundi n'a pas fonctionné.
Bien sûr, c'est une histoire fictive. Cela n'est jamais arrivé à personne. Au moins, cela ne se serait pas produit si le travail de modification du schéma de la base de données avait été organisé par le biais de migrations.
Qu'est-ce qu'un outil de migration de base de données?
L'idée de gérer les modifications de schéma de base de données via des migrations est extrêmement simple:
- Chaque modification est émise dans un fichier de migration distinct.
- Le fichier de migration comprend des modifications directes et inverses.
- L'application des migrations à la base de données est effectuée par un utilitaire spécial.
L'exemple de migration le plus simple:
Cette approche offre de nombreux avantages par rapport à l'organisation des modifications dans un fichier SQL commun. La simple absence de conflits de fusion en vaut la peine.
Il est d'autant plus surprenant que l'approche elle-même a gagné en popularité relativement récemment. Il semble que le framework Ruby on Rails, dans lequel l'outil de migration était à l'origine intégré, était la principale renommée de l'approche, c'était fin 2005. Martin Fowler, 2003, a
écrit un peu plus tôt sur cette approche, mais le fait est que le développement n'a commencé à adapter activement l'utilisation du système de contrôle de version qu'au début de ce siècle. En 2000, le premier paragraphe du
test de Joel Spolsky était
"Utilisez-vous le contrôle de code source?" - cela suggère que tout le monde n'utilisait pas les systèmes de contrôle de version à l'époque. Mais nous étions distraits.
Huit ans avec MyBatis Migrations
Chez Wrike, nous avons commencé à utiliser l'approche de changement de base de données lors des migrations en 2010, le 29 mars à midi et demi. Depuis lors, nous avons implémenté 1 440 migrations, contenant 6 436 modifications directes et 5 015 inverses. En général, nous avons acquis une certaine expérience en utilisant l'outil de
migration MyBatis en conjonction avec
PostgreSQL .
Bref, nous n'avons jamais regretté. S'il vous arrive de ne pas utiliser Migrations ou quelque chose de similaire, il est temps de commencer. Oui, le
Pentium 4 est également obsolète.
Mais c'est ennuyeux de parler des mérites de quoi que ce soit, passons directement aux difficultés.
Spécificités de PostgreSQL
Il n'y a aucune difficulté à écrire des migrations pour Postgres, sauf pour deux:
- Vous ne pouvez pas créer d'index,
- Vous ne pouvez pas ajouter de colonnes NOT NULL.
Non, en fait, c'est possible, mais pas de manière tout à fait évidente. Lors de la création d'un index, vous devez toujours spécifier
CREATE INDEX CONCURRENTLY , sinon vous interrompez la production, car Postgres verrouille la table pendant la durée de la création de l'index, ce qui peut être assez long. Bien sûr, les développeurs l'oublient une fois, vous devez toujours garder cette subtilité à l'esprit. Ici, on pourrait écrire un test. Mais ce n'est qu'un léger inconvénient.
La création de colonnes NOT NULL est plus délicate, ici vous devez effectuer une modification en quatre étapes:
- Créez une colonne NULL (dans Postgres c'est gratuit).
- Définissez la colonne DEFAULT sur une valeur.
- Dans une boucle, mettez à jour progressivement les valeurs NULL dans DEFAULT.
- Définissez SET NOT NULL.
Le plus gros problème ici est dans le troisième paragraphe. Les valeurs NULL doivent être mises à jour par portions, car
UPDATE some_table SET some_column='' WHERE some_column IS NULL
; bloquera la table, comme c'est le cas avec l'index, avec les mêmes conséquences. Et les migrations ne peuvent exécuter que des commandes SQL, de tels scripts doivent donc être mis en production manuellement. Plaisir en dessous de la moyenne. Maintenant, si un cycle pouvait être écrit dans Migrations, il n'y aurait aucun problème. Peut-être que cela est implémenté via des
crochets .
La création d'un index
UNIQUE
et la modification d'une
PRIMARY KEY
nécessitent également des compétences, mais ces opérations sont relativement rares à approfondir.
Spécificités du cluster
L'outil de gestion de la migration de base de données est bon tant que vous n'avez qu'une seule base de données. D'autant plus amusant si vous avez plusieurs bases. Surtout si vous avez plusieurs types de bases de données, chacune ayant plusieurs instances.
Par conséquent, après
git pull
développeur doit transférer les modifications à la première instance de la première base de données, puis à la deuxième instance, puis à la première instance de la deuxième base de données et ainsi de suite - un tel principe. Ici, il est juste d'écrire un utilitaire pour gérer l'utilitaire de gestion de la migration de la base de données. Automatisation totale.
Jongler avec les rôles
Ce n'est un secret pour personne que les rôles en tant qu'entités ne vivent pas au niveau d'une base de données distincte, mais au niveau de l'ensemble du serveur de base de données, du moins dans Postgres. Dans ce cas, vous devrez peut-être spécifier
REVOKE INSERT ON some_table FROM some_role
; Il est toujours possible de s'attendre à ce que les rôles soient préconfigurés en production, mais pour le développement ou la mise en scène, c'est déjà difficile. En même temps, en cours de développement, bien sûr, toutes les bases de données existent sur le même serveur local, vous ne pouvez donc tout simplement pas écrire
CREATE ROLE
dans la migration, et
IF NOT EXISTS
pas pris en charge. Tout est résolu simplement:
DO $$ BEGIN IF NOT EXISTS (SELECT * FROM pg_roles WHERE rolname = 'some_role') THEN CREATE ROLE "some_role" NOLOGIN; END IF; END; $$;
Regardez-le! Je les attrape et les jette, attrape et jette, c'est si simple.
Un peu de réalité de développement
Les développeurs font des erreurs, et même dans les migrations SQL, cela se produit. Habituellement, des erreurs peuvent être constatées dans la revue, mais elles peuvent également être inhabituelles. Si nous parlons de changements directs, alors les jambages là-bas n'atteignent toujours pas la production - il y a trop d'étapes de vérification. Mais avec les changements inverses, des incidents peuvent survenir. Pour éviter les erreurs dans la migration UNDO, lors du test de la migration, vous devez effectuer non seulement
./migrate up
, mais
./migrate up
, puis
./migrate down
, puis à nouveau
./migrate up
. Ce n'est rien de compliqué, il vous suffit de vous assurer que quarante développeurs le font toujours. Dans le bon sens, l'utilitaire pourrait effectuer automatiquement un tel combo pour l'environnement de développeur.
Environnements de test
Si l'environnement de test est de courte durée: disons que vous créez un conteneur, initialisez la base de données et exécutez des tests d'intégration, il ne devrait pas y avoir de problème. Nous
./migrate bootstrap
, puis
./migrate up
, et vous avez terminé. C'est juste lorsque le nombre de migrations dépasse mille, ce processus peut être retardé. C'est dommage quand la base de données est initialisée plus longtemps que les tests ne s'exécutent. Nous devons esquiver.
Avec des environnements à longue durée de vie, c'est encore plus difficile. QA, vous savez, ils n'aiment pas voir une base de données impeccablement propre lorsqu'ils viennent travailler. Je ne sais pas pourquoi il en est ainsi, mais le fait est le fait. Ainsi, l'état des bases utilisées dans les tests manuels doit être maintenu en intégrité. Et ce n'est pas toujours facile.
La subtilité est que si la migration est appliquée à la base de données, l'identifiant de migration y est écrit. Et si le code de migration a été modifié ultérieurement, la base de données ne sera pas affectée. Si les modifications ne sont pas critiques, le code peut réussir à atteindre la production. Rssynchron. Bien sûr, c'est une honte. Le premier principe du travail avec les migrations est de ne jamais changer les migrations écrites, mais d'en créer toujours de nouvelles. Mais parfois j'ai envie de tâtonner - je vais changer un peu ici, rien ne se cassera, car la vérité est. Bien sûr! Allez-y!
Si des migrations étaient signées après l'examen, il serait possible d'interdire l'application de projets pour la mise en scène. Et il serait possible d'enregistrer non seulement l'identifiant de migration dans le
changelog
, mais également la
checksum
- également utile.
Retour tel qu'il était
Un virage particulièrement insidieux se produit lorsqu'une tâche est annulée: ils l'ont fait, l'ont fait et ont changé d'avis. C’est une situation normale. Une fois que le code n'est plus nécessaire, la branche doit être supprimée. Et il y a eu la migration ... et elle est déjà en scène ... ah, ... oups. Une bonne raison de vérifier si vous pouvez restaurer la sauvegarde du référentiel. Tout en rappelant qu'il y avait peut-être plus facile.
Dans le même temps, la migration est du texte. Et il serait possible de sauvegarder ce texte là, dans le
changelog
. Ensuite, si la migration à partir du code a disparu, peu importe pour quelles raisons, elle pourrait toujours être annulée. Et même automatiquement.
Refaire encore
La section UNDO est définitivement nécessaire. Mais pourquoi l'écrire? Bien sûr, il existe des cas accrocheurs, mais la plupart des modifications sont
CREATE TABLE
ou
ADD COLUMN
ou
CREATE INDEX
. Pour eux, l'utilitaire pouvait générer automatiquement des opérations inverses, directement à l'aide de code SQL. Bien sûr, il y a une spécificité.
CREATE TABLE ${name}
- c'est une équipe si spéciale, soudainement non standard. Oui, et pour générer
DROP TABLE ${name}
, vous devez pouvoir analyser l'expression jusqu'au troisième mot. Bien qu'il s'agisse en général d'une tâche technique pleinement réalisable. Peut être sorti de la boîte.
Conclusion
Bien sûr, je trouve à redire. MyBatis Migrations a été conçu comme un utilitaire simple et universel, minimalement lié aux spécificités des bases de données. Et elle est plus que se justifier. Mais il semble que quelques petites améliorations le rendraient beaucoup mieux, surtout lorsqu'il est utilisé sur de longues distances.
-
Dmitry Mamonov / Wrike