Pull to refresh

Comments 68

Почему мы не использовали — из коробки Kohana не поддерживает эти миграции, использовать для этого Doctrine тоже не хочется — слишком уж он монструозен ИМХО.

Doctrine Migrations — относительно независимая билиотека, работает с чистым sql, можно использовать с чем угодно, правда 5.3+, но думаю, что это уже не проблема. Так что про монструзность — зря.
С миграциями(symfony 1.4+doctrine ), кстати, есть проблема, когда больше одного человека меняют основную структуру бд.
У нас был постоянный конфликт версий бд, т.к. обычно разработка ведется параллельно. Приходилось в первую очередь выкладывать миграцию, чтобы отталкиваясь от нее напарник мог разрабатывать свою.
Если используются плагины это не так страшно, а вот если основная структура, то нужно заранее предупреждать, о том, что будет миграция.
А у нас для этого было простое решение, у каждого разработчика была своя бд.
и как вы сводили миграции? особенно если уже работающий сервер и перебилдивать его нельзя.
А SQLyog пробовали в качестве генератора diff'ов? По моему опыту, он довольно-таки неплохо справлялся с этой задачей. С переименованием колонок проблем точно никаких не было :)
Это допустимо когда вы один работаете над проектом.
А когда 6 человек, каждый имеет право изменять структуру базы и у каждого своё рабочее пространство — это уже не вариант. Однозначно нужны средства синхронизации.
Не совсем понятно, что вы имеете в виду.
Предположим, есть 6 человек, которые разрабатывают приложение. Версионирование БД в проекте — по «методу уподобления структуры БД исходному коду» (см. исходную статью).
Когда им нужно обновить свою локальную копию БД до последней версии, ее можно просто создать с нуля и накатить какой-то простой набор тестовых данных.
Генерировать diff-скрипт в этом случае нужно только при деплое на продакшн, где в БД уже есть данные.

Вообще, как обычно, все зависит от процесса и специфики проекта. Непонятно, из каких предположений вы исходили, когда писали: "[...] это уже не вариант. Однозначно нужны средства синхронизации."
Это отлично, что вы не стали слепо следовать какому-то методу, а выработали для себя максимально простой способ, который просто работает.
У нас на одном проекте тоже используется упрощенная версия метода инкрементных изменений. Версии приложения выкатываются на продакшн примерно раз в месяц, промежуточных версий на продакшене никогда нет. Поэтому инкрементность идет поверсионно. У нас для каждой версии есть отдельная папка следующей структуры:
Database
 |- 3.09
 | |- Base.sql
 | |- Changes.sql
 | '- TestData.sql
 |
 '- 3.10
     |- Base.sql
     |- Changes.sql
     '- TestData.sql
Base.sql создает с нуля структуру БД — какой она была на момент деплоя предыдущей версии (т.е. в конце периода ее разработки). После выполнения на девелоперских машинах файла Base.sql, накатывается TestData.sql, содержащий минимальный набор тестовых данных.
Все последующие изменения в структуре БД записываются в Changes.sql. Именно этот файл будет выполнен на продакшен-БД во время деплоя.
Я так понимаю, девелоперы дропают всю базу, накатывают себе Base.sql, а потом измененный Changes.sql при изменении структуры другим членом команды? Интересный подход, спасибо.

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

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

Ну, а ваш подход, насколько я вижу, не сильно отличается от исходного метода инкрементных скриптов. Я так понимаю, основная разница — в том, что вы выбираете из БД не текущую версию (последнюю выполненную дельту), а список всех дельт, и затем выполняете все, которых в changelog'е еще нет.
Недостаток вы сами описали — все те же потенциальные проблемы при мердже. Причем, на мой взгляд, даже менее очевидные в случае возникновения. В исходном методе ругнется система контроля версий при мердже. А в вашем случае — уже БД при выполнении скриптов. Причем, даже необязательно ругнется: может быть, один из скриптов REPLACE'нет данные одним образом, а второй — другим, затерев изменения первого. Порядок выполнения таких мерджнутых скриптов (с одинаковыми номерами) вы контроллировать не можете. А их, между прочим, может быть намного больше одного, особенно если ветка разрабатывалась достаточно долгое время.
Еще один недостаток — неудобно комментировать структуру БД, в сравнении с методом структуры БД как исходного кода. Впрочем, это больше преимущество последнего :)

В общем говоря, особо страшных проблем я здесь не вижу. Если этот метод для вас работает — отлично.
Благодарю за развернутый ответ
А как вы решаете задачу миграции содержимого таблиц с суррогатными ключами?
Не понял вопроса, поясните
В таблицах есть записи с одинаковым id. Допустим в таблице 1 это объекта А, в таблице 2 это объект Б, нужно объединить таблицы, сохранив оба объекта.
Мне кажется, что эта задача не связана с версионированием структуры БД. При любом подходе тут возникнут одни и те же вопросы.

