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

  • 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 улучшает восприятие инструмента (ссылка)

Успешных миграций!
  • +17
  • 48,2k
  • 9
Поделиться публикацией

Похожие публикации

Комментарии 9
    0
    Спасибо, хорошая статья. За пункт №8 голосую дополнительно «за».

    ПС
    ИМХО немного картинка смутила, т.к. часто используется в такой тематике. Сразу возникла мысль: «а не старая ли это статья?». Но прочев несколько первых предложений все понял.
      0
      Эти практики теперь можно просто показывать новым членам команды (что я и делаю), и с ходу экономить человекомесяц. Оцениваю из предыдущего опыта.

      КДПВ и правда из популярных. Поищу позабористее.
      0
      Эти практики теперь можно просто показывать новым членам команды (что я и делаю), и с ходу экономить человекомесяц. Оцениваю из предыдущего опыта.

      КДПВ и правда из популярных. Поищу позабористее.
        +1
        За пост плюс, за содержание немного поворчу.

        Итак:
        1. Порядок в именовании файлов с ченджсетами это очень хорошо, как в прочем любой порядок. Возможно в имени ченджсета стоит отражать тип БД для которого он писан.
        2. Ролбеки для ченджсетов это полезно и клево, но не стоит забывать, что надо вручную тестировать выполнения ченджсета и его откат, это обязательно!
        3. В целом маленькие ченджсеты это опять про порядок в их структуре. Но есть нюансы, о них ниже.
        4. Вам действительно не жаль своего времени для каждой сущности писать ченджсет? Я вам не завидую!
        5. Я предпочитаю помещать ченджсеты в класспатх, и брать их относительно корня класспатх. Тога мы перестаем зависеть от операционных систем и других странностей.
        6. Менять прошлое не всегда хорошая идея. В большинстве случаев надо делать отдельный ченджсет. Но тут не все так просто.
        7. Согласен хранить данные в ченджсетах не очень разумная идея. Они приводят к увеличению объема ченджсетов, ну и не стоит забывать что миграции ведь для них тоже придется писать.
        8. Пункт тоже логичен. Никому верить нельзя.
        9. Я бы дополнил пункт, и сказал что XML ченджсеты зло, возможно хуже чем DSL. И SQL тут самое то. Удобно, быстро, прозрачно.
        10. Опыт конечно хорошо, но вы бы видели исходный код liquibase. Например логер туда впилить весело :)

        И в дополнении:
        1. Может быть xml описание зло? Вас действительно не напрягает это писать и поддерживать? Просто я сторонник обычного нативного sql.
        2. Иногда полезно делать ченджсет со всей структурой БД, куча маленьких на новой БД применяется не быстро порой. И опять я намекаю на sql ченджсеты тут.
        3. Чем больше размер sql ченджсета, тем дольше считается контрольная сумма. Это еще одна из причин почему хранить данные в ченджсетах зло. Помню мегабайт текста обрабатывался за секунд 20.
        4. Есть аналоги, и надо думать и пробовать что лучше. Например flyway — flywaydb.org/ (там есть таблица сравнения на подумать).
          0
          4. А как это делаете вы?
          5. У меня они там же — изменения накатаваются при старте приложения. Конечно, это спорно (что, если прав нет у пользователя? Что, если надо вручную что-то накатить?). Вообще вся идея хранения пути к файлу и завязке на него в ликвибейс кажется мне непродуманной, ведь уже есть чексам…

          Дополнения:
          1. Нативный SQL конечно неплохо, но Liquibase берет на себя многие вопросы синтаксиса. Я говорю «создай индекс», а уж как он будет создаваться для конкретной базы — не мое дело (если все корректно создалось).
          таким образом, я пишу генерализированный код, а если надо, добавляют хаков для конкретной базы, через прекондишны.
          2. В бест практисас об этом сказано, что, мол, ребята, если у вас 20 версия приложения, и насчет отката до 18 версии речь уже не пойдет — сливайте все чейнджсеты, которые были до 18 версии в один большой init-SQL, и не будете долго ждать. Таких ухищрений, к счастью, я пока не делал.
          4. Вы пробовали? Что лучше?
            0
            4. Делаю кошерный работающий скрипт и использую БД postgresql где есть транзакции в DDL — wiki.postgresql.org/wiki/Transactional_DDL_in_PostgreSQL:_A_Competitive_Analysis
            5. Согласен идея с путями не очень, я бы предпочел внутренние идентификаторы ченджсетов. А чексуммы это отдельный ад, нафиг их.

            Дополнения:
            1. Вы до сих пор верите в волшебные свойства библиотек? У вас всегда работает правильно конвертация xml в sql скрипт? Вам не надо на горячую писать упдейты, а потом вписывать их в ченджсеты? Опять я вам завидую :)
            4. Я так и не дошел реального использования флайдб. Вроде бы у них роллбеков не было, и это немного печалило. Хотя есть надежда что там код и реализация, не такое гавно как в ликвидбейзе.
          0
          --комментарий удален---
            0
            По поводу пункта 9 — мне лично сильно не хватало макросов. Например, у нас была практика забивать словари через csv-файлы. Очень хотелось макрос, который принимал бы имя таблицы и имя файла, чтобы раз за разом не копипастить громоздкие конструкции.
              0
              Использую в своем проекте для MSSQL форматированные sql-файлы, по мне так очень удобно!
              1. logicalFilePath:path-independent решает вопрос с перенакатом при разных расположений из п.5
              2. splitStatements:true endDelimiter:GO решает вопрос с поддержкой инструкции GO

              Пример файла миграции
              \migrations\20171207_WL-759_AutoLock\02_comon.Setting_Data.sql
              --liquibase formatted sql
              --changeset rvaliullin:20171207_WL-759_AutoLock_02_MergeTable_CommonSettings logicalFilePath:path-independent splitStatements:true endDelimiter:GO stripComments:false context:data
              MERGE common.Setting as [target]
              USING
                  (
                      VALUES
                          (N'ComplaintCountForAutoLock', N'10')
                  ) as [source] (SettingCode, SettingValue)
                      ON [source].SettingCode = [target].SettingCode
              WHEN MATCHED THEN
                  UPDATE
                  SET
                      SettingValue = [source].SettingValue
              WHEN NOT MATCHED BY TARGET THEN
                  INSERT
                  (
                      SettingCode,
                      SettingValue
                  )
                  VALUES
                  (
                      [source].SettingCode,
                      [source].SettingValue
                  );
              GO
              

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

              Самое читаемое