Pull to refresh

Версионная миграция структуры БД: почему так лучше не делать

Reading time12 min
Views8K

Предыстория


В сети появляется масса разнообразных статей, посвященных миграции структуры базы данных, в которых предлагается множество вариантов безболезненного решения данной проблемы (стоит учесть, что необходимость в такого рода миграции уже сама по себе является серьезной проблемой). Любопытно, но исключить необходимость в таком решении предлагают существенно реже. Автор уверен, что вместо поиска решения сложной проблемы, лучше постараться ее предотвратить. Сделать это нужно как можно раньше. Ниже вас ждет рассказ о проекте, демонстрирующем негативную сторону миграции структуры базы данных.

Статьи, на которые стоит обратить внимание:


Почему автору можно верить

Верить нельзя никому, но автор искренне надеется, что к его словам отнесутся с должной долей скептицизма, продолжая руководствоваться здравым смыслом и логикой. Тем не менее, автор будет рад, если вместо решения зачастую надуманных проблем, вы потратите время на что-то более приятное. Идеи, оценки и допущения, описываемые в данное статье, являются следствием многолетнего опыта разработки программного обеспечения. Кроме всего прочего, автор более пяти лет руководил поддержкой существующих и разработкой нескольких новых продуктов, для которых описываемая проблема является более чем актуальной. По истечении нескольких лет, наконец появилась возможность поделиться информацией. Ниже вы сможете найти реальные примеры серьезных проблем, способы их решения и предотвращения. Проект, о котором пойдет речь, является скорее небольшим, но, тем не менее, достаточно показательным:
Оценка сложности проекта

Краткое содержание

Постараюсь кратко сформулировать все замечания, предположения и выводы для облегчения последующей дискуссии:
  1. Современный процесс разработки ориентирован на короткие итерации, частые билды и постоянную интеграцию
  2. Инкрементальные изменения структуры баз данных весьма дороги, ввиду явных ограничений СУБД
  3. На ранних этапах разработки ограничения СУБД не всегда очевидны/интересны (показано ниже)
  4. Успешный продукт сталкивается с большинством проблем достигнув значительных размеров, именно тогда стоимость исправления многих проблем огромна
  5. Выбор способа хранения и обработки данных определяет архитектуру приложения
  6. Внесение значительных изменений в архитектуру на поздних стадиях является очень дорогим удовольствием
  7. Именно на поздних стадиях разработки процесс пытаются ускорить ввиду явных преимуществ с точки зрения бизнеса и прибыльности
  8. Изменения структуры базы данных часто влекут за собой дополнительную обработку/преобразование хранимых данных
  9. На поздних стадиях любые изменения структуры базы данных, ввиду многих ограничений СУБД, являются проблематичными.
  10. Для многих проектов, версионная миграция структуры БД на ранних этапах покажется очевидным решением, впоследствии став серьезным ограничением. Более или менее универсальный рецепт — необходимо избегать частого изменения структуры базы данных.

Еще один пример

Особенности современной разработки


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

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

Коротко:
  • интенсивная разработка — частые сборки и частые изменения структуры БД
  • разработчики не работают в изоляции — помните о тестерах и зависимых разработчиках
  • на любом этапе миграции могут возникнуть проблемы
  • очень часто проблемы миграции сложно выявить сразу
  • «битая база» всегда приводит к временным затратам
  • клиент, ждущий несколько часов пока база «обновится», расстроится или даже разозлится
  • поломка у клиента означает на несколько порядков больше проблем и времени необходимого для их решения

Пример первый, система живущая 14 лет


Краткое описание

Основная решаемая проблема — управление средним количеством сетевых устройств нескольких типов. Размер реальных установок варьировался от 1 до 60 устройств. От системы устройства получают конфигурацию и управляющие команды, отправляя в ответ подтверждения, разнообразную статистику и «телеметрию». Также система реализует пользовательский интерфейс для редактирования разнообразной конфигурации устройств. Стоит отметить сложность конфигурации (и интерфейса пользователя), необходимость хранить историю, поддерживать огромное число опций, специфических пользовательских типов данных и т.д. Конфигурация могла включать от десяти до нескольких сотен различных параметров. Для хранения всех данных использовалась база данных (преимущественно MySQL).

Упрощенная модель

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

Хронология: начало

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

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

Проблемы на данном этапе

