Потокобезопасные миграции Doctrine 2 / Symfony 2
// 19 февраля, 2013 // Doctrine 2, MySQL, NoSQL
В этой статье я раскрою сразу две темы. Первая тема — это потокобезопасные миграции в Doctrine 2, а второй будет каскадное исполнение команд в консоле Symfony 2. На самом деле, ко всему надо иметь прагматический подход. Вот например, касаясь той же симфонии, есть там кое-что, что мне определенно нарвится. В частности — это замечательная симфоневская консоль.
Потокобезопасные миграции.
Коротко эту тему я уже затронул в одной из предыдущих статей. В кластере находятся несколько веб-серверов и один сервере базы данных (MySQL). При этом происходит одновременный деплой кода на веб-серверах, и необходимо выполненеи миграции один (и только один раз) на одном из веб-серверов. Т.е. два потока должны разделять один ресурс. Как это сделать?
Будем использовать сервис глобальных блокировок, разработанный в предыдущей статье.
Каскадное исполнение команд в Symfony
У нас в app/console есть волжебная команда doctrine:migrations:migrate Нам необходимо обернуть её в обертку, которая будет смотреть а можно ли собственно накатывать мигарцию. Если ресурс свободен (нет другого потока) — то накатываем миграцию, а если ресурс занят, и нам не удаётся поулчить блокировку, тогда ничего не делаем. Ведь сейчас другой процесс как раз вовсю выполняет миграцию 🙂
Вот код волшебной оберточки.
My\DefaultBundle\Command\SafeMigrateCommand.php
<?php namespace My\DefaultBundle\Command; use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * Потокобезопасное выполнение миграций в кластере */ class SafeMigrateCommand extends ContainerAwareCommand { protected function configure() { $this ->setName('my:migrations:safeMigrate') ->setDescription('Multi threading ready doctrine migrations'); } protected function execute(InputInterface $input, OutputInterface $output) { /** @var $dm \Doctrine\ODM\MongoDB\DocumentManager */ $dm = $this->getContainer()->get('doctrine_mongodb.odm.default_document_manager'); $repo = $dm->getRepository('My\DefaultBundle\Document\Mutex'); // Потокобезопасный запуск миграций. // Только один поток на всех серверах кластера может запускать этот кусок кода). if($repo->isFreeLock('migrations') && $repo->getLock('migrations', 3600)) { // Убеждаемся, что все индексы монги есть (важно для TTL индекса на коллекции mutexes) $command = $this->getApplication()->find('my:ensure:indexes'); $returnCode = $command->run($input, $output); $arguments = array( 'command' => '', // без этого ключа пишет ошибку "No enought argument". '--no-interaction' => true, ); $input = new ArrayInput($arguments); $command = $this->getApplication()->find('doctrine:migrations:migrate'); $returnCode = $command->run($input, $output); $repo->releaseLock('migrations'); } else { $output->writeln('Another migration process detected. Exiting.'); } } }
Заключение
Собственно, вот и всё. Как видите создать команду-обертку для другой команды в Symfony 2 проще простого. Зато теперь можно не беспокоиться за целостность нашей базы данных.
P.S.
В замечательном средстве деплоя Capistrano для выполнения аналогичной операции предусмотрены специальные роли серверов. В частности, если мы имеем несколько серверов БД, то одного из них надо в конфиге назначить мастером вот так:
:primary => true
Тогда миграции будут выполняться только на нём.
Ссылки
The Console Component
Сapistrano, :db role, what’s it for?
Спасибо!
Если вам помогла статья, или вы хотите поддержать мои исследования и блог - вот лучший способ сделать это:
Что-то я не понял, зачем вообще выполнять миграции на всех веб-серверах…
Вот как раз на всех и не нужно, но выполняется. Это особенность TeamCity. Если скажете, как можно указать, что определённую цель(target) выполнять на определённом build-agent буду премного благодарен.
Я тоже не понял постановку задачи. Какие потоки? Кто их пораждает? Предложения не согласованы и суть не ясна. Первый абзац просто выносит.
Имеется в виду вот этот бандл: http://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html
Потоки порождает скрипт деплоя запущенный одновременно на нескольких серверах. Про первый абзац — это анонс имеется в виду. Там краткое описание двух тем. Не понимаю, что именно вынесло мозг.