14 июня 2011 в 06:55

Версионная миграция структуры базы данных: основные подходы

SQL*
Проблемы контроля версий баз данных и миграций между версиями уже не раз поднимались как на Хабре (1, 2, 3 и др.), так и в Интернете (преимущественно, англоязычном).

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

Терминология

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

Структура базы данных — совокупность всех объектов БД и статических данных. Пользовательские данные в понятие структуры БД не входят.

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

Миграция, в данном контексте, — обновление структуры базы данных от одной версии до другой (обычно более новой).

В этом смысле термин миграция, похоже, используется во многих источниках (особенно этому поспособствовали миграции из gem'а Active Record, входящего в состав Ruby on Rails). Однако при использовании этого термина возникает двусмысленность: человек, который не знает контекста, скорее подумает, что речь идет о переносе базы данных с одной СУБД на другую (MySQL => Oracle), а то и вовсе о миграции процессов/данных между нодами кластера. Поэтому предлагаю в случаях, когда контекст неочевиден, использовать более точный термин: версионная миграция баз данных.

Зачем это нужно?

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

Версия базы данных должна соответствовать версии приложения


Итак, представьте себе следующую ситуацию: команда из нескольких программистов разрабатывает приложение, которое активно использует базу данных. Время от времени приложение поставляется в продакшн — например, это веб-сайт, который деплоится на веб-сервер.
Любому программисту в процессе написания кода приложения может понадобиться изменить структуру базы данных, а также, сами данные, которые в ней хранятся. Приведу простой пример: допустим, есть необнуляемое (not nullable) строковое поле в одной из таблиц. В этом поле не всегда есть данные, и в этом случае там хранится пустая строка. В какой-то момент вы решили, что хранить пустые строки — семантически неправильно в некоторых случаях (см. 1, 2), а правильно — хранить NULL'ы. Для того, чтобы это реализовать, понадобятся следующие действия:

1. Изменить тип поля на nullable:
ALTER myTable CHANGE COLUMN myField myField VARCHAR(255) NULL DEFAULT NULL;

2. Так как в этой таблице на продакшн БД уже наверняка есть пустые строки, вы принимаете волевое решение и трактуете их как отсутствие информации. Следовательно, нужно будет заменить их на NULL:
UPDATE myTable SET myField = NULL WHERE myField = '';

3. Изменить код приложения так, чтобы при получении из БД данных, хранящихся в этом поле, он адекватно реагировал на NULL'ы. Записывать в это поле тоже теперь нужно NULL'ы вместо пустых строк.

Из пункта 3 можно видеть, что приложение и база данных — неразрывные части одного целого. Это означает, что при поставке новой версии приложения в продакшн, нужно обязательно обновлять и версию базы данных, иначе приложение попросту не сможет правильно работать. В данном примере, если до новой версии будет обновлено только приложение, то в какой-то момент произойдет вставка NULL в необнуляемое поле, а это очевидная ошибка.

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

Так ли это просто?


Осознав, что паритет версий БД и приложения необходим, вам нужно удостовериться, что миграции БД до нужной версии всегда будут выполняться правильно. Но в чём здесь проблема? Ведь, на первый взгляд, сложного здесь ничего нет!

Тут снова обратимся к живому примеру. Допустим, программисты в процессе разработки записывают свои изменения структуры и данных БД в отдельный файл в виде SQL-запросов (как DDL-, так и DML-запросов). А при каждом деплое последней версии приложения вы одновременно обновляете до последней версии и базу данных, выполняя запросы из того самого SQL-файла… Но погодите, с какой версии вы обновляете БД до последней версии? «С прошлой»? Но так ли хорошо вы помните, что конкретно из себя представляла прошлая версия (её выпустили 2 месяца назад)? Если нет, то как вы собрались её обновлять? Ведь без точной информации о состоянии структуры и данных выполнить корректную миграцию невозможно: если вы непредумышленно выполните запросы, которые уже когда-то выполнялись, это может привести к потере данных или нарушению их целостности.
Простой пример — замена паролей на их MD5-суммы. Если повторно выполнить такой запрос, то данные можно будет восстановить только из бэкапа. Да и вообще, любые UPDATE'ы, DELETE'ы, и даже INSERT'ы, выполненные повторно, могут привести к крайне нежелательным последствиям. Не говоря уже о несвоевременных TRUNCATE'ах и DROP'ах (хотя такие случаи намного менее вероятны).
Кстати говоря, с этой точки зрения, недовыполнить — не меньшая опасность для работоспособности приложения, чем перевыполнить.

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


Общие принципы версионной миграции

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

Основание миграции


Как оказалось, у большинства подходов есть общий принцип: им необходимо основание (baseline) — некоторое эталонное состояние БД, от которого можно отталкиваться. Эта концепция довольно хорошо описана в статье «Versioning Databases – The Baseline» Скотта Аллена.

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

Далее будут рассмотрены три подхода к организации версионной миграции баз данных.


Метод инкрементных изменений

Этот метод хорошо описан в статье «Versioning Databases – Change Scripts» все того же Скотта Аллена. Схожий подход также описан в статье «Managing SQL scripts and continuous integration» Майкла Бэйлона.

Структура файлов


Пример того, как в этом случае может выглядеть папка с файлами-миграциями:
Database
|- Baseline.sql
|- 0001.03.01.sql
|- 0002.03.01.sql
|- 0003.03.01.sql
|- 0004.03.02.sql
|- 0005.03.02.sql
|- 0006.03.02.sql
'- 0007.03.02.sql

В этом примере в папке хранятся все файлы, созданные при разработке версии 03. Впрочем, папка может быть и общей для всех версий приложения.

В любом случае, самый первый файл, который появится в такой папке, — основание (Baseline.sql). После этого любое изменение в БД сабмиттится в репозиторий в виде нового файла-миграции с именем вида [номер файла].[версия].[подверсия].sql.

Фактически, в этом примере в имени файла содержится полный номер версии БД. То есть после выполнения файла-миграции с именем 0006.03.02.sql база данных обновится с состояния, соответствующего версии 03.02.0005, до версии 03.02.0006.

Хранение истории версий


Следующий шаг — добавление в базу данных специальной таблицы, в которой будет храниться история всех изменений в базе данных.
CREATE TABLE MigrationHistory
(
    Id INT,
    MajorVersion VARCHAR(2),
    MinorVersion VARCHAR(2),
    FileNumber VARCHAR(4),
    Comment VARCHAR(255),
    DateApplied DATETIME,

    PRIMARY KEY(Id)
)


Это всего лишь пример того, как может выглядеть таблица. При необходимости, её можно как упростить, так и дополнить.

В файле Baseline.sql в эту таблицу нужно будет добавить первую запись:
INSERT INTO
MigrationHistory ( MajorVersion, MinorVersion, FileNumber, Comment,    DateApplied )
VALUES           ( '03',         '01',         '0000',     'Baseline', NOW() )


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

Автоматическое выполнение миграций


Завершающий штрих в этом подходе — программа/скрипт, который будет обновлять БД с текущей версии до последней.

Выполнять миграцию БД автоматически довольно просто, т.к. номер последней выполненной миграции можно получить из таблицы MigrationHistory, а после этого остается только применить все файлы с бо́льшими номерами. Сортировать файлы можно по номеру, поэтому с порядком выполнения миграций проблем не будет.

На такой скрипт также возлагается задача добавления записей о выполненных миграциях в таблицу MigrationHistory.

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

Плюсы, минусы, выводы


Быстрое и удобное выполнение миграции до последней версии;
Механизм нумерации версий. Номер текущей версии хранится прямо в БД;
Для максимального удобства нужны средства автоматизации выполнения миграций;
Неудобно добавлять комментарии к структуре БД. Если их добавлять в Baseline.sql, то в следующей версии они пропадут, т.к. основание будет сгенерировано с нуля вновь, в качестве дампа новой версии структуры. Вдобавок, такие комментарии будут быстро устаревать;
Возникают проблемы в процессе параллельной разработки в нескольких ветках репозитория. Так как нумерация файлов-миграций — последовательная, то под одинаковыми номерами в разных ветках могут возникнуть файлы с разными DDL-/DML-запросами. Как следствие, при слиянии веток придется либо вручную редактировать файлы и их последовательность, либо же в новой, «слитой» ветке начинать с нового Baseline.sql, учитывающего изменения из обеих веток.

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


Метод идемпотентных изменений

Этот метод описан в статье «Bulletproof Sql Change Scripts Using INFORMATION_SCHEMA Views» Фила Хэка. Описание схожего подхода также изложено в ответе на этот вопрос на StackOverflow.

Под идемпотентностью понимается свойство объекта оставаться неизменным при повторной попытке его изменить.
В тему вспоминается смешная сцена из «Друзей» :)

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