Поскольку пользователи взаимодействовали с устройствами посредством системы управления — задачи по разработке и улучшению последней, перешли в разряд очень важных. Реализация нового функционала сводилось к добавлению, удалению или замене одного или нескольких параметров (столбцов) в базе данных и соответствующих правок интерфейса пользователя и генерируемой конфигурации. Но, как оказалось позже, большую часть времени разработчики тратили на решение разнообразных проблем, связанных с изменениями структуры базы данных. Никто не хотел расставаться со своими тестовыми данными, никто не хотел выполнять дополнительные шаги про приведению системы в работоспособное состояние после очередных изменений.

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

Технические нюансы

Изменение существующих таблиц, используя ALTER TABLE, скорее всего, не будет дешевой операцией. Автор заранее опускает идеальные варианты, которые сводятся к изменению метаданных таблицы, предлагая читателю задуматься над индексами, которые могут существовать, над необходимостью преобразования данных, над физическим размещением данных на диске и отличием идеализированного табличного представления от реального размещения данных и соответствующих проблем с производительностью.

Кстати, часто вы можете встретить призыв не использовать ALTER TABLE, отдав предпочтение copy-rename процедуре (например:Don’t Alter Table. Do Copy and Rename)

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

Решение

На данном этапе светлые головы компании решили полностью избавиться от необходимости менять структуру базы данных и хранить конфигурацию устройств в очень модном (только начал набирать популярность) XML формате. Согласитесь, очень удобно: десериализуем XML в дерево объектов, работаем с ними, затем сериализуем обратно в XML и сохраняем в базу. Естественно, в базе появляется BLOB, но после недолгих колебаний было решено оставить эту проблему базе данных. Для преобразования данных использовались небольшие независимые программы (изначально perl скрипты, затем логика на java), которые обеспечивали последовательное преобразование базы данных, скажем, от версии X до версии Y.
Упрощение обновления базы данных

Эффект

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

Хронология: становление


После значительной переработки способа хранения конфигурации, развитие системы ускорилось и количество проблем, на первый взгляд, уменьшилось. Компания выпустила новую версию, которая при установке преобразовывала старую базу в новый формат, в очередной раз тратя время клиентов без видимой причины, но обещая избавить их от всех бед в будущем. Клиенты верили и терпели. Затем последовали новые версии, которые расширяли функционал системы и решали проблемы предыдущих. Переход на более новую версию системы занимал от 15 до 30 минут, что выглядело очень серьезным достижением. Но так было недолго. Наращивание функционала привело к взрывному росту хранимых данных. Далеко не самую последнюю роль в этом сыграла относительная дешевизна изменения конфигурации. Разработчики выбирали самый простой способ реализации совершенно не заботясь о будущем.

Результатом такого подхода явились горы XML кода и большое количество необходимых преобразований при переходе к каждой последующей версии. На данном этапе, для перехода базы данных из состояния X.1 до состояния X.2 (добавлен новый функционал) необходимо было выполнить одно или несколько преобразований над хранившимися в базе XML представлениями конфигурации. Только после успешного выполнения таких преобразований система могла начать работу (обновленный код мог десериализовать сохраненное представление).

Проблемы на данном этапе

Преобразования конфигурации представляли собой набор последовательных трансформаций сохраненных XML файлов (в базе действительно хранились xml файлы, которые на ранних этапах даже были полостью совместимы с устройствами). Часть таких преобразований трактовала XML представление как строку и использовала обычную замену. Другие программисты предпочитали работать с DOM деревом или писать «оптимизированные» варианты, используя SAX парсеры. Подобного рода преобразования были независимы и, например, наличие их большого количества в релизе, могло означать необходимость выполнить несколько преобразований для одного типа конфигурации, каждое из которых самостоятельно выполняло десериализацию в DOM дерево, изменение дерева и последующую сериализацию, повторяя, таким образом, ресурсоемкие и совершенно необязательные шаги несколько раз.

У разработчиков данная операция занимала незначительное время, ввиду исключительно небольшого размера тестовых данных, используемых во время разработки. Размер реальной конфигурации, хранимой реальными системами, был на несколько порядков больше (от 100 килобайт до 1-2 мегабайт, при общем размере базы до 1-2 Гб). Переход на новую версию системы, в очередной раз, стал большой проблемой для клиентов.
Ошибка во время обновления

