Миграция БД
В связи с тем, что я таки частично реализовал свою давнишнюю идею генерить опердень из модели опердени, хранящейся в этой же опердени, очередной раз решил поразмыслить над вопросом "как же должна выглядеть миграция данных при изменении модели".
Обычно я делаю примерно так: доработки, изменяющие структуру БД и задевающие хранящиеся данные, продумываются намного дольше и аккуратнее, чем остальные части, которые можно менять независимо (код, запросы, хранимые процедуры, итд), и выделяются в отдельные скрипты для миграции, которые при деплойменте доработок применяются сначала к тестовой, а потом боевой базе. Для некоторых проектов это вообще автоматизировано и выполняется вместе с обновлением софта специальной утилитой.
Но предположим, что мы сделали генератор опердени и дали его специально обученным настройщикам клепать предметную область. При этом они могут менять описания таблиц и полей (как сделано на данный момент) или структуры хранящихся объектов (как хотелось бы сделать будущем) и это, само собой, должно как-то в итоге отобразится на рабочей базе.
Первая мысль - что их изменения в рабочую базу попадают только в виде сгруппированного набора непротиворечивых действий, т.е. вперемежку изменений данных и метаданных таким образом чтобы база не оказалась в состоянии типа "в not null полях сидят null". Некоторые базы такое не дают делать, у некоторых при DDL операциях такое возможно, потом бэкап черезжопными методами восстанавливать придется.
Второе - что их работа над моделью должна вестись под системой контроля версий. Но работа ведется, во-первых, не над текстом модели, а в специализированном GUI (который может отражать состояние некоего текста, а может и не отражать - сейчас модель хранится в специально заточенной базе Firebird), а во вторых, _текстовый_ контроль версий не отражает происходящего с моделью в, условно говоря, строго типизированном виде.
Т.е. например "добавить поле в таблицу" с точки зрения системы типов выглядит как модификация описания этого типа методом "добавить в список полей еще один элемент", а с точки зрения какого-нибудь меркуриала - это просто появление новой строки где-то в середине файла модели и ему пофиг на смысл этой строки. Конечно, из hg diff можно было бы сделать описание операции в типизированном виде, но это, по моему убеждению, есть удаление гланд через задницу, т.к. текст должен генерироваться из строго типизированных данных, а не оные данные должны доставаться методом парсинга текста хз какого формата.
На данный момент у меня рабочая идея примерно такая:закрыть бухгалтерам доступ к базе опердени, написать по емылу им, что их софт больше не обслуживается, выключить телефон, завтра вместо работы пойти купить зеркалку и поехать фотографировать уток на речку к базе данных модели добавляется лог всех произведенных в ней изменений в отдельные таблицы со сквозной нумерацией. Этот номер в логе и будет текущей "ревизией" модели. При каждой генерации и деплойменте в результатирующий скрипт деплоймента вставляется номер ревизии. И в состав скрипта деплоймента будут входить все изменения от начала (ну или если мы точно знаем что база какой-то версии - от этой версии) до текущей ревизии.
Сейчас у меня кодогенерация происходит по текущей модели, т.е. условно говоря, это "создать базу с нуля до текущего состояния". А реально нужно другое, более сложное: нужно накатить на существующую базу изменения "от прошлой кодогенерации до текущей". Причем накатить непротиворечивым образом. И вместо кодогенератора который делает из модели просто скрипты создания всех нужных объектов базы данных и описание GUI к ним, нужно каким-то образом из списка изменений сделать скрипты изменения схемы базы данных и обновления данных в базе (я думаю, не всегда тривиальных).
И в таком случае вылазят всякие хитрые заморочки, в основном связанные с зависимостями объектов на уровне метаданных и зависимостями на уровне данных - например, в Firebird грохнуть объект, на который ссылается другой объект просто невозможно - нужно сначала грохнуть зависимый(а если от него еще что-нибудь зависит - то по цепочке, или же сделать объект с пустым телом). На уровне данных - это всякие внешние ключи, которые не дадут изменить данные если их создать слишком рано (поэтому всякие утилиты импорта данных сначала всасывают данные а потом создают внешние ключи).
С теоретической точки зрения это все выглядит как список функций "МодельN+1=F(N)(МодельN)", потом функция, которая из функций преобразования модели генерит скрипты преобразования базы данных, возможно, анализируя список изменений на предмет "сгрупировать изменения в один скрипт", и функций преобразования "старая версия объекта"->"новая версия объекта" и анализа "можно это сделать средствами SQL(alter table) или придется долбится курсорами по всей базе до посинения".
Кстати, я до сих пор не очень понимаю, что в таких случаях предлагаю всякие NoSQL базы. Я так понимаю, там же ложится/достается чисто объект по ключу в хранилище. А если мы поменяем описание объекта в коде программы, нужно ж будет описывать явно "что делаем при загрузке старой версии объекта". О ад-хок запросах к таким вещам даже и думать не хочется.
Обычно я делаю примерно так: доработки, изменяющие структуру БД и задевающие хранящиеся данные, продумываются намного дольше и аккуратнее, чем остальные части, которые можно менять независимо (код, запросы, хранимые процедуры, итд), и выделяются в отдельные скрипты для миграции, которые при деплойменте доработок применяются сначала к тестовой, а потом боевой базе. Для некоторых проектов это вообще автоматизировано и выполняется вместе с обновлением софта специальной утилитой.
Но предположим, что мы сделали генератор опердени и дали его специально обученным настройщикам клепать предметную область. При этом они могут менять описания таблиц и полей (как сделано на данный момент) или структуры хранящихся объектов (как хотелось бы сделать будущем) и это, само собой, должно как-то в итоге отобразится на рабочей базе.
Первая мысль - что их изменения в рабочую базу попадают только в виде сгруппированного набора непротиворечивых действий, т.е. вперемежку изменений данных и метаданных таким образом чтобы база не оказалась в состоянии типа "в not null полях сидят null". Некоторые базы такое не дают делать, у некоторых при DDL операциях такое возможно, потом бэкап черезжопными методами восстанавливать придется.
Второе - что их работа над моделью должна вестись под системой контроля версий. Но работа ведется, во-первых, не над текстом модели, а в специализированном GUI (который может отражать состояние некоего текста, а может и не отражать - сейчас модель хранится в специально заточенной базе Firebird), а во вторых, _текстовый_ контроль версий не отражает происходящего с моделью в, условно говоря, строго типизированном виде.
Т.е. например "добавить поле в таблицу" с точки зрения системы типов выглядит как модификация описания этого типа методом "добавить в список полей еще один элемент", а с точки зрения какого-нибудь меркуриала - это просто появление новой строки где-то в середине файла модели и ему пофиг на смысл этой строки. Конечно, из hg diff можно было бы сделать описание операции в типизированном виде, но это, по моему убеждению, есть удаление гланд через задницу, т.к. текст должен генерироваться из строго типизированных данных, а не оные данные должны доставаться методом парсинга текста хз какого формата.
На данный момент у меня рабочая идея примерно такая:
Сейчас у меня кодогенерация происходит по текущей модели, т.е. условно говоря, это "создать базу с нуля до текущего состояния". А реально нужно другое, более сложное: нужно накатить на существующую базу изменения "от прошлой кодогенерации до текущей". Причем накатить непротиворечивым образом. И вместо кодогенератора который делает из модели просто скрипты создания всех нужных объектов базы данных и описание GUI к ним, нужно каким-то образом из списка изменений сделать скрипты изменения схемы базы данных и обновления данных в базе (я думаю, не всегда тривиальных).
И в таком случае вылазят всякие хитрые заморочки, в основном связанные с зависимостями объектов на уровне метаданных и зависимостями на уровне данных - например, в Firebird грохнуть объект, на который ссылается другой объект просто невозможно - нужно сначала грохнуть зависимый(а если от него еще что-нибудь зависит - то по цепочке, или же сделать объект с пустым телом). На уровне данных - это всякие внешние ключи, которые не дадут изменить данные если их создать слишком рано (поэтому всякие утилиты импорта данных сначала всасывают данные а потом создают внешние ключи).
С теоретической точки зрения это все выглядит как список функций "МодельN+1=F(N)(МодельN)", потом функция, которая из функций преобразования модели генерит скрипты преобразования базы данных, возможно, анализируя список изменений на предмет "сгрупировать изменения в один скрипт", и функций преобразования "старая версия объекта"->"новая версия объекта" и анализа "можно это сделать средствами SQL(alter table) или придется долбится курсорами по всей базе до посинения".
Кстати, я до сих пор не очень понимаю, что в таких случаях предлагаю всякие NoSQL базы. Я так понимаю, там же ложится/достается чисто объект по ключу в хранилище. А если мы поменяем описание объекта в коде программы, нужно ж будет описывать явно "что делаем при загрузке старой версии объекта". О ад-хок запросах к таким вещам даже и думать не хочется.
no subject
интересно как штуки "выделить емейл из одной таблички и имя из другой в отдельную табличку "люди" и создать форейн кеи в ту табличку" изображаются.
(no subject)
(no subject)
(no subject)
no subject
1. тип объекта
2. его атрибуты
3. его значения
ну и связывающие таблицы. Но это довольно муторно и работает на больших объемах данных стремно.
(no subject)
no subject
Почитай как сделаны migrations в ruby on rails, там как раз оно самое, даже в две стороны, насколько я помню.
(no subject)
(no subject)
(no subject)
no subject
^^^^^^^^^^^^
Некоторые делают проще - пишут конвертер БД с релиза 1.1 до 1.2. В результате видел набор примерно такой:
converter1_2.exe
converter2_3.exe
converter3_4.exe
converter4_5.exe
и на выходе теоретически должна была получиться версия 1.5
Правда сопряжено сие с выводом БД в оффлайн на определенное время.
no subject
И да, алгебра преобразований над моделью должна позволять безмозглое транслирование в SQL's ALTER TABLEs.
(no subject)
(no subject)
(no subject)
no subject
Задача то старая и вроде как решённое (в том плане, что однозначно существуют некоторые подходы по решению данной проблемы и их реализация). Версионность схемы базы данных уже давно не рокет сайенс.
P.S. пример из старого рабочего
class GeodatasUniqueAltAndLen < ActiveRecord::Migration
def self.up
create_table "geodatas_unique_alt_and_lens", :force => false, :row_version => false do |t|
t.column "Breite_Abs", :decimal, :precision => 10, :scale => 8
t.column "Laenge_Abs", :decimal, :precision => 10, :scale => 8
end
load_from_sqlscript("geodatas_unique_alt_and_lens", geodatas_unique_script)
end
def self.load_from_sqlscript(table_name, script)
cnx = ActiveRecord::Base.connection
cnx.insert(script)
end
def self.down
drop_table :geodatas_unique_alt_and_len
end
def self.geodatas_unique_script
<<'END_OF_DATA'
INSERT INTO geodatas_unique_alt_and_lens
("Breite_Abs", "Laenge_Abs")
select distinct "Breite_Abs", "Laenge_Abs"
from geodatas
group by "Breite_Abs","Laenge_Abs"
END_OF_DATA
end
end
(no subject)
(no subject)
(no subject)
(no subject)
(no subject)
no subject
то, что не изменилось, переселяецца автоматически
что изменилось, затачиваецца напильнегом
(no subject)
(no subject)
(no subject)
(no subject)
no subject
(no subject)
(no subject)
no subject
Как вариант - отказаться от удаления (вообще любого), ввести версионность объектов и метаданных, отказаться от мержинга и соблюдать строгую очередность. Вот бы еще БД, которая метаданные с таблицами таскала и голову не морочила... Cache чтоль вспомнить?
(no subject)
(no subject)
no subject