13 May 2013

Использование Liquibase без головной боли. 10 советов из опыта реальной разработки

JavaGroovy & Grails
Tutorial
kdpvLiquibase — это система управления миграциями базы данных. Это вторая статья о Liquibase, на этот раз содержащая советы «боевого» использования системы. Для получения базовых сведений подойдет первая статья-перевод «Управление миграциями БД с Liquibase» (ссылка).

Как и многие инструменты, служащие для облегчения жизни разработчиков программного обеспечения, Liquibase имеет «обратную сторону медали», с которой приходится рано или поздно столкнуться.

Вот 10 вещей, которые в определенный момент работы с Liquibase были для меня открытием.

1. Версионность приложения должна быть отражена в структуре папок миграций


Если вы не будете следовать этому правилу, файлы чейнджлогов быстро украсят папку миграций своим количеством и необычными именами.
На данный момент для себя я выработал оптимальную стратегию именования файлов и папок. Вот она:

/db-migrations
    /v-1.0
        /2013-03-02--01-initial-schema-import.xml
        /2013-03-02--02-core-data.xml
        /2013-03-04--01-notifications.xml
        /changelog-v.1.0-cumulative.xml
    /v-2.0
        ...
        /changelog-v.2.0-cumulative.xml
    /changelog.xml

Подробнее:

1.1. Чейнджлоги изменений базы данных должны содержаться в отдельных папках и соответсвуют версиям приложения.

Плюсы:
  • Всегда можно легко сказать, какие изменения схемы данных произошли при переходе с версии на версию приложения
  • Поддерживается аккуратность в папках, не позволяет расслабляться.


1.2. Имейте кумулятивный файл чейнджлогов для каждой версии

Основным файлом миграций является /db-migrations/changelog.xml. В него включаются (тэг «include») только кумулятивные файлы чейнджлогов каждой из версий.

<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
    xmlns="http://www.liquibase.org/xml/ns/dbchangelog">
    <!-- Should be only links to per-version cumulative changelog files. -->
    <include file="v-1.0/changelog-v.1.0-cumulative.xml" relativeToChangelogFile="true"/>
    <include file="v-2.0/changelog-v.2.0-cumulative.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

В свою очередь, в кумулятивные чейнджлоги версий приложений включаются конкретные файлы с чейнджсетами.
Таким образом удается не замусорить главный файл чейнджлогов.

1.3. Соблюдайте правила именования файлов

Правило именования файлов позволяет без просмотра кумулятивных чейнджлогов файлов понять, что за чем следовало.

-<DATE_INCR>-.xml
DATE – дата чейнджлога
DATE_INCR – инкремент файла в конкретную дату (когда несколько файлов добавлены в один день.)
DESCR – описание.
Пример:
2013-03-02--01-initial-schema-import.xml

2. Писать чейнджсеты непросто, поймите это


Любой чейджсет (не забываем про его rollback секцию) может запороть вам миграцию.
Писать чейнджсеты для серьезного приложения сложно, как бы нам не хотелось иного. Особенно остро это чувствуется, если вы мигрируете данные.

Если у вас есть несколько сред развертывания, и если у вашего приложения уже есть PROD-среда, требуется двойная концентрация внимания, усидчивость, тестирование на среде с похожими данными, тестирование откатов изменений.

3. Лучше много маленьких чейнджсетов, чем один большой


Один чейнджсет — одна транзакция. Если данных меняется много, лучше, чтобы транзакции были поменьше.
Работайте над ними изолированно, не добавляйте несколько изменений в один чейнджсет, думайте над содержимым тэга rollback. Лучше много простых и даже однотипных чем один сложный и невнятный.

4. Чейнджлоги, изменяющие схему БД, должны быть атомарными


Не могу сказать про многие базы данных, но по крайней мере в Oracle и MySQL DDL-операторы не включаются в транзакцию.
Это значит, что если при запуске чейнджсета произошка ошибка, DDL-операторы, выполненные до ошибки останутся в базе.
Пример (так неправильно):

<changeSet id="2013-03-02-initial-schema-import-1" author="eg"> <createTable tableName="HOUSES"> <column name="ID" type="BIGINT" /> </createTable> <addPrimaryKey tableName="HOUSES" columnNames="ID" /> </changeSet>

Если при добавлении индекса произойдет ошибка, таблица HOUSES не будет удалена!
Как избежать боли? Каждое изменение схемы БД должно жить в своем чейнджлоге.