Сложность кода, который занимался преобразованием сохраненного XML, постоянно увеличивалась, поскольку росло количество параметров, количество возможных вариантов, количество клиентов и т.д. Появлялись ошибки, которые приводили к появлению «битой» или попросту неправильной, с точки зрения логики приложения, конфигурации. Основная часть такого рода проблем была скрытой и проявлялась только в самых сложных и изощренных случаях — у самых больших и самых ценных клиентов. Усложняло ситуацию и то, что во время разработки преобразования базы данных запускались вручную. Затем, перед выпуском очередной публичной версии, один из разработчиков собирал весь код, отвечающий за преобразование базы данных, в одно приложение и формировал финальный билд. Учитывая огромное количество вариантов и очень жесткие (как всегда) временные рамки, протестировать все нюансы системы не представлялось возможным. Клиенты находили баги, теряли базы данных (если забывали позаботиться о резервной копии), теряли базы данных позже, когда добирались до проблемных веток и т.д.

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

Решение

На этот раз светлые головы на некоторое время усомнились в своей светлости, но пути назад уже не было. Еще раз провести глобальный редизайн системы не представлялось возможным из-за громадного объема работ. После продолжительного совещания постановили:
  • ввести понятие апгрейда между билдами и во время разработки (тестирования) использовать механизмы, которые, в дальнейшем, будут использоваться клиентами
  • внимательно писать код для преобразования конфигурации
  • интенсивнее тестировать систему, используя базы данных клиентов (затраты на тестирование существенно возросли)


Эффект

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

Хронология: зрелая система


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

Проблемы на данном этапе

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

Кроме того, некоторым, особенно активным, клиентам удалось довести размер конфигурации до 10 мегабайт и общий размер базы до 5 гигабайт, после чего, они столкнулись с новым классом очень интересных проблем. Завершить картину, можно желанием руководства получить систему, совместимую с устройствами предыдущих версий. Все время устройства и система управления развивались синхронно. Пример: Система.1 отправляла конфигурацию на Устройство.1, Система.2 на Устройство.2 и т.д. Была поставлена задача обеспечить возможность для Системы.4 отправить правильную конфигурацию на Устройства.2,3,4. Отличия, к слову, зачастую были очень существенными.

Решение

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

Конфигурацию начали хранить в архивированном виде, решая как проблему размера, так и проблему скорости чтения и преобразования (производительность процессоров растет, а вот дисковая подсистема как была страшным тормозом, так им и осталась. Производительность базы (MySQL) действительно увеличилась). Используя стабильный и отлаженный механизм обновлений, был реализован механизм обратных преобразований, который позволил поддерживать устройства разных версий. Автоматизированные тесты использовались и в этом случае. Жизнь разработчиков, помимо всего прочего, существенно упростил универсальный эмулятор устройств, который не только показывал, что отправляла система управление, но и проверял полученную конфигурацию, указывая на потенциальные ошибки.
Результат применения компрессии

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

Заключение


Автор получил данный проект в достаточно плачевном состоянии. Вместе с проектом досталось несколько сотен багов, реальные клиенты и множество запросов на дополнительную функциональность, включая страшный запрос на систему с обратной совместимостью и адаптацию всего хозяйства под новомодный Agile. Кстати, реализация непрерывной интеграции (continuous integration) для существующего уже длительное время большого проекта и проверка всего, что только можно проверить после каждого комита — отдельная и очень интересная тема. С задачей удалось справиться, исключительно благодаря отличной команде и безграничному оптимизму. Множество ошибок было исправлено:
Исправленные ошибки

Процес разработки был достаточно интенсивным:
Публичные сборки

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

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

Бонус


Решая проблему обновления хранимых в базе данных XML документов, среди прочих рассматривался вариант использования XSLT преобразований. Решение казалось привлекательным и удачным, позволяло использовать несколько оптимизационных техник. Например, несколько преобразований можно представить в виде конвейера и при их достаточном количестве, получить заметный выигрыш производительности. Ниже приведены результаты небольшого оценочного тестирования. Тесты 1-5 отличаются размером обрабатываемых данных. Не стоит воспринимать результаты как руководство к действию или истину в последней инстанции.
Производительность XSLT преобразований
Tags:
Hubs:
Total votes 47: ↑32 and ↓15+17
Comments55

Articles