Потокобезопасные миграции Doctrine 2 / Symfony 2
В этой статье я раскрою сразу две темы. Первая тема – это потокобезопасные миграции в Doctrine 2, а второй будет каскадное исполнение команд в консоле Symfony 2. На самом деле, ко всему надо иметь прагматический подход. Вот например, касаясь той же симфонии, есть там кое-что, что мне определенно нарвится. В частности – это замечательная симфоневская консоль.
Потокобезопасные миграции.
Коротко эту тему я уже затронул в одной из предыдущих статей. В кластере находятся несколько веб-серверов и один сервере базы данных (MySQL). При этом происходит одновременный деплой кода на веб-серверах, и необходимо выполненеи миграции один (и только один раз) на одном из веб-серверов. Т.е. два потока должны разделять один ресурс. Как это сделать?
Будем использовать сервис глобальных блокировок, разработанный в предыдущей статье.
Каскадное исполнение команд в Symfony
У нас в app/console есть волжебная команда doctrine:migrations:migrate Нам необходимо обернуть её в обертку, которая будет смотреть а можно ли собственно накатывать мигарцию. Если ресурс свободен (нет другого потока) – то накатываем миграцию, а если ресурс занят, и нам не удаётся поулчить блокировку, тогда ничего не делаем. Ведь сейчас другой процесс как раз вовсю выполняет миграцию 🙂
Вот код волшебной оберточки.
My\DefaultBundle\Command\SafeMigrateCommand.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
<?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 для выполнения аналогичной операции предусмотрены специальные роли серверов. В частности, если мы имеем несколько серверов БД, то одного из них надо в конфиге назначить мастером вот так:
1 |
:primary => true |
Тогда миграции будут выполняться только на нём.
Что-то я не понял, зачем вообще выполнять миграции на всех веб-серверах…
Вот как раз на всех и не нужно, но выполняется. Это особенность TeamCity. Если скажете, как можно указать, что определённую цель(target) выполнять на определённом build-agent буду премного благодарен.
Я тоже не понял постановку задачи. Какие потоки? Кто их пораждает? Предложения не согласованы и суть не ясна. Первый абзац просто выносит.
Имеется в виду вот этот бандл: http://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html
Потоки порождает скрипт деплоя запущенный одновременно на нескольких серверах. Про первый абзац – это анонс имеется в виду. Там краткое описание двух тем. Не понимаю, что именно вынесло мозг.