Пример (так правильно):

<changeSet id="2013-03-02-initial-schema-import-1" author="eg">
    <createTable tableName="HOUSES">
        <column name="ID" type="BIGINT" />
    </createTable>
</changeSet>
    
<changeSet id="2013-03-02-initial-schema-import-2" author="eg">
    <addPrimaryKey tableName="HOUSES" columnNames="ID" />
</changeSet>


5. Путь запуска скрипта миграции имеет значение


Посмотрите на две команды запуска скрипта миграции схемы БД

cd c:/mychangelogs/
liquibase --url=jdbc:mysql://localhost:3306/liquiblog --driver=com.mysql.jdbc.Driver --username=root --password="" --changeLogFile=db.changelog-0.1.0.xml update

liquibase --url=jdbc:mysql://localhost:3306/liquiblog --driver=com.mysql.jdbc.Driver --username=root --password="" --changeLogFile=c:/mychangelogs/db.changelog-0.1.0.xml update

Кажется, что вторая команда не должна накатывать никаких изменений. Но это не так, Liquibase хранит путь к файлу чейнджлога в столбце FILENAME таблицы DATABASECHANGELOG.
В связи с этим фактом, при втором запуске Liquibase будет считать все чейнджсеты новыми. Это может сломать вашу схему данных!

Как избежать боли? Всегда запускайте чейнджлоги с использованием относительных путей, из одной и той же директории.

6. Условия precondition не влияют на контрольную сумму чейнджлога


Это хорошая новость для любителей ошибиться (то есть всех нас, рано или поздно). Иногда это позволяет изменить условия применения чейнджсета, таким образом, «исправив прошлое».

7. Liquibase не так уж хорош для часто меняющихся данных


Однажды мы попытались мигрировать много однотипных, но меняющихся от версии к версии данных конфигурации через чейнджлоги. Получилось очень грустно – постоянно кто-то из команды ошибался, ошибку уезжала на тестовую среду, откатывать ее надо было полностью вручную (помните контрольную сумму чейнджсетов?).
Безрадостную картину пришлось исправлять трезвым подходом к конкретной ситуации. Liquibase был оставлен для изменений схемы данных, а конфигурационные данные с каждой версией наливались Groovy-скриптом, имеющим properties-файл в качестве исходника.

8. Не верьте скриптам автоматической генерации чейнджсетов


Liquibase умеет генерировать
  • чейнджсеты по имеющейся схеме базы данных,
  • разницу, произошедшие в базе данных по сравнению с имеющимися чейнджсетами (применимо для Grails).

К этим удобствам надо относиться с осторожностью. Это просто небольшое избавление от рутины, не более. Все чейнджсеты надо просматривать, понимать и проверять. Вы не хотите исправлять логическую ошибку (нагенеренную глупой утилитой) уехавшую в PROD, правда?

9. Используйте XML, а не DSL


Иногда хочется «облегчить» жизнь и отказаться от XML, начав использовать более краткий DSL (groovy, yaml, json)
Все это очень хорошо до тех пор, пока вам не захочется иметь:
  • Автодополнение в IDE
  • Автоматическую проверку формальной верности документа по схеме данных

Ну и зачем отказываться от таких полезных вещей в пользу краткости? Видимо, я не настолько хороший программист, чтобы отказаться от автодополнения ради немногим лучшей читаемости кода.

10. Изучайте чужой опыт


Хотя большую часть пунктов этой статьи я познал на собственном опыте, официальный гайд по лучшим практикам использования Liquibase улучшает восприятие инструмента (ссылка)

Успешных миграций!
Tags:liquibaseмиграция бдjavadatabase migrationsсоветtips and tricks
Hubs: Java Groovy & Grails
+17
74.6k 149
Comments 9
Popular right now
Java-разработчик
from 200,000 to 250,000 ₽Страховая Компания СогласиеМоскваRemote job
Тренер JAVA
from 350,000 to 400,000 ₽ИЦ «Ай-Теко»Remote job
Java developer
from 240,000 to 270,000 ₽ОТП БанкМоскваRemote job
Java разработчик
from 140,000 to 230,000 ₽МойСкладМоскваRemote job
Тестировщик/ QA Engineer
from 50,000 ₽NatlexПетрозаводскRemote job
Top of the last 24 hours