Потокобезопасные миграции Doctrine 2 / Symfony 2

// Февраль 19th, 2013 // Doctrine 2, MySQL, NoSQL

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

 

Потокобезопасные миграции.

Коротко эту тему я уже затронул в одной из предыдущих статей. В кластере находятся несколько веб-серверов и один сервере базы данных (MySQL). При этом происходит одновременный деплой кода на веб-серверах, и необходимо выполненеи миграции один (и только один раз) на одном из веб-серверов. Т.е. два потока должны разделять один ресурс. Как это сделать?

migrations

Будем использовать сервис глобальных блокировок, разработанный в предыдущей статье.

Каскадное исполнение команд в 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?

Share

Спасибо!


Если вам помогла статья, или вы хотите поддержать мои исследования и блог - вот лучший способ сделать это:


4 Responses to “Потокобезопасные миграции Doctrine 2 / Symfony 2”

  1. Что-то я не понял, зачем вообще выполнять миграции на всех веб-серверах…

    • google.com Андрей Токарчук:

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

  2. max:

    Я тоже не понял постановку задачи. Какие потоки? Кто их пораждает? Предложения не согласованы и суть не ясна. Первый абзац просто выносит.

Комментировать