Автоматическое создание миграций Liquibase для PostgreSQL +8


Работа с Liquibase всегда приносит уверенность в том, что объекты БД находятся в том состоянии, в котором ты этого хочешь. Именно эта уверенность и делает системы миграций баз данных столь популярными.

Сегодня я хочу вам рассказать, как сделать работу с liquibase немного удобнее. Если вы пишите миграции вручную, а вашей целевой базой данных является PostgreSQL, то прочитайте эту статью, уверен, время потраченное на чтение статьи окупится с лихвой.

Логотип Liquibase и pgCodeKeeper


Я надеюсь, что вы уже знакомы с работой Liquibase, поэтому лишь в двух словах опишу как работает система. Для того, чтобы внести изменения в БД, вам необходимо создать файл миграции (changeset), ссылку на который нужно будет указать в файле журнала изменений (changelog), после чего миграция может быть успешно применена к целевой БД. Неоспоримым и очевидным преимуществом подобного подхода является возможность выполнения отката сделанных изменений.

На мой взгляд, у liquibase есть лишь один существенный недостаток — автоматический откат изменений работает лишь только в том случае, если миграция описана в виде файла XML. Этот момент омрачает головы как разработчиков выполняющих изменения в БД, так и администраторов баз данных выполняющих инспекцию изменений. Разметка XML далеко не так хороша для восприятия, в отличие старого и хорошо всем известного SQL. Liquibase, к счастью, позволяет писать миграции и на SQL, только лишь с одним досадным недостатком — автоматический откат теперь не работает и секцию отката нужно писать вручную.

Сегодня мы исправим это недоразумение и научимся создавать файлы миграций для liquibase, во-первых на SQL, во-вторых с секцией отката, в-третьих эти файлы будут формироваться автоматически. Не верите? Сейчас я всё подробно расскажу.

Создавать миграции мы будем следующим образом — у нас будут две базы данных, одна будет условно для разработки, другая боевая (стейдж, тест — выберите по вкусу). Мы будем вносить изменения в девелоперскую БД используя любимый инструмент, потом сравним базы данных, сформируем миграцию, которую и развернем в целевой базе данных (у нас мы её будем считать боевой).

Все команды ниже будут отображены с учетом работы в Linux shell. Для генерации секции файлов миграции нам потребуется pgCodeKeeper, а для формирования секции rollback наличие в системе утилиты sed.

Давайте условимся, что у нас есть две базы данных — dbdev и dbprod, в dbdev мы вносим изменения вручную, а в dbprod изменения попадают с помощью Liquibase.

Я создам в /tmp каталог migration в котором и буду выполнять всю работу, разумеется вы можете использовать тот каталог, который пожелаете. PostgreSQL у меня уже установлен на локальном компьютере, и именно с ним я и буду проводить работу.

Создаём базы данных для работы:

$ mkdir /tmp/migration
$ cd /tmp/migration/
$ createdb dbdev
$ createdb dbprod

Загружаем драйвера JDBC для PostgreSQL, дистрибутивы Liquibase и pgCodeKeeper

$ wget https://jdbc.postgresql.org/download/postgresql-42.1.3.jar
$ wget https://github.com/liquibase/liquibase/releases/download/liquibase-parent-3.4.2/liquibase-3.4.2-bin.tar.gz
$ wget http://pgcodekeeper.ru/cli/release/pgCodeKeeper-cli-3.11.4.201707170702.zip

Разархивируем liquibase и pgcodekeeper в текущий каталог, вы конечно же распаковать их в выделенные каталоги.

$ tar xzvf liquibase-3.4.2-bin.tar.gz
$ unzip pgCodeKeeper-cli-3.11.4.201707170702.zip

В текущем каталоге создайте файл настроек для liquibase — liquibase.properties со следующим содержимым:

driver: org.postgresql.Driver
classpath: ./postgresql-42.1.3.jar
url: jdbc:postgresql:dbprod
username: user
password: topsecret
changeLogFile: db.changelog.xml

Обращаю внимание, что это настройки для Liquibase, т.е. описывайте в нём соединение с БД dbprod, на которую мы будем развертывать наши миграции. Имя пользователя и пароль установите в свои значения, у меня имя пользователя — ags и в дальнейших сообщения вывода команд будет встречаться как раз это имя. В текущем каталоге создаем файл db.changelog.xml со следующим содержимым:

<?xml version="1.0" encoding="UTF-8"?>

<databaseChangeLog
  xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
         http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
 
</databaseChangeLog>

Проверяем, готов ли liquibase к работе:

$ ./liquibase status
ags@jdbc:postgresql:dbprod is up to date
Liquibase 'status' Successful

Отлично, половина дела сделано, осталось совсем малость. Проверяем, работает ли pgCodeKeeper.

$ ./pgcodekeeper-cli.sh jdbc:postgresql:dbdev jdbc:postgresql:dbprod

если команда не выдала никакого сообщения — значит всё хорошо. Обратите внимание, если ваш инстанс постгреса настроен на другом хосте или порту или используется доступ по паролю, то вы должны сформировать соответствующую JDBC URL для вашей базы данных. Как правильно его сформировать читаем здесь.

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

Создадим таблицу в БД dbdev:

[ags@saushkin-ag:/tmp/migration] $ psql dbdev
psql (9.6.3, сервер 9.5.7)
Введите "help", чтобы получить справку.

(ags@[local]:5432) 16:08:43 [dbdev]  =# create table users (id serial primary key, name text);                                                        
CREATE TABLE
Время: 20,708 мс
(ags@[local]:5432) 16:09:16 [dbdev] * =# commit;
COMMIT
Время: 6,913 мс

Проверяем, может ли pgCodeKeeper найти отличия.

$ ./pgcodekeeper-cli.sh jdbc:postgresql:dbdev jdbc:postgresql:dbprod
CREATE SEQUENCE users_id_seq
        START WITH 1
        INCREMENT BY 1
        NO MAXVALUE
        NO MINVALUE
        CACHE 1;

ALTER SEQUENCE users_id_seq OWNER TO ags;

CREATE TABLE users (
        id integer DEFAULT nextval('users_id_seq'::regclass) NOT NULL,
        name text
);

ALTER TABLE users OWNER TO ags;

ALTER TABLE users
        ADD CONSTRAINT users_pkey PRIMARY KEY (id);

ALTER SEQUENCE users_id_seq
        OWNED BY users.id;


Да, всё работает так как и ожидалось. Подготавливаем скрипт создания миграций migrate.sh

#!/bin/bash

FILENAME=${1:-changeset.sql}

# Заголовок файла миграции
echo "--liquibase formatted sql" > $FILENAME
echo "--changeset $USER:$FILENAME" >> $FILENAME
echo "" >> $FILENAME

# Создаем секцию наката
./pgcodekeeper-cli.sh jdbc:postgresql:dbdev jdbc:postgresql:dbprod >> $FILENAME

echo "" >> $FILENAME

# Создаем секцию отката (базы данных идут в обратном порядке)
./pgcodekeeper-cli.sh jdbc:postgresql:dbprod jdbc:postgresql:dbdev | sed -e 's/^/--rollback /' >> $FILENAME

Скрипт принимает один параметр — имя выходного файла, если имя файла не задано, то по умолчанию принимается changeset.sql. Далее формируется заголовок файла SQL, который необходим Liquibase для сохранения информации о выполненных изменениях на целевой БД. Далее идёт формирование собственно файла миграции.

Команда:

./pgcodekeeper-cli.sh jdbc:postgresql:dbdev jdbc:postgresql:dbprod >> $FILENAME

находит различия между базами, формирует файл преобразования структуры объектов БД из dbdev в dbprod и сохраняет в выходном файле.

А команда:

./pgcodekeeper-cli.sh jdbc:postgresql:dbprod jdbc:postgresql:dbdev | sed -e 's/^/--rollback /' >> $FILENAME

ищет различия между этими же базами только в другом направлении, целевой БД теперь становится девелоперская, для того, чтобы можно было выполнить автоматический откат изменений. Секция отката в Liquibase помечается комментариями --rollback.

Проверяем, формирование миграции. Для теста, в качестве выходного файла, я использую /dev/stdout:

$ chmod +x ./migrate.sh
$ ./migrate.sh /dev/stdout 
--liquibase formatted sql
--changeset ags:/dev/stdout

CREATE SEQUENCE users_id_seq
        START WITH 1
        INCREMENT BY 1
        NO MAXVALUE
        NO MINVALUE
        CACHE 1;

ALTER SEQUENCE users_id_seq OWNER TO ags;