Эту идею проще всего уяснить на примере. Допустим, вам нужно добавить в БД новую таблицу. Если вы хотите, чтобы в том случае, если она уже существует, при выполнении запроса не возникло ошибки, — в MySQL для этих целей есть краткий синтаксис:
CREATE TABLE IF NOT EXISTS myTable
(
    id INT(10) NOT NULL,
    myField VARCHAR(255) NULL,
    PRIMARY KEY(id)
);

Благодаря ключевой фразе IF NOT EXISTS, MySQL попытается создать таблицу только в том случае, если таблицы с таким именем еще не существует. Однако такой синтаксис доступен не во всех СУБД; к тому же, даже в MySQL его можно использовать не для всех команд. Поэтому рассмотрим более универсальный способ:
IF NOT EXISTS
(
    SELECT *
    FROM information_schema.tables
    WHERE table_name = 'myTable'
        AND table_schema = 'myDb'
)
THEN
    CREATE TABLE myTable
    (
        id INT(10) NOT NULL,
        myField VARCHAR(255) NULL,
        PRIMARY KEY(id)
    );
END IF;

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

Стоит отметить, что в MySQL по какой-то причине запрещено выполнять DDL-запросы внутри условных выражений. Но этот запрет легко обойти — достаточно включить все подобные запросы в тело хранимой процедуры:
DELIMITER $$

CREATE PROCEDURE sp_tmp() BEGIN

IF NOT EXISTS
(
    --
    -- Условие.
    --
)
THEN
    --
    -- Запрос, изменяющий структуру БД.
    --
END IF;

END;
$$

DELIMITER;

CALL sp_tmp();

DROP PROCEDURE sp_tmp;


Что за птица такая — information_schema?


Полную информацию о структуре базы данных можно получить из специальных системных таблиц, находящихся в базе данных с именем information_schema. Эта база данных и ее таблицы — часть стандарта SQL-92, поэтому этот способ можно использовать на любой из современных СУБД. В предыдущем примере используется таблица information_schema.tables, в которой хранятся данные о всех таблицах. Подобным образом можно проверять существование и метаданные полей таблиц, хранимых процедур, триггеров, схем, и, фактически, любых других объектов структуры базы данных.

Полный перечень таблиц с подробной информацией об их предназначении можно посмотреть в тексте стандарта. Краткий перечень можно увидеть в уже упоминавшейся выше статье Фила Хэка. Но самый простой способ, конечно же, — просто открыть эту базу данных на любом рабочем сервере БД и посмотреть, как она устроена.

Пример использования


Итак, вы знаете, как создавать идемпотентные SQL-запросы. Теперь рассмотрим, как этот подход можно использовать на практике.

Пример того, как в этом случае может выглядеть папка с sql-файлами:
Database
 |- 3.01
 |   |- Baseline.sql
 |   '- Changes.sql
 |
 '- 3.02
     |- Baseline.sql
     '- Changes.sql

В этом примере для каждой минорной версии базы данных создается отдельная папка. При создании каждой новой папки генерируется основание и записывается в Baseline.sql. Затем в процессе разработки в файл Changes.sql записываются все необходимые изменения в виде идемпотентных запросов.

Предположим, в процессе разработки в разное время программистам понадобились следующие изменения в БД:
a) создать таблицу myTable;
b) добавить в нее поле newfield;
c) добавить в таблицу myTable какие-то данные.

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

К примеру, один из разработчиков создал на своей локальной копии БД таблицу myTable, записал изменение a) в хранящийся в общем репозитории кода файл Changes.sql, и на какое-то время забыл о нём. Теперь, если он выполнит этот файл на своей локальной БД, изменение a) будет проигнорировано, а изменения b) и c) будут применены.

Плюсы, минусы, выводы


Очень удобное выполнение миграций с любой промежуточной версии до последней — нужно всего лишь выполнить на базе данных один файл (Changes.sql);
Потенциально возможны ситуации, в которых будут теряться данные, за этим придется следить. Примером может служить удаление таблицы, и затем создание другой таблицы с тем же именем. Если при удалении проверять только имя, то обе операции (удаление и создание) будут происходить каждый раз при выполнении скрипта, несмотря на то, что когда-то уже выполнялись;
Для того, чтобы изменения были идемпотентными, нужно потратить больше времени (и кода) на их написание.

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


Метод уподобления структуры БД исходному коду

Отдельных статей, посвященных этому подходу, я, к сожалению, не нашел. Буду благодарен за ссылки на существующие статьи, если таковые имеются. UPD: В своей статье Absent рассказывает о своем опыте реализации схожего подхода при помощи самописной diff-утилиты.

Основная идея этого метода отражена в заголовке: структура БД — такой же исходный код, как код PHP, C# или HTML. Следовательно, вместо того, чтобы хранить в репозитории кода файлы-миграции (с запросами, изменяющими структуру БД), нужно хранить только актуальную структуру базы данных — в декларативной форме.

Пример реализации


Для простоты примера будем считать, что в каждой ревизии репозитория всегда будет только один SQL-файл: CreateDatabase.sql. В скобках замечу, что в аналогии с исходным кодом можно пойти еще дальше и хранить структуру каждого объекта БД в отдельном файле. Также, структуру можно хранить в виде XML или других форматов, которые поддерживаются вашей СУБД.

В файле CreateDatabase.sql будут храниться команды CREATE TABLE, CREATE PROCEDURE, и т.д., которые создают всю базу данных с нуля. При необходимости изменений структуры таблиц, эти изменения вносятся непосредственно в существующие DDL-запросы создания таблиц. То же касается изменений в хранимых процедурах, триггерах, и т.д.

К примеру, в текущей версии репозитория уже есть таблица myTable, и в файле CreateDatabase.sql она выглядит следующим образом:
CREATE TABLE myTable
(
    id INT(10) NOT NULL,
    myField VARCHAR(255) NULL,
    PRIMARY KEY(id)
);

Если вам нужно добавить в эту таблицу новое поле, вы просто добавляете его в имеющийся DDL-запрос:
CREATE TABLE myTable
(
    id INT(10) NOT NULL,
    myField VARCHAR(255) NULL,
    newfield INT(4) NOT NULL,
    PRIMARY KEY(id)
);

После этого измененный sql-файл сабмиттится в репозиторий кода.

Выполнение миграций между версиями


В этом методе процедура обновления базы данных до более новой версии не так прямолинейна, как в других методах. Поскольку для каждой версии хранится только декларативное описание структуры, для каждой миграции придется генерировать разницу в виде ALTER-, DROP — и CREATE-запросов. В этом вам помогут автоматические diff-утилиты, такие, как Schema Synchronization Tool, входящая в состав SQLyog, TOAD, доступный для многих СУБД, Dklab_pgmigrator для PostgreSQL от DmitryKoterov, а также, SQL Comparison SDK от RedGate.

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

Как быть с изменениями данных?


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

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

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

Плюсы, минусы, выводы


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

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


Готовые решения для версионной миграции БД

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

Некоторые из них рассмотрены в недавней статье «Подходы для версионирования баз данных» Дениса Гладких.

Ниже перечислена лишь малая часть готовых к использованию систем версионной миграции:

В заключение

Существует огромное количество разных способов хранить и применять изменения в базах данных. Какой из них вы выберете для своего проекта — не так уж и важно, главное — принять один из методов и неуклонно ему следовать. Остальное — детали. Именно эту мысль я попытался донести этой статьей, добавив классификацию простейших методов со своими мыслями — в надежде, что они помогут вам выбрать наиболее подходящее для вас решение.
Дмитрий Шевченко @Shedal
карма
108,2
рейтинг 0,0
Пользователь
Самое читаемое Разработка

Комментарии (82)

  • +5
    Для php очень неплохо работает Doctrine Migrations — используюся инкрементные изменения. Если использовать в сочетании с ORM, то частично и 3ий метод используется (изменения генерируются из описания маппинга).
  • +1
    В некоторых фреймворках, например Yii Framework, версионирование БД уже предусмотрено. Прежде чем рассматривать внешние решения, убедитесь, что в вашем фреймворке нет своего.
    • +1
      Убил кучу времени, пока нашёл версионирование БД для Yii. Причём сначала нашёлся экстеншн, который потом стал частью фреймворка :) Короче, вот ссылка на детальное описание: www.yiiframework.com/doc/guide/1.1/en/database.migration

      И кстати, это версионирование в Yii появилось совсем недавно, буквально год назад ещё ничего не было :)
  • +1
    спасибо автору за статью, добавил в избранное
  • +3
    Для django удобно работать с south — реализация варианта «Метод инкрементных изменений».
  • +1
    Мы в проекте все изменения базы данных записываем в два скрипта: статичный, для создания базы с нуля (с create-ами), и инкрементный лог, для апгрейда версий (с alter-ами). Задача — удостовериться, что оба метода создают одинаковые базы данных. Это делает ночной билдер, создавая базы обоими методами и сравнивая их information schema.
    • 0
      Не совсем понятно, можете уточнить? От какого основания отталкиваются ваши инкрементные изменения?
      • +2
        Если я правильно понял, то: есть начальное состояние A0, есть конечное состояние An, есть набор изменений X1-Xn. Соответсвенно на пустую базу можно сразу накатить An, а на базу в версии Am через изменения Xn.
        Скрипт проверяет условия равенства A0 + X1...Xn = An
        • 0
          Понял. Да, это имеет смысл.
      • 0
        От первой версии, поставленной на продакшн. После этого собственно и завели инкрементный лог.
  • +1
    Подскажите, есть ли инструменты для версионирования er-диаграмм? Например, может ли Erwin или PowerDesigner хранить несколько состояний базы и генерировать скрипты для перехода между любым из состояний? Было бы идеально, чтобы на каждый schema-update можно было повесить дополнительный data-update скрипт, но это наверное что-то из области фантаскики :)
    • 0
      В некотором смысле тот же SQLyog подходит под ваше описание. Он может отображать диаграммы связей, и умеет генерировать diff'ы. Только делает это на реальных копиях БД. И проблема состоит в том, что он не заточен под именно эту функциональность, поэтому будет не слишком удобно.
      Я уверен, что есть и другие инструменты со схожим функционалом, но у меня пока не было времени для их обзора.
  • +3
    Еще в Oracle есть ретроспективные запросы и flashback'и
    • +1
      В Oracle чего только нет. Например, отчасти по данной теме, в версии 11gR2 появилась интересная фича, называемая Edition Based Redefinition.

      Она не касается вопроса, как поддерживать версионирование структуры базы, но позволяет без проблем применять миграции на живую базу в режиме реального времени без остановок. Можно также одновременно держать две версии объектов базы данных (например, если один модуль еще не переписан под новую структуру) без их рассогласования.
      • 0
        Звучит интересно. В Oracle, и правда, чего только нет. Время от времени жалею, что пока так и не довелось с ним поработать.
      • 0
        Что-то мне показалось, что это хорошо замаскированные материализованные вьюшки.
  • +3
    Можно почитать книжку «Рефакторинг баз данных: эволюционное проектирование»
    Скотт В. Эмблер, Прамодкумар Дж. Садаладж
    • 0
      Спасибо, посмотрю. Там есть что-то по схожей теме?
      • +1
        Посмотрел на эту книжку. По теме там много чего есть. А ещё там есть как связать scrum и dba, хотя раньше мне это казалось малореальным. Буду дальше читать, книга на первый взгляд очень интересная :)
  • +1
    Метод инкрементных изменений мне кажется не до конца описан. Например, если мы поддерживаем несколько версий своих продуктов, и может быть такое, что в версии 2.3 была исправлена ошибка в хранимке, а самая свежая версия, к примеру, 2.6. Тут мы делаем обновление для 2.3, а что потом делать с остальными версиями, если там тоже нужно это обновление? Продублировать эти изменения в каждую из следующих версий (естественно, после тестирования), или есть другой, более верный вариант? Подскажите пожалуйста, кто уже сталкивался с этим
    • 0
      Имхо — править и выпускать версии

      2.3.1

      • 0
        2.4.1
        2.5.1 и т.д.
    • 0
      Так же как нужно поступать с исходным кодом в двух разных branch-ах — мержить.
      • +1
        Проблема в том, что с методе инкрементных изменений есть своя нумерация версий, которая может конфликтовать с версионностью системы контроля версий. К примеру, в бранче в какой-то момент уже могут быть свои изменения структуры БД под тем же номером, что и в транке, которые туда попасть не должны. А система контроля версий захочет все это смержить. В общем, есть, над чем подумать.
        • 0
          Вы не правильно поняли меня. Я имел ввиду, что если поддерживаются две различные версии продукта (а они должны поддерживаться в в двух разных бранчах), если исправляется как-то критический баг в одном из них (в младшей версии), то тогда чтобы изменения затронули и последнюю или придется скопипастить в ней эти исправления или дождаться момента мержинга двух бранчей. В любом случае делать это придется вручную для разруливания зависимостей.
    • 0
    • +1
      Да, вы правы, удобство работы с бранчами в системах контроля версий может быть еще одним критерием при выборе подхода. Мне представляется, что лучше всего с этим обстоит у третьего метода (так и не придумал, как его кратко назвать). Чуть хуже — у метода идемпотентных изменений, и совсем нехорошо у инкрементного подхода.

      Наверное, позже добавлю это в статью.
      • 0
        Я бы предложил для этого 4-й вариант. Инкрементные версии по таблицам/сущностям. Решит проблемы с мержем, ну и добавит больше гибкости (например можно откатить только одну таблицу, хотя откат — это очень скользкая тема). Счетчик версий один, так что легко знаем какие инкременты накатывать. Правда выйдет нечто вроде VCS в VCS, т.е. небольшая избыточность, но возможно получится использовать 3й метод в совокупности с VCS. Например, версия базы — это номер ревизии, делаем дифф с хэдом и накатываем. Здесь также структура лежит для каждой таблицы в отдельных файлах.
    • 0
      Немного дополнил статью своими мыслями по поводу слияния веток.
  • 0
    После долгих мучений с ораклом, написали таск, который экспортирует схему в текстовые файлы. А дальше уже системой контроля версий
  • +1
    Хорошая статья. Хотелось бы отметить, то что вы называете «Ruby Migrations», в действительности является ActiveRecord Migrations. ActiceRecord не является частью руби. В руби есть и другие ORM, поддерживающие миграции. Например, Sequel и DataMapper.
    • +1
      Спасибо за замечание, позже отредактирую статью.
  • 0
    Когда-то давно использовали скрипты миграции, сколько же с ними приключений было…

    Сейчас используем что-то близкое к «уподобления структуры БД исходному коду» только чуть дальше чем уровень бд.
    ORM + CodeFirst методология разработки.
    В исходном коде хранится структура классов, а уже ORM отвечает за то что будет находится в конкретной базе, при необходимости создавая/изменяя структуру.
  • 0
    Как вариант еще можно использовать ddl-триггеры для сбора ddl-скриптов, но вообще автоматическая генерация скл-скриптов апгрейда-дегрейда продакшн баз это абсолютное зло.
    • 0
      Мне кажется, это зависит от уровня вашего доверия к автоматическим diff-утилитам. И потом, никто не отменяет тестирования сгенерированных скриптов на копии продакшн базы.
  • +1
    У нас отлично второй способ работает — «Метод идемпотентных изменений».

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

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

    Ну и на закуску — номера версии в базе критичны, чтобы приложение могло проверить, а совместимо-ли оно с базой (или даже обновить базу самостоятельно).
    • 0
      Я так понимаю, каждое конкретное изменение у вас не идемпотентно само по себе? Просто ваша хранимая процедура принимает решение о применении каждого изменения, в зависимости от текущей версии БД? А каким образом она определяет, в какой версии было добавлено каждое изменение?
      • 0
        Да, действительно, не совсем оно… просто результат похож :-)

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

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

        Самое удобное — транзакционность самого файла обновлений — его можно накатывать сколько угодно.

        А сделать каждое изменение в базе идемпотентным — имхо на практике не реально.
    • +1
      Примерно так работает:

      CREATE OR REPLACE FUNCTION update_1_0_2() RETURNS INTEGER AS $PROC$
      BEGIN
      IF validate_update_script( '1.0.1', '1.0.2' ) = 1 THEN RETURN 1; END IF;

      ----- Собственно изменения

      RETURN 1;
      END;
      $PROC$ LANGUAGE plpgsql VOLATILE;

      SELECT * FROM update_1_0_2();



      Завязано на особенности PostgreSQL в работе с хранимыми процедурами (я-бы предпочел без них, но не получается по ряду причин).

      А это собственно, проверка версии:

      -- service function - no need to copy it to the next update script (it will be saved)
      -- this function returns 1 if the updated was already applied, and 0 if the update must be applied
      -- an exception is thrown if the update cannot be applied due to wrong database version
      CREATE OR REPLACE FUNCTION validate_update_script( old_version TEXT, new_version TEXT ) RETURNS INTEGER AS $PROC$
      BEGIN
      IF EXISTS( SELECT id FROM version WHERE id = new_version ) THEN
      RAISE INFO 'Already upgraded to version %, skipping.', new_version;
      RETURN 1; -- means already done - skip the update
      END IF;
      RAISE INFO 'Upgrading from version % to version %...', old_version, new_version;
      IF NOT EXISTS( SELECT id FROM version WHERE id = old_version ) THEN
      RAISE EXCEPTION 'Error: version % not found - cannot upgrade', old_version;
      END IF;
      -- set the version here, assuming that if a transaction fails, it will be rolled back
      INSERT INTO version( id, modified, update_number ) --VALUES( new_version, NOW() );
      SELECT new_version, NOW(), MAX(update_number) + 1 FROM version;
      IF NOT EXISTS( SELECT id FROM version WHERE id = new_version ) THEN
      RAISE EXCEPTION 'Error: version % not found - upgrade failed', new_version;
      END IF;
      RETURN 0; -- means continue with the update
      END;
      $PROC$ LANGUAGE plpgsql VOLATILE;
  • +2
    Могу, кстати, порекомендовать статью М. Фаулера по теме эволюционного дизайна баз данных, перевод на русский имеется там по ссылке

  • +1
    У метода инкрементых изменений есть существенный минус — при большом количестве версий миграция с очень старой версии до очень новой занимает все больше и больше времени. Так, в одном из моих старых проектов, полный скрипт от первой до последней версии весит уже 3 МБ и проигрывается несколько десятков минут даже на пустой базе. На рабочих БД миграция может идти сутками.

    Сейчас в разработке идея сделать т.н. jump-скрипты прямой конвертации между отдаленными версиями без промежуточной конвертации. Беда в том, что кроме изменения метаданных скрипты содержат и работу с данными.
    • 0
      Да, в таком случае вам, действительно, может подойти третий способ. Но в потенциале проблемы с данными есть всегда.

      С другой стороны, настолько ли часто вам нужно обновляться с очень старой версии до новой? Если же речь об очень старой версии baseline-файла, то проблема решается созданием нового baseline для каждой новой версии, или же подверсии.
      • +1
        Софт о котором я писал — тиражируемый. БД в комплекте. Клиенты обновляют свои БД самостоятельно при обновлении версии ПО.

        baseline'ы мы используем в разработке. фактически храним «эталон» БД каждой релизной версии
        • +1
          Ага, БД у клиентов — это даже интереснее. В таком случае, конечно, всегда желательно минимизировать время обновления.

          Если найдете какой-нибудь интересный подход к изменениям данных (привязка DML-запросов к номеру ревизии системы контроля, или еще что) — пишите, будет интересно почитать :)
          • +2
            Я планировал статью на эту тему сделать, вы меня в каком-то смысле опередили :)

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

            Напишу обязательно
    • 0
      т.е. вы предлагаете, для каждой версии писать отдельный (прямой) скрипт для старых версий?
      например, у нас имеется 4 версии:
      1,2,3,4
      При первом подходе, мы пишем инкрементальные скрипты для последовательного обновления:
      1->2, 2->3, 3->4
      а вы предлагаете поддерживать миграцию типа:
      1->2
      1->3
      1->4
      2->3
      2->4
      3->4
      Не слишком ли накладно получается?
      • 0
        для каждой версии конечно очень накладно. но если версия скажем 30, вполне можно кроме основной последовательности 1-2-...-30 сделать 1-10, 1-20, 1-30, 10-20, 20-30.
  • 0
    Спасибо за статью! Помогла разложить мысли по полкам :)

    Хочу еще добавить, что сейчас идет работа над 2 версией ECM7.Migrator.
    Добавлена возможность параллельного ведения нескольких независимых последовательностей номеров версий (например, когда в системе несколько модулей и каждый имеет собственную структуру БД), почти полностью переписано ядро. Также планируется добавить поддержку MSSQL CE 4.0 и возможность выполнения всех миграций в пределах одного подключения (для пересоздания БД в тестах на SQLite).

    Планируем релиз через 1-2 месяца.
    • 0
      :) Рад, что статья полезна.
      Интересно будет посмотреть на новую версию вашего мигратора. Сам я им не пользуюсь, но есть знакомые, которые используют и довольны.
  • +2
    Для контроля версий БД, особенно при работе большого количества разработчиков, была мной написана специальная утилита (для внутреннего использования). Которая умеет: сканировать и сохранять «снимок» структуры, сравнивать снимки/структуры, генерировать скрипт изменений.

    Анализ изменений:
    habrastorage.org/storage/264fb666/e8f75ce4/d4fef82b/d7001ddd.png

    Просмотр скрипта на изменение
    habrastorage.org/storage/65279ec4/eb2e479f/147fda2f/5f59112f.png

    • 0
      Вот это очень интересно! Не хотите написать статью на эту тему?
      • 0
        Хочу :) Не хватает сами знаете чего.
    • 0
      Судя по наречию SQL и коннекшен стрингам — это T SQL для ms SQL Server. А чем кроме цены не устроили существующие инструменты (кроме цены), такие как SQL Examiner www.sqlaccessories.com/SQL_Examiner/
      ?
      • 0
        1) цена. для себя бесплатно, и вообще я не против открыть доступ к своим исходникам. и уже сейчас отдать в использование собранный проект.
        2) сейчас реализован только SQL Server, но в коде заложены возможности использования «драйверов», когда-то я их сделаю (или кто-то поможет)
        3) будет возможность миграции не только структуры, но и данных
        4) ну и самое главное, всё что написано в этом проекте — будет использовано в нашем основном коммерческом продукте.
        • +1
          >>в коде заложены возможности использования «драйверов»

          интересно какой уровень абстракции предлагают ваши «драйвера», все же структура метаданных да и DDL-синтаксис разных СУБД сильно отличаются
          • 0
            Классы C# описывают объекты структуры. Некий интерфейс. Вы правы, разные БД отличаются сильно: но ведь не за всеми мелочами и требуется следить. (если всё таки требуется — уж простите).

            Вполне достаточно: таблица, представление, хранимка, функция, поле, тип, ключи (внешние и первичные), индексы, некоторые констрейнты.

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

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

            Сейчас нам было достаточно такие элементы: таблица, представление, процедура, функция, триггеры, поля, индексы, ключи (первичные, внешние), констрейнты, домены типов.
            • 0
              Мне на почту сначала пришло уведомление об вашем первом комментарии, но здесь, в комментариях, я его увидеть не смог. А теперь вижу оба ваших сообщения. Хабр глючит.
              • 0
                Да пришлось два раза писать :) Сам потерялся…
    • 0
      Для Interbase/Firebird есть отличный инструмент Database Comparer. он же включен в IDE IBExpert. пользуемся-ненарадуемся.
  • +2
    Спасибо за статью. От себя еще хочу добавить пару слов про один немаловажный критерий выбора подхода к версионированию — возможность отката до предыдущих версий. Я имею ввиду не удаление базы и применение миграций по порядку до нужной версии, а именно откат.

    Я использую один замечательный инструмент: Liquibase. Он предполагает хранение changelog в виде XML, сам по нему генерирует SQL код в прямую и в обратную сторону. Поддерживается достаточно большое число СУБД — вобщем, я им весьма доволен.
    • 0
      Да, удобство отката может быть еще одним критерием при выборе подхода. Спасибо, позже добавлю в статью.
  • 0
    >>Допустим, программисты в процессе разработки записывают свои изменения структуры и данных БД в отдельный файл в виде SQL-запросов

    это не очень удобно. С более-менее сложными БД как правило применяют визуальные средства разработки, скрипты напрямую никто не пишет. И тут то встает проблема версионирования. Даже логирование всех запросов не очень-то ее решает, т.к. в логе будет много лишних изменений/откатов.
    • 0
      Это был простой пример для того, чтобы наглядно показать, зачем держать версии БД и приложения синхронизированными. Вам можно было пропустить этот раздел :)
      • 0
        Просто было бы очень интересно почитать про автоматизированные механизмы учета изменений в структуре БД, независимо от способа их внесения.
  • +1
    Мы в работе используем вот такую вот тулзовину — github.com/davejkiger/mysql-php-migrations (возможно кому-то станет полезной тоже).

    Работает как нужно, с задачами справляется, кушать не просит. Да, инкриментальная миграция.
    • 0
      Да, хорошая вещь. Но неудобна по вышеупомянутым причинам — необходимость вручную логгировать все важные изменения в SQL-коде миграций. Поэтому я для нее создал простенький скриптик, который сравнивает сохраненную структуру с текущей и генерит готовую миграцию. Но все равно хотелось бы чего-то более удобного.
  • +1
    Нет идеального хранения структуры и миграции БД. В первом и втором способах требуется довольно много ручной работы (особенно во втором, иногда приходится творить чудеса, чтобы написать идемпотентные скрипты), велика вероятность ошибки. В идеале хорошо бы для каждого скрипта писать скрипт отката, но это еще столько же, если не больше, работы. Кроме того, необходима самописная тулза для применения изменений. В одном проекте для такой тулзы использовали интеграцию с svn, в качестве версии базы использовался номер коммита последнего примененного скрипта. И самое печальное — подходы со скриптованием изменений делают практически невозможным трэкинг изменений объектов базы.

    Метод хранения базы как исходного кода всем хорош. Никаких самописных скриптов, изменения можно делать на тестовой базе, а потом реверсить их в проект. Но главный недостаток — необходимость думать о миграции данных, и снова извращаться с ручными скриптами. Кроме того, нужен надежный инструмент. В свое время пришлось отказаться от RedGate, поскольку для MS SQL он генерировал скрипты настолько избыточные и глючные, что проще было писать вручную. Сейчас Database project в VS 2010 довольно стабилен, пользуюсь им в большинстве проектов с MS SQL.
  • +1
    У нас в компании используется что-то среднее между первым и вторым вариантом. Структура таблиц описывается разработчиками на XML. Из этого XML с помощью специальных XSL-файлов можно получить либо скрипт с DDL для создания хранилища с нуля, либо патч для внесения в таблицы идемпотентных изменений. Заодно этот XML используется для описания метамодели (по типу этой).
    • 0
      Это интересный подход. Только, по-моему, он ближе всего как раз к третьему методу, потому что, как я понял, вы используете декларативное описание всей структуры в виде XML. Возникли вот такие вопросы:
      1. Скажите, вы храните XML-описание в системе контроля версий?
      2. Каким образом вы получаете патч в виде идемпотентных изменений при помощи XSLT? Ведь у вас есть два XML-описания разных версий, и нужно сгенерировать патч как разницу между ними Не совсем представляю, как это сделать при помощи XSLT.
      3. Еще интереснее было бы узнать про описание этим файлом мета-модели.

      И вообще, на словах ваше решение довольно-таки красиво звучит :) Не расскажете ли поподробнее? Мне кажется, и статья на эту тему была бы хорошо воспринята.
      • 0
        И конечно, мой главный вопрос во всех комментариях: как вы поступаете с изменениями в данных? :)
        • 0
          Для изменения данных специальных механизмов нет. Фактически все делаем простыми и не очень update'ами. Но все они хранятся в патчах, которые инкрементально накатываются на базу. Это не удобно, но пока ничего лучше не придумали.
      • +1
        1. Да, XML хранится в CVS (так исторически сложилось).
        2. Каждое изменение описывается в виде блока на PL/SQL по типу той, что в статье с information_schema. Проверяется, есть ли таблица/индекс/столбец таблицы с описанными свойствами. Если нет, то делается нужный ALTER (или CREATE).
        Соответственно, такой блок можно получить из описания одного столбца на XML путем его непосредственного XSLT-преобразования сразу в PL/SQL. Сам XSL-файл, конечно, выглядит страшно :)
        3. Привел не очень подходящую ссылку. Метамодель у нас выполняет несколько другие функции. Где-то на Хабре видел более близкое описание, но не смог найти. Когда-нибудь, возможно, она будет приведена здесь в отдельной статье. Коротко так: в метамодели описываются сущности и их атрибуты. Эта модель имеет признаки «объектности»: сущности имеют методы, могут наследоваться, быть абстрактными.
        Список всех полей таблиц хранится в таблице метамодели. На него мапятся атрибуты сущности, которые хранятся в соответствующих полях. Это может быть и не простейшее отображение один к одному, хотя бы потому, что поле на самом деле может не являться столбцом таблицы, а вычисляться по формуле.
        Формула, кстати — это атрибут поля, которое тоже является сущностью, хотя и не предметной модели, а самой метамодели. Поэтому описывается оно также, как и остальные атрибуты. Есть и описание таблицы-хранилища полей, которое в ней же и хранится.
        Заполняется хранилище полей из тех же XML, разумеется.
  • +2
    Я недавно выложил утилиту для создания DIFF-миграций в PostgreSQL, которой пользовался в разных проектах последние несколько лет. Вот она: dklab.ru/lib/dklab_pgmigrator/ — может, кому пригодится.

    Вещи, которые я подразумеваю при ее использовании (и которые считаю более-менее резонными исходя из предыдущего опыта, хотя кому-то они могут показаться спорными):
    1. Идеальная длина релиз-цикла — неделя, не больше (в идеале — как а Facebook, вообще каждый день). Чем чаще происходят релизы, тем меньше рассинхронизация БД на дев-сервере и на продакшене, тем проще бороться с их различиями.
    2. Откаты к предыдущим версиям на практике не так полезны, как кажутся (поэтому — ИМХО не нужны). Во многих случаях они даже и невозможны (например, удалилась таблица — как такое откатить?). Если откаты хочется применять для отката «неудачных релизов», то на сколько-нибудь больших базах они все равно будут происходить слишком медленно, чтобы их использовать. Лучше вместо откатов применять короткий релиз-цикл.
    3. При разработке используется единая БД для всех разработчиков (исходники веб-части, естественно, у всех свои). Это лишь самая простая и очевидная схема. Хотя я верю, что ситуация, когда у каждого своя БД, может быть удобнее, но на практике ни разу не получилось ее задействовать — опять же, все упирается в объем данных в БД, ведь на дев-сервере должна быть БД, максимально приближенная по данным к продакшену.
    4. Структура БД уже и так хранится в недрах сервера СУБД, поэтому дублировать ее еще раз в файлах и версионировать — не есть хорошо. Когда центральная точка находится в БД, а не в файлах, это не приведет к сюрпризам и потерям изменений. Т.е. отказ от хранения схемы в файле в качестве первичной точки — это защита от человеческого фактора.
    5. После наката всех DIFF-ов БД на продакшене должна стать гарантировано идентичной БД на дев-сервере. Т.е. DIFF генерируется именно как разница между структурой, извлеченной из СУБД дев-сервера, и структурой, извлеченной прямо с продакшена. (Поэтому моя утилита лезет на продакшен, чтобы взять актуальную схему оттуда.) Нельзя завязываться за то, что БД на продакшене меняется только скриптом миграции — всегда существует человеческий фактор, кто-нибудь когда-нибудь обязательно что-то поправит напрямую на продакшене (да и в аварийных случаях бывает удобно прокинуть один-два альтера, которые только что накатил на дев-сервер, чтобы починить «лежащий» сайт — по крайней мере, это лишняя степень свободы, не обязательно же ее каждый день использовать).
    6. Результирующий DIFF должно быть возможно подвергнуть ручной коррекции (как по альтерам, так и по данным), причем после таких ручных коррекций должен быть, опять же, гарантирован п.4.


    Ну и по процессу разработки:
    1. Должна быть общая для всех дев-база, на нее после каждого удачного релиза целиком копируется БД с продакшена вместе со всеми данными (либо — с большой их частью, плюс можно персональные данные искажать, это уже детали).
    2. Обязательно должна быть БД «репетиции релиза», на которую перед каждым релизом (выкладкой на продакшен) проводится накат миграции и полная проверка работоспособности сайта. БД «репетиции релиза» перед этим перезаливается самыми свежими данными с продакшена. Ее же, кстати, можно использовать для нагрузочного тестирования (т.е. там должны быть и веб-морды).
    3. Ну и, естественно, должна быть БД продакшена. :-) Причем, т.е. релиз вначале репетируется на «репетиционной» базе, вероятность того, что на продакшене возникнут проблемы при накате, практически равна нулю.

    • 0
      > Должна быть общая для всех дев-база, на нее после каждого удачного релиза целиком копируется БД с продакшена вместе со всеми данными
      Хе-хе, со всеми данными. К счастью, в большинстве случаев это невыполнимо. Хотя такая жесткая синхронизация всех баз сразу решает много проблем.
    • 0
      Спасибо за развернутое описание вашего процесса, и отдельное спасибо за diff-утилиту для PostgreSQL, добавил ссылку в статью.

      По поводу единой центральной БД для разработки — здесь, думаю, все зависит от проекта и команды. К примеру, когда мы использовали центральную dev-БД, время от времени это приводило к ошибкам, т.к. в процессе разработки какой-либо фичи не всегда сразу понятно, какими именно должны быть изменения в БД. Иногда, после нескольких экспериментов с разной структурой таблиц, человек забывал откатить все ненужные изменения, и в итоге оставлял как те, что нужны для работы фичи, так и те, что остались от экспериментов. Конечно же, при использовании diff-подхода, все эти изменения в итоге попадут на продакшн.
      Второй негативный момент центральной dev DB — временное несоответствие структуры БД версии приложения. Ведь когда один разработчик создает фичу и при этом меняет структуру общей БД, у других разработчиков что-то может сломаться в приложении, т.к. код, который работает с измененной структурой, еще не выложен в общий репозиторий кода.

      Поэтому мы пришли к следующей схеме:
      1. Центральной базы данных нет. Каждый разработчик работает с локальной базой данных.
      2. Скрипты для создания текущей версии БД с нуля хранятся в репозитории.
      3. Эти же скрипты умеют заливать во вновь созданную БД и один из наборов тестовых данных: легкий набор с минимально необходимыми для тестирования данными, и большой — для тестирования тяжелых запросов на больших объемах данных.
      4. При обновлении кода из репозитория, разработчик запускает скрипт, который обновляет и базу данных до последней версии. При этом с нуля она не создается, применяются только изменения по сравнению с прошлым обновлением. Так что много времени это не занимает.
      5. Когда человек заканчивает разработку фичи или баг-фикса, он отправляет в общий репозиторий кода как изменения в, собственно, коде, так и изменения в БД. Причем на этом этапе его изменения проходят через общую процедуру code review.
      6. Как и вы, мы практикуем «репетицию релиза» на копии продакшн базы данных.

      Между прочим, такой подход к разработке может использоваться с любым из трех методов версионирования, описанных в статье.
    • 0
      Очень вовремя! Сами хотели уже такой механизм писать. Будем пробовать.

      Пробежался по ридми — описание довольно краткое. Там все, что необходимо, описано? Какие-то недокументированные функции есть?
      • 0
        Упс, этот комментарий предназначался не сюда.
    • 0
      Очень вовремя! Сами хотели уже такой механизм писать. Будем пробовать.

      Пробежался по ридми — описание довольно краткое. Там все, что необходимо, описано? Какие-то недокументированные функции есть?
      • 0
        Ах, черт. Дмитрий, Вы сбили меня с толку, сделав в своем комментарии отступы:)

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