Никога не е било, и ето отново!
На поредния проект решихме да използваме Liquibase от самото начало, за да избегнем проблеми в бъдеще. Както се оказа, не всички млади членове на екипа знаят как да го използват правилно. Проведох вътрешен уъркшоп, който след това реших да превърна в статия.
Статията включва полезни съвети и описание на трите най-очевидни капана, в които можете да попаднете, работейки с инструменти за миграция на релационни бази данни, по-специално Liquibase. Насочена е към Java разработчици на ниво Junior и Middle, за по-опитни разработчици може да бъде интересна за структуриране и преговор на това, което вероятно вече е известно.

Liquibase и Flyway са основните конкуриращи технологии за решаване на задачи за контрол на версиите на релационни структури в света на Java. Първата е напълно безплатна, в практиката по-често се избира именно тя, затова Liquibase е избран за герой на публикацията. Въпреки това, част от описаните практики могат да бъдат универсални, в зависимост от архитектурата на вашето приложение.
Миграциите на релационни структури са принудителен начин за справяне с ниската гъвкавост на релационните хранилища от данни. В епохата на модата на ООП, стилът на работа с БД предполага, че веднъж описваме схемата и повече няма да я променяме. Но реалността винаги е такава, че всичко се променя и измененията в структурата на таблиците са необходими доста често. Разбира се, процесът сам по себе си може да бъде болезнен и неприятен.
Няма да задълбавам в описанието на технологията и инструкциите за добавяне на библиотеката към проекта си, на тази тема са написани достатъчно статии:
Освен това, вече имаше отлична статия на тема полезни съвети:
Съвети
Искам да споделя своите съвети и коментари, които се родиха чрез пот, кръв и болка при решаването на проблеми с миграцията.
1. Преди да започнете работа, трябва да се запознаете с раздела за най-добри практики на Liquibase
са описани прости, но много важни неща, без които използването на библиотеката може да ви усложни живота. Например, неструктурният подход към управлението на променливите рано или късно ще доведе до объркване и повредени миграции. Ако се прилагат свързани изменения в структурата на базата данни и логиката на услугите не едновременно, съществува голяма вероятност това да доведе до червени тестове или повредена среда. Освен това, препоръките за използване на Liquibase на официалния сайт включват точка относно разработването и проверката на rollback скриптове заедно с основните миграционни скриптове. Има и примери в статията, свързани с миграциите и механизма за rollback.
2. Ако започнете да използвате средства за миграция – не допускайте ръчни корекции в структурата на базата.
Както казват: „Един път Persil — винаги Persil“. Ако базата на вашето приложение започне да се управлява с помощта на Liquibase — всякакви ръчни изменения незабавно водят до неконсистентно състояние, а нивото на доверие към променливите става нула. Потенциалните рискове — няколко прекарани часа за възстановяване на базата, а в най-лошия случай — убит сървър. Ако във вашия екип има DBA архитект „от старата школа“, търпеливо и внимателно му обяснете как всичко ще бъде зле, ако просто редактира базата по свое усмотрение от условен SQL Developer.
3. Ако променливата вече е била качена в репозиторий, избягвайте редактиране.
Ако друг разработчик е направил pull и е приложил changset, който по-късно ще бъде редактиран, той задължително ще споменава името ви с добро, когато получи грешка при стартиране на приложението. Ако редакцията на changset по някакъв начин премине в девелоп, ще трябва да се върви по хлъзгавия път на хотфиксовете. Същността на проблема опира до валидиране на промените по хеш-сумата - основният механизъм на Liquibase. При редактиране на кода на changset хеш-сумата се променя. Редактирането на changset-ите е възможно само когато има възможност без загуба на данни да се разпредели цялата база от нулата. В подобен случай, рефакторирането на SQL или XML кода може, напротив, да улесни живота, да направи миграциите по-разбираеми. Пример за това може да бъде ситуация, когато при стартиране на приложението схемата на изходната БД е била одобрена в рамките на екипа.
4. Имейте проверени резервни копия на базите данни, ако е възможно
Тук, мисля, всичко е ясно. В случай, че миграцията случайно е неуспешна, всичко може да бъде върнато обратно. В Liquibase има инструмент за връщане на промени, но скриптовете за връщане също ги пише самият разработчик и в тях могат да възникнат проблеми с такава съща вероятност, както и в скриптовете на основния changset. Това означава, че да сте предпазливи с резервните копия е полезно във всеки случай.
5. Използвайте проверени резервни копия на базите данни в разработката, ако е възможно
Ако това не противоречи на договорите и личната информация, в базата няма лични данни и тя не тежи като две слънца - преди да приложите миграцията на живи сървъри, можете да проверите как ще работи на машината на разработчика и да изчислите почти 100% от потенциалните проблеми при миграцията.
6. Общувайте с други разработчици в екипа
В правилно организиран процес на разработка всички в екипа знаят кой с какво се занимава. В действителност често не е така, затова, ако в рамките на задачата си подготвяте промени в структурата на БД, е желателно да уведомите цялата команда допълнително за това. Ако някой прави промени паралелно - вие трябва да се организирате внимателно. С колегите е добре да общувате и след завършване на работата, не само в началото. Много потенциални проблеми с changset-ите могат да бъдат разрешени на етапа на code review.
7. Мислете какво правите!
На пръв поглед, очевиден съвет, приложим в различни ситуации. Въпреки това, много проблеми можеха да бъдат избегнати, ако разработчикът отново анализираше какво прави и как това може да повлияе. Работата с миграции винаги изисква допълнително внимание и прецизност.
Капани
Сега нека разгледаме типичните капани, в които може да се попада, ако не се следват горните съвети, и какво всъщност да правим?
Ситуация 1. Два разработчика се опитват да добавят нови ченджсети едновременно