CREATE TABLE users (
        id integer DEFAULT nextval('users_id_seq'::regclass) NOT NULL,
        name text
);

ALTER TABLE users OWNER TO ags;

ALTER TABLE users
        ADD CONSTRAINT users_pkey PRIMARY KEY (id);

ALTER SEQUENCE users_id_seq
        OWNED BY users.id;


--rollback ALTER TABLE users
--rollback      DROP CONSTRAINT users_pkey;
--rollback 
--rollback DROP TABLE users;
--rollback 

Отлично, сформирована как секция наката, так и секция отката. А нам не пришлось для этого написать ни одной строки SQL вручную! Вся работа по созданию файла миграции была сделана автоматически.

Формируем реальный файл миграции

$ ./migrate.sh 001_users.sql

Подключаем его к журналу миграций, отредактировав файл db.changelog.xml, добавив в него директиву include с файлом 001_users.sql:

<?xml version="1.0" encoding="UTF-8"?>

<databaseChangeLog
  xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
         http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
 
    <include file="001_users.sql" />
 
</databaseChangeLog>


И накатываем изменения на dbprod, перед накатом изменений давайте выставим тег в целевой БД, чтобы мы могли откатиться в дальнейшем.

$ ./liquibase tag 001_before_users
Successfully tagged ags@jdbc:postgresql:dbprod
Liquibase 'tag' Successful

$ ./liquibase migrate
Liquibase Update Successful

Проверяем статус

$ ./liquibase status
ags@jdbc:postgresql:dbprod is up to date
Liquibase 'status' Successful

Откатываемся на состояние перед накатом таблицы

$ ./liquibase rollback 001_before_users
Liquibase Rollback Successful

Проверяем статус повторно

$ ./liquibase status
1 change sets have not been applied to ags@jdbc:postgresql:dbprod
Liquibase 'status' Successful

Возвращаем выполненный предварительно накат

$ ./liquibase migrate
Liquibase Update Successful

Давайте проверим, отличаются ли структуры баз данных сейчас? Теперь, после того как мы «поигрались» с накатами и откатами состояние баз данных dbdev и dbprod должно быть одинаковым. Так ли это?

$ ./pgcodekeeper-cli.sh jdbc:postgresql:dbdev jdbc:postgresql:dbprod
ALTER TABLE databasechangeloglock
        DROP CONSTRAINT pk_databasechangeloglock;

DROP TABLE databasechangeloglock;

DROP TABLE databasechangelog;

pgCodeKeeper говорит, что в dbdev нет двух таблиц — databasechangeloglock и databasechangelog. Эти таблицы были созданы автоматически liquibase и в них он сохраняет информацию о выполненных накатах. Без этих таблиц работа liquibase будет невозможна. Чтобы эти таблицы в дальнейшем не мешали нам при формировании миграций можно или скопировать структуру этих таблиц в девелоперскую базу или использовать возможность списка игнорирования для pgCodeKeeper.

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

$ cat .pgcodekeeperignore 
SHOW ALL
HIDE REGEX "databasechangelog.*"

Теперь, команда

$ ./pgcodekeeper-cli.sh -I .pgcodekeeperignore jdbc:postgresql:dbdev jdbc:postgresql:dbprod

не должна отображать никаких изменений.

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

  1. Вносим изменения в девелоперскую БД
  2. С помощью нашего скрипта формируем миграцию
  3. Инспектируем миграцию и подключаем её в журнал миграций
  4. Деплоим изменения на боевую (тест, стейдж) БД
  5. В случае необходимости, делаем откат

Создание файлов миграций Liquibase теперь стало возможно выполнять автоматически. Миграции описаны на языке SQL и что немаловажно, секция отката создается также без участия человека. Самое сложное что нужно сделать, это придумать имя файла миграции.

P.S.: У Liquibase есть встроенный механизм формирования чейнджсета различий между двумя БД, к сожалению, по своему опыту могу сказать, что он работает не всегда хорошо, заменив этот механизм на код формируемый pgCodeKeeper можно формировать миграции для более сложных изменений в БД.

P.P.S.: pgCodeKeeper распространяется с триальной лицензией на 100 объектов БД, если ваша база содержит больше объектов, свяжитесь с разработчиками (адрес эл. почты и канал Telegram есть на сайте продукта) и вам вышлют лицензию на неограниченное количество объектов БД абсолютно бесплатно.
-->


К сожалению, не доступен сервер mySQL