Как вариант решения — INSERT INTO tbl2 ([поля кроме id]) (SELECT [поля кроме id] FROM tbl1), когда наш суррогатный ID в tbl2 просто сделается новый для записей из tbl1.
При этом поломаются внешние ключи на tbl1, но я, в общем-то, тоже не понимаю, к чему тут этот вопрос.
UFO just landed and posted this here
А я видел в одном проекте, такую задачу решили.
Схема миграций была та же, миграции были в директориях,
где файл *.sql был обязательным, он накатывался на бд,
а рядом опционально мог быть и *.php, он выполнялся,
если есть, после накатывания.
А как вы определяете, какая дельта уже применена на конкретной базе, а какая — нет?
Примененные дельты сохраняются в табличке changelog (которая загружается из отдельного «начального» sql-дампа)
Возник такой вопрос:
Вася создал свою миграцию в бранче для «Запомнить меня» и
применил её к бд.
Далее ему понадобилось переключиться в транк.
Транк ничего не знает о новой фиче, может и не узнает никогда,
но база-то уже накачена. А вдруг та ветка вообще не будет далее
развиваться, — а база-то, опять же накачена.
Вы пишете, — «отказываемся от возможности отката», а как же данный пример?
Очень хороший пример, спасибо. На самом деле мы сталкиваемся с такой проблемой время от времени. Обычно я чтобы переключиться на транк использую дополнительную БД. На продакшене и тестовых серверах такого не произойдет — только на машинках разработчиков, а это уже намного проще обрабатывать кому как удобно. Мое мнение — это небольшая цена за существенное упрощение всего процесса.
Спасибо за ответ )
На самом деле, конечно, вопрос был с подковыркой,
я и сам сторонник подобной схемы миграций, но мы используем
её в ещё более упрощённом виде, дельты у нас чисто номерные.
Поэтому вообще из веток не создаём апгрейды.
Не согласен, что дельты с номерами это более упрощенный вид. Как раз отказ от номеров дельт дает очень много преимуществ в виде возможности независимо менять структуру БД и добавлять новые дельты в ветках и не думать об их нумерации. Это очень удобно.
Да, я о том и говорю, — что у вас продуманнее система.
А у нас «упрощённый вид» — имеется ввиду менее удобный,
миграций в ветках просто нет, не говоря об остальном,
поэтому, — обязательно приму на заметку.
Удобно в отдельную тулзу вынести «накачку» базы. Тогда после переключения в транк прогоняем все миграции и запускаем тулзу.
Посмотрите South для Django. Там очень хорошо миграции реализованы.
Они и в Symfony хорошо реализованы, по моему их идею там, как и многое другое
взяли из RoR, но это больше тема исходной статьи
Сам всегда писал скрипты обновления ручками и также отказывался от откатов. При этом старался запланировать обновление структуры так, чтобы не требовалось обновления кода или оно было минимальным, тогда при выкатке кода — код можно откатить обратно или в случае экстренной правки не требуется думать о том — есть ли поле или нет.
Несколько лет практикуем точно такую же систему, вполне удобно.

Раньше использовали простеньний мигратор, который позволял: применить все не примененные миграции, от последней примененной до указной и просто все. Потом научили его обрабатывать патчи (в нашем случае это код на перле). Патчи бывают полезны, когда необходимо произвести сложные манипуляции с данными в бд и возможностей sql недостаточно. Они подчиняются тем же правилам что и обычные миграции, только хендлер другой.
Вспомнился? Вы ж его только что опубликовали, нет?
Вспомнился и я его сразу опубликовал, да. И прошу ответа, если знаете :)
Слушайте, мб я что-то не понимаю. Объясните, зачем каждому разработчику свой инстанс базы данных? Почему не работать на одной и с нее уже формировать скрипт миграции структуры БД на продакшене?
Вы размышляете в пределах офиса?
Я размышляю в любых пределах.
Чем сложнее логика и больше людей работают над проектом тем больше вероятность конфликтов. Чем позже происходит синхронизация изменений — тем позже проблемы будут обнаружены и тем сложнее их исправлять. Если для исходного кода есть куча vcs, которые эти проблемы решают, то для БД таких инструментов нет.

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

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

В офисе можно поставить на какой-то общий сервер одну базу,
и почти безболезненно с ней работать.
При этом проблемы:
  • сервер упал и вся команда курит в ожидании поднятия
  • кто и когда и зачем внёс правки в бд непонятно, если нет
    системы миграций, и хранения миграций в vcs


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


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


По моему, — это уже достаточные основания для использования подобной схемы.
Ну и фактически обязательно использовать при удалённой разработке.

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

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