Вася и Петя искат да създадат ченджсет версия 4, без да знаят един за друг. Те направиха промени в структурата на БД и пуснаха pull request с различни файлове на ченджсета. Следващият механизъм на действие е:
Как да действаме
- По някакъв начин колегите трябва да се договорят за реда, в който трябва да идват техните ченджсети; да предположим, че ченджсетът на Петя трябва да бъде приложен първи.
- Някой един трябва да обедини втория в себе си и да етикетира ченджсета на Вася с версия 5. Това може да стане чрез Cherry Pick или внимателно обединение (merge).
- След промените е задължително да се провери валидността на извършените действия.
Всъщност механизмите на Liquibase позволяват в репозитория да имате два ченджсета версия 4, така че можете да оставите всичко както е. Тоест, просто ще имате две изменения версия 4 с различни имена. При този подход по-късно в версиите на базата данни става много сложно да се ориентира.
Освен това, Liquibase, подобно на дома на хобитите, съхранява много тайни. Една от тях е ключът validCheckSum, който се появи с версия 1.7 и позволява да укажете валидна стойност на хеш-сумата за конкретен ченджсет, независимо от това, което е съхранено в базата данни. Документацията казва следното:
Добавете хеш-сумата, която се счита за валидна за този changeSet, независимо от това, което е съхранено в базата данни. Използва се предимно, когато трябва да промените changeSet и не искате да бъдат генерирани грешки в бази данни, на които вече е бил изпълнен (не е препоръчвана процедура)
Да-да, такава процедура не се препоръчва. Но понякога силен светъл маг владее и тъмни техники.
Ситуация 2. Миграция, която зависи от данни.

Предположим, че нямате възможност да използвате резервни копия на бази от живи сървъри. Петя създаде чейнджсет, проверил го е локално и с пълна увереност в правотата си направи pull request в девелоп. Лидът на проекта за всеки случай уточни, провери ли го Петя и след това го вля. Но разгръщането на девелоп сървъра се провали.
Всъщност това е възможно и никой не е застрахован от него. Това се случва, ако модификациите на структурата на таблиците по някакъв начин са обвързани с конкретни данни от БД. Очевидно е, че ако базата на Петя е запълнена само с тестови данни, тя може да не покрива всички проблемни случаи. Например, при изтриване на таблица се установява, че има записи в други таблици по Foreign Key, свързани с изтритите записи. Или при промяна на типа колона излиза, че не всички данни могат да бъдат преобразувани в новия тип.
Как да действаме
- Да се напишат специални скриптове, които ще се прилагат еднократно съвместно с миграцията и ще приведат данните в задоволителен вид. Това е общо решение на проблема с преместването на данни в нови структури след прилагане на миграции, но нещо подобно може да се приложи и преди, в специфични случаи. Този подход, разбира се, не винаги е достъпен, тъй като редактирането на данни на живи сървъри може да бъде опасно и дори разрушително.
- Друг сложен подход е да се редактира съществуващия чейнджсет. Сложността е, че всички БД, където той е бил приложен в съществуващия вид, ще трябва да бъдат възстановени. Напълно е възможно целия бекенд екип да бъде принуден локално да инсталира БД от нулата.
- И най-универсалният подход е да се прехвърли проблемът с данните в средата на разработчика, като се възпроизведе същата ситуация и се добави нов чейнджсет преди счупения, който ще позволи заобикалянето на проблема.

