Потокобезопасные миграции Doctrine 2 / Symfony 2
// Февраль 19th, 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?
Related posts
- Оживляем Doctrine 2 Document/Entity при получении из кэша
- Глобальные блокировки на MongoDB
- Тормозят inserts в MongoDB (Doctrine 2 ODM)
- Пару слов про Doctrine 2 Identity Map
- Symfony 2.1 Session Handler MongoDB MongoTimestamp Bug
- Как убрать deprecated warnings в Symfony 2?
Смотрите также:
Спасибо!
Если вам помогла статья, или вы хотите поддержать мои исследования и блог - вот лучший способ сделать это:









Что-то я не понял, зачем вообще выполнять миграции на всех веб-серверах…
Вот как раз на всех и не нужно, но выполняется. Это особенность TeamCity. Если скажете, как можно указать, что определённую цель(target) выполнять на определённом build-agent буду премного благодарен.