2. Работа в полном offline. Очень неудачный способ удаленной работы, скорее исключение, чем правило (могу много написать, но это будет оффтопиком). Городить огород с миграциями вместо копирования структуры ради возможности иногда работать offline — личный выбор каждого. С учетом возможных проблем слияния я бы не стал.

3. История изменения базы в репозитории. Если у вас > 100 таблиц то без возможности сделать annotate на полном дампе, чтобы увидеть всю картину история (тем более в виде дельт) вам не поможет. Если у вас < 100 таблиц, то такая история вам не нужна. Упрощать коммуникацию между участниками команды при удаленной работе можно более простыми методами, нежели комментарии к дельтам миграции.

По секрету расскажу вам идеальную схему удаленной работы. Есть сервер разработки, на нем крутиться web и 1 инстанс бд. У каждого разработчика есть свой documet_root на сервере в домашней папке. Разработчики правят локальную копию и через scp/rsync закидывают правки на сервер и смотрят на результат на сервере. В случае успеха отправляют правки в репозиторий.
Работать можно из любого места где есть интернет и с любого устройства, где есть ssh. Все изменения конфигурации etc производятся один раз и влияют на всех.
Расскажи мне по секрету: модульные / приемочные тесты они тоже одновременно на одной базе гоняют? :)
Расскажу. Да. Тесты либо минимально используют БД, либо обернуты транзакциями и ничему не мешают.
Судя по всему — вы тролль.
Расскажи, как у вас пишутся функциональные тесты, которые по минимум используют бд? :)
Функциональные тесты оборачиваются в транзакции.
Вообще-то хорошем тоном является наличие у каждого разработчика своего тестового окружения, в котором может полноценно крутиться разрабатываемая система. И нормальные разработчики перед каждым коммитом гоняют как минимум модульные, а как максимум и функциональные тесты.
Вообще-то все зависит от того, что вы имеете ввиду под словами «тестовое окружение». Описанное мной — вполне является тестовым окружением. Некоторые системы крутятся на большом числе машин и к разработчикам на ноуты не влезают, чтобы там полноценно крутиться.
А еще нормальные разработчики внимательно читают то, на что отвечают и не домысливают значение фразы «в случае успеха»
По секрету расскажу вам идеальную схему удаленной работы. Есть сервер разработки, на нем крутиться web и 1 инстанс бд

Это вполне помещается на ноут. Повторюсь: если в системе есть хотя бы какие-то тесты (модульные, функциональные), описанная выше схема работать не будет, о чем я жирно намекнул в соседней ветке комментариев.
Расскажите, почему модульные тесты, обернутые в транзакции не будут работать на общей БД? Я вот только что открыл teamcity, и там, о чудо, они работают.
Используемая у вас в проекте СУБД позволяет в транзакции записать данные, потом прочитать, а потом сделать откат?
Вы удивитесь, но я не знаю СУБД, которые этого не умеют. MySQL (innoDB) и PosgreSQL с этой задачей справляются.
Уточнил у ребят — действительно все так, это я напутал. Так что насчет невозможности был не прав, теоретически это возможно. Но интересно, как это работает, если в самом проекте используются транзакции?
В разных обертках для работы с БД по разному, но принцип вроде везде одинаковый. Первый begin() открывает транзакцию, остальные — просто увеличивают счетчик. commit() счетчик уменьшает и при достижении нуля транзакция накатывается. rollback() тоже уменьшает счетчик, но при уменьшении счетчика до 0 — всегда будет откат транзакции, независимо от того что было в промежутках — commit или rollback().
Схема не идеальная, но поскольку обычно логика приложения без вложенных транзакций и нам важно просто сделать откат в tearDown() — вполне работает.
Если делать старт транзакции в setup, а откат в teardown, то наличие транзакций в тестируемом коде приведет к неприятным последствиям. По крайней мере в mysql, где старт новой транзакции влечет автоматический коммит предыдущей.

Еще неясно следующие: в ветке удалили таблицу, поправили тесты и перед мерджем их запустили. Как в это время гонять тесты в других ветках, которым все еще нужна таблица?
Видимо, я непонятно выразился в предыдущем комментарии. Транзакция стартует и заканчивается один раз. Остальные вызовы begin()/commit()/rollback() — фиктивные и увеличивают/уменьшают счетчик.

Удаление таблицы — действие, затрагивающее всю архитектуру. Как рефакторинг авторизации или acl. Чем позже такие изменения интегрируются с ветками тем сложнее интеграция. Не должно быть ситуации, что есть feature branches, которым нужна удаленная таблица — сразу после окончания рефакторинга нужно интегрироваться.
Дык кто этим занимается: вы сами, или поддержка есть в системе CI, которую вы используете? В любом случае, это не решает проблему с кодом, который использует транзакции (он работает некорректно, тесты не проходят).

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

Если разработчик своими изменениями сломал БД в момент правки, разве он не сломает ее делая все тоже самое через вашу систему миграций?