Общо взето, колкото повече базата по състав на данни прилича на базата на продукционния сървър, толкова по-малка е вероятността проблемите с миграциите да бъдат сериозни. И, разбира се, преди да изпратите чейнджсет в репозитория, струва си да се замислите дали ще счупи нещо.
Ситуация 3. Liquibase започва да се прилага вече след излизане в продукция.
Предположим, че тимлидът е помолил Петя да включи Liquibase в проекта, но проектът вече е в продукция и съществува вече съществуваща структура на базата.
Съответно, проблемата е, че на всякакви нови сървъри или машини на разработчиците данните от таблиците трябва да се възстановяват от нулата, а вече съществуващата среда трябва да остане в консистентно състояние, готова да приема нови изменения.
Как да действаме
Има няколко пътя:
- Първият и най-очевиден — да имате отделен скрипт, който трябва да бъде приложен ръчно при инициализацията на новата среда.
- Вторият — по-малко очевиден, е да имате Liquibase миграция, която е в друг Liquibase контекст и да я приложите. Още за Liquibase контекста можете да прочетете тук: . Като цяло това е интересен механизъм, който може да бъде успешно приложен например за тестване.
- Третият път се състои от няколко стъпки. Първо трябва да се създаде миграция за вече съществуващите таблици. След това тя трябва да бъде приложена на някаква среда и по този начин ще се получи нейният хеш-сум. Следващата стъпка е да инициализирате на нашия не празен сървър празни Liquibase таблици, и в таблицата с историята на прилагането на изменения можете ръчно да внесете запис за "както би било приложено" изменение с вече съществуващите в базата промени. Така на вече съществуващия сървър историята ще започне от версия 2, а всички нови среди ще се държат идентично.

Ситуация 4. Миграциите стават огромни и не успяват да се изпълнят
В началото на разработката на услугата, обикновено, Liquibase се използва като външна зависимост и всички миграции се обработват при стартиране на приложението. Въпреки това, с течение на времето можете да се натъкнете на следните случаи:
- Миграциите стават огромни и се изпълняват дълго време.
- Появява се необходимост от миграция в разпределени среди, да речем, на няколко инстанции на сървъри БД едновременно.
В такъв случай твърде дългото прилагане на миграции ще доведе до таймаут при стартиране на приложението. Освен това, прилагането на миграции за всяка инстанция на приложението поотделно може да доведе до това, че различните сървъри да се окажат в несинхронно състояние.
Как да действаме
В такива случаи вашият проект вече е голям, може би дори пълнолетен, и Liquibase започва да действа като отделен външен инструмент. Факт е, че Liquibase като библиотека се събира в jar файл и може да работи както зависимост вътре в проекта, така и автономно.
В офлайн режим можете да поверите прилагането на миграции на вашата CI/CD среда или на опитни системни администратори специалисти по внедряване. За целта ще е необходима командна линия на Liquibase. . В този режим се появява възможност за стартиране на приложението веднага след като всички необходими миграции са проведени.
Извод
Всъщност капаните при работа с миграции на БД могат да бъдат много повече, и много от тях изискват креативен подход. Важно е да се разбере, че ако инструментът се използва правилно, повечето от тези капани могат да бъдат избегнати. Конкретно на мен ми се е налагало да се сблъсквам с всички изброени проблеми в различни версии, а някои от тях бяха резултат от моите грешки. Основно това се случва, разбира се, поради небрежност, но понякога – поради криминално неумение да се използва инструментът.
Източник: habr.com


