Статьи

Тримминг Liquibase ChangeLogs

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

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

Мой первый ответ всегда звучит так: «Тебе действительно нужно упростить это?» Вы создавали этот журнал изменений в течение длительного периода времени, и вы запускали его и проверяли его бесчисленное количество раз. Как только вы начинаете возиться с файлом изменений, вы вводите риск, который имеет свою стоимость. Какая бы у вас ни была проблема с производительностью или размером файла, перевешивает ли риск работы со сценарием, который, как вы знаете, работает?

Если это стоит риска, почему это работает риск? Иногда проблема в том, что ваш файл журнала изменений стал настолько большим, что ваш редактор задохнулся от него, или вы получили слишком много конфликтов слияния. Лучший способ справиться с этим — просто разбить файл журнала изменений на несколько файлов. Вместо того, чтобы иметь единственный файл changelog.xml со всем этим, создайте файл master.changelog.xml, который использует тег <include> для ссылки на другие файлы changelog. 

<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
  xmlns="http://www.liquibase.org/xml/ns/dbchangelog/3.3"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/3.3
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.3.xsd">
    <include file="com/example/news/news.changelog.xml"/>
    <include file="com/example/directory/directory.changelog.xml"/>
</databaseChangeLog>

Когда вы запустите ‘liquibase update` для файла master.changelog.xml, запустятся changeSets в com / example / news / news.changelog.xml, а затем запустятся changeSets в com / example / directory / directory.changelog.xml. Вы можете разбить наборы изменений любым удобным для вас способом. Некоторые разбивают их по функциям, некоторые разбивают по релизам. Найдите то, что работает лучше для вас.

В других случаях проблема заключается в том, что «обновление жидкой базы» занимает слишком много времени. Liquibase старается быть максимально эффективным при сравнении содержимого таблицы DATBASECHANGELOG с текущим файлом журнала изменений, и даже если существуют тысячи уже запущенных наборов изменений, выполнение команды «update» может занять всего несколько секунд. Если вы обнаружите, что обновление занимает больше времени, чем должно, посмотрите журнал Liquibase, чтобы определить причину. Возможно, существует старый набор изменений runAlways = ”true”, который больше не требуется запускать или есть предварительные условия, которые больше не нужны. Запуск Liquibase с параметром –logLevel = INFO или даже –logLevel = DEBUG может дать дополнительный вывод, который поможет вам определить, какие наборы изменений являются медленными. Как только вы узнаете, что замедляет ваше обновление, попробуйте изменить только эти наборы изменений, а не выбрасывать весь список изменений и начинать с нуля.Вы все равно захотите провести повторную проверку вашего журнала изменений, но это гораздо менее рискованное изменение.

Для других людей они считают, что «liquibase update» хорошо работает для инкрементных обновлений, но создание базы данных с нуля занимает слишком много времени. Я снова спрашиваю: «Это действительно проблема?» Вы создаете базы данных достаточно часто, чтобы риск изменения сценария создания имел смысл? Если вы, ваш первый шаг должен быть для поиска проблемных наборов изменений, как описано выше. Базы данных быстрые, особенно когда они пусты. Даже если вы создаете таблицу только для того, чтобы удалить ее снова, это обычно занимает всего несколько миллисекунд и не стоит оптимизации. Самыми большими узкими местами производительности при создании базы данных обычно являются индексы, поэтому начните с них. Если вы часто создаете и обновляете индексы в процессе создания, вы можете объединить эти наборы изменений в нечто более эффективное.

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

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

Чтобы объединить или изменить существующие наборы изменений, вы будете выполнять комбинацию редактирования существующих наборов изменений, удаления старых наборов изменений и создания новых.

Удаление ненужных наборов изменений легко, потому что Liquibase не заботится о строках DATABASECHANGELOG без соответствующих наборов изменений. Просто удалите устаревшие наборы изменений, и все готово. Например, если у вас есть набор изменений, который создает таблицу «корзина», а затем другой, который ее отбрасывает, просто удалите оба набора изменений из файла. Однако вы должны убедиться, что между созданием и удалением, которые используют эту таблицу, нет никаких наборов изменений, иначе они потерпят неудачу при новой сборке базы данных. Это пример того, как вы вводите риск при изменении файла журнала изменений.

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

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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.3.xsd">
    <changeSet author="nvoxland" id="1">
        <createTable tableName="cart">
            <column name="id" type="int"/>
        </createTable>
    </changeSet>

    <changeSet author="nvoxland" id="2">
        <addColumn tableName="cart">
            <column name="promo_code" type="varchar(10)"/>
        </addColumn>
    </changeSet>

    <changeSet author="nvoxland" id="3">
        <addColumn tableName="cart">
            <column name="abandoned" type="boolean"/>
        </addColumn>
    </changeSet>

</databaseChangeLog>

Один из вариантов — объединить все в новый набор изменений, используя существующий идентификатор = «1», и удалить другие наборы изменений.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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.3.xsd">
    <changeSet author="nvoxland" id="1">
        <validCheckSum>7:f24b25ba0fea451728ffbade634f791d</validCheckSum>
        <createTable tableName="cart">
            <column name="id" type="int"/>
            <column name="promo_code" type="varchar(10)"/>
            <column name="abandoned" type="boolean"/>
        </createTable>
    </changeSet>
</databaseChangeLog>

Это будет хорошо работать, если во всех существующих базах данных уже есть таблица корзины с промо-кодом и заброшенными столбцами. Запуск Liquibase для существующих баз данных просто видит, что id = ”1” уже запущен, и ничего нового не делает. Запуск Liquibase для пустой базы данных сразу создаст таблицу корзины со всеми столбцами. Обратите внимание, что нам пришлось добавить флаг <validCheckSum>, или существующие базы данных выдают ошибку, сообщающую, что id = ”1” изменилось с момента его запуска. Просто используйте контрольную сумму в сообщении об ошибке в теге validCheckSum, чтобы отметить, что вы знаете, что оно изменилось, и новое значение в порядке.

Если у вас есть базы данных, в которых рекламный код и / или заброшенные столбцы еще не добавлены, обновите исходный createTable, как и раньше, но используйте предварительные условия с onFail = ”MARK_RAN” для обработки случаев, когда старый changeSet выполнялся, при этом еще не добавляя столбцы снова если новый набор изменений побежал.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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.3.xsd">
    <changeSet author="nvoxland" id="1">
        <validCheckSum>7:f24b25ba0fea451728ffbade634f791d</validCheckSum>
        <createTable tableName="cart">
            <column name="id" type="int"/>
            <column name="promo_code" type="varchar(10)"/>
            <column name="abandoned" type="boolean"/>
        </createTable>
    </changeSet>

    <changeSet author="nvoxland" id="2">
        <preConditions onFail="MARK_RAN">
            <not><columnExists tableName="cart" columnName="promo_code"/></not>
        </preConditions>
        <addColumn tableName="cart">
            <column name="promo_code" type="varchar(10)"/>
        </addColumn>
    </changeSet>

    <changeSet author="nvoxland" id="3">
        <preConditions onFail="MARK_RAN">
            <not><columnExists tableName="cart" columnName="abandoned"/></not>
        </preConditions>
        <addColumn tableName="cart">
            <column name="abandoned" type="boolean"/>
        </addColumn>
    </changeSet>

</databaseChangeLog>

Теперь в существующих базах данных, в которых уже запущены все 3 набора изменений, Liquibase продолжит работу, как и раньше. Для существующих баз данных, у которых есть старое определение корзины, он увидит, что столбцы не существуют для id = ”2” и id = ”3”, и затем будет выполнен как обычно. Для пустых баз данных он создаст таблицу с промо-кодом и оставленными столбцами, а затем с id = ”2” и id = ”3” увидит, что они уже есть, и отметит, что они запустились без повторного добавления столбцов. Однако, предупреждающее слово: использование предварительных условий увеличит производительность ваших обновлений и будет игнорироваться в режиме updateSQL, потому что Liquibase не может знать, насколько они применимы, когда changeSets фактически не выполняются. По этой причине лучше избегать их, если это возможно, но обязательно используйте их при необходимости.Предварительные условия также усложняют ваш журнал изменений, что потребует дополнительного тестирования, поэтому помните об этом при принятии решения об изменении логики журнала изменений. Иногда проще и безопаснее подождать, пока все базы данных не будут иметь столбцы, а затем изменить наборы изменений, чтобы избежать предварительных условий.

Пример cart / promo_code / покинутый показывает некоторые основные шаблоны, которые вы можете использовать при изменении существующих наборов изменений. Подобные шаблоны могут быть использованы для оптимизации ваших узких мест. Просто помните, когда вы меняете один набор изменений, это может повлиять на другие наборы изменений ниже, которые также могут потребоваться изменить. Это может легко выйти из-под контроля, поэтому помните о том, что вы делаете.

Если в итоге вы обнаружите, что лучше всего будет полностью перезапустить журнал изменений, см. Http://www.liquibase.org/documentation/existing_project.html, в котором описано, как добавить Liquibase в существующий проект (даже если этот проект ранее управлялся по Liquibase).