Если разные ветки — это разные проекты, которые больше никогда не сольются вместе — тут возражений нет. Если ветки нужно будет сливать — что будет, если за время расщепления в БД успеют поменяться имена таблиц и полей?
Наиболее частые изменения, как вы отметили в посте, инкрементальные — добавление таблиц и полей, иногда индексов. От того, что поля будут добавлены, но не будут использоваться в чужих ветках — большой беды не будет. Если же изменения необратимые (удаление полей/таблиц) — то чем раньше все об этом узнают (пусть даже получив fatal) — тем быстрее будут внесены нужные изменения в код.
Не согласен.

Простейший пример: Петя переименовывет табличку users_tokens в user_tokens. Для этого он переименовывает ее в БД и переименовывет в каком-то методе is_authorized(). Это к примеру.

Что произойдет, когда работа ведется на общем сервере БД? Петя переименовал табличку, но еще не закоммитил измененный метод is_authorized(). У всех остальных разработчиков сайт поломался. Они кричат «кто сломал БД?». Петя — «я, сейчас закоммичу код». Все ждут Петю, Петя коммитит, все обновляются, все хорошо. Удобно? Мне кажется, не очень. А если Петя работает в бранче, это часть какой-то большой фичи, и не хотелось бы до полного тестирования сливать ее с транком? Нужно отдельно об этом думать.

Что произойдет, если команда работает с миграциями? Петя добавил миграцию с переименованием таблички, изменил код. Когда он закоммитит эти изменения (или смержит с транком, если работает в бранче отдельном), остальные после апдейта получат измененный метод is_authorized() и дельту с изменением имени таблички. Накатят апдейт своей локальной БД и продолжат работать.

Это, как показывает моя практика — самая частая задача при совместной разработке. Конечно, есть более сложные моменты, когда так все гладко не будет. Но это как конфликты в мержах (SVN) — они бывают всегда, и их нужно разруливать аккуратно руками.
Ок.
Сценарий 1 — Петя сломал всем сайт, починил за 5 минут, вся команда в курсе изменений и возможно выявила проблемы с переименованием, о которых забыл Петя.

Про тестирование большой фичи в отдельной ветке. Нельзя делать полное тестирование фичи, пока она не проинтегрирована в транк.

Сенарий 2 — Петя переименовал таблицу, сделал миграцию написал код и ушел домой.
Вася решил переименовать поле в этой таблице. Сделал миграцию и ушел домой, но перед уходом сказал Маше, что он поменял поле, т.к она тоже работает с авторизацией
Маша засиделась до поздна.
Утром Петя и Вася полчаса занимаются сексом с базой, кодом и файлами миграции. Маша смотрит на них с упреком т.к не понимает, что произошло.
Вот тут и нужно выбирать — для какого-то проекта проблемы в сценарии 1 окажутся более частыми и неприятными, для какого-то — проблемы в сценарии 2.
перед уходом сказал Маше, что он поменял поле

При работе с миграциями ничего никому говорить не требуется.
Или я не понял, — о чём «Сценарий 2».
Почему утром Петя и Вася «занимаются сексом с базой»?
А в чем конкретно проблема тестирования фичи в ветке? Сделал фичу, прогнал тесты. Если все ок, влил в ветку транк, опять прогнал тесты. В случае наличия конфликтов решаются они все в ветке.
Под словами «полное тестирование фичи» я понимал не только автоматические, но и ручные тесты. Тестировать руками очень дорого и имеет смысл только код, максимально готовый к выезду на продакшен.
Я так и не понял: в чем проблема любые тесты проводить в ветке, в которую влит транк?
Вполне возможно, что у нас с вами просто сильно различаются проекты и процессы, поэтому наша методика не подходит вам, а ваша — нам. Конечно же, методика, описанная в этом топике не является единственной и самой лучшей, просто для наших проектов и процессов разработки она подходит наиболее хорошо, а поэтому, я думаю, подойдет и большинству команд, имеющих похожие на наши процессы. Не всем, конечно.
Вполне возможно. В аргументированном споре рождается истина и происходит обмен опытом :)
По моему опыту откладывание интеграции — зло. Откладывание интеграции БД, где нет средств, вроде svn/git, которые ее упрощают — зло в квадрате.
Конечно, песочницы, где можно потестировать большие изменения в БД нужны. Но чем их меньше тем лучше.
на первый взгяд тоже самое, что и dbdeploy.
К предпоследнем абзацу про именование дельт:
Что б названия файлов миграций (как в примере — 0004) не пересекались — можно приписывать после номера версии номер задачи к которой это измение принадлежит, например 0004-123-alter_user_table.sql. А еще можно к названию приписывать слова before/after что бы помнить — до обновления скриптов-сайта должен выполниться sql или после.
Sign up to leave a comment.

Articles