Merge pull request #463 from FriendsOfSymfony/add-hotswapping-aliased-indexes-on-populate

Add support for using aliases to allow hot swapping of indexes.
This commit is contained in:
Tim Nagel 2014-03-17 09:20:12 +11:00
commit 5292a2adad
7 changed files with 174 additions and 4 deletions

View file

@ -32,4 +32,9 @@ class Client extends ElasticaClient
return $response;
}
public function getIndex($name)
{
return new DynamicIndex($this, $name);
}
}

View file

@ -126,6 +126,7 @@ class PopulateCommand extends ContainerAwareCommand
}
$output->writeln(sprintf('<info>Refreshing</info> <comment>%s</comment>', $index));
$this->resetter->postPopulate($index);
$this->indexManager->getIndex($index)->refresh();
}

View file

@ -140,6 +140,7 @@ class Configuration implements ConfigurationInterface
->prototype('array')
->children()
->scalarNode('index_name')->end()
->booleanNode('use_alias')->defaultValue(false)->end()
->scalarNode('client')->end()
->scalarNode('finder')
->treatNullLike(true)

View file

@ -125,6 +125,7 @@ class FOSElasticaExtension extends Extension
$indexIds[$name] = $indexId;
$this->indexConfigs[$name] = array(
'index' => new Reference($indexId),
'name_or_alias' => $indexName,
'config' => array(
'mappings' => array()
)
@ -135,6 +136,10 @@ class FOSElasticaExtension extends Extension
if (!empty($index['settings'])) {
$this->indexConfigs[$name]['config']['settings'] = $index['settings'];
}
if ($index['use_alias']) {
$this->indexConfigs[$name]['use_alias'] = true;
}
$this->loadTypes(isset($index['types']) ? $index['types'] : array(), $container, $name, $indexId, $typePrototypeConfig);
}

28
DynamicIndex.php Normal file
View file

@ -0,0 +1,28 @@
<?php
namespace FOS\ElasticaBundle;
use Elastica\Index;
/**
* Elastica index capable of reassigning name dynamically
*
* @author Konstantin Tjuterev <kostik.lv@gmail.com>
*/
class DynamicIndex extends Index
{
/**
* Reassign index name
*
* While it's technically a regular setter for name property, it's specifically named overrideName, but not setName
* since it's used for a very specific case and normally should not be used
*
* @param string $name Index name
*
* @return void
*/
public function overrideName($name)
{
$this->_name = $name;
}
}

View file

@ -2,6 +2,8 @@
namespace FOS\ElasticaBundle;
use Elastica\Exception\ExceptionInterface;
use Elastica\Index;
use Elastica\Exception\ResponseException;
use Elastica\Type\Mapping;
@ -27,8 +29,8 @@ class Resetter
*/
public function resetAllIndexes()
{
foreach ($this->indexConfigsByName as $indexConfig) {
$indexConfig['index']->create($indexConfig['config'], true);
foreach (array_keys($this->indexConfigsByName) as $name) {
$this->resetIndex($name);
}
}
@ -41,7 +43,17 @@ class Resetter
public function resetIndex($indexName)
{
$indexConfig = $this->getIndexConfig($indexName);
$indexConfig['index']->create($indexConfig['config'], true);
$esIndex = $indexConfig['index'];
if (isset($indexConfig['use_alias']) && $indexConfig['use_alias']) {
$name = $indexConfig['name_or_alias'];
$name .= uniqid();
$esIndex->overrideName($name);
$esIndex->create($indexConfig['config']);
return;
}
$esIndex->create($indexConfig['config'], true);
}
/**
@ -112,4 +124,122 @@ class Resetter
return $this->indexConfigsByName[$indexName];
}
public function postPopulate($indexName)
{
$indexConfig = $this->getIndexConfig($indexName);
if (isset($indexConfig['use_alias']) && $indexConfig['use_alias']) {
$this->switchIndexAlias($indexName);
}
}
/**
* Switches the alias for given index (by key) to the newly populated index
* and deletes the old index
*
* @param string $indexName Index name
*
* @throws \RuntimeException
*/
private function switchIndexAlias($indexName)
{
$indexConfig = $this->getIndexConfig($indexName);
$esIndex = $indexConfig['index'];
$aliasName = $indexConfig['name_or_alias'];
$oldIndexName = false;
$newIndexName = $esIndex->getName();
$aliasedIndexes = $this->getAliasedIndexes($esIndex, $aliasName);
if (count($aliasedIndexes) > 1) {
throw new \RuntimeException(
sprintf(
'Alias %s is used for multiple indexes: [%s].
Make sure it\'s either not used or is assigned to one index only',
$aliasName,
join(', ', $aliasedIndexes)
)
);
}
// Change the alias to point to the new index
// Elastica's addAlias can't be used directly, because in current (0.19.x) version it's not atomic
// In 0.20.x it's atomic, but it doesn't return the old index name
$aliasUpdateRequest = array('actions' => array());
if (count($aliasedIndexes) == 1) {
// if the alias is set - add an action to remove it
$oldIndexName = $aliasedIndexes[0];
$aliasUpdateRequest['actions'][] = array(
'remove' => array('index' => $oldIndexName, 'alias' => $aliasName)
);
}
// add an action to point the alias to the new index
$aliasUpdateRequest['actions'][] = array(
'add' => array('index' => $newIndexName, 'alias' => $aliasName)
);
try {
$esIndex->getClient()->request('_aliases', 'POST', $aliasUpdateRequest);
} catch (ExceptionInterface $renameAliasException) {
$additionalError = '';
// if we failed to move the alias, delete the newly built index
try {
$esIndex->delete();
} catch (ExceptionInterface $deleteNewIndexException) {
$additionalError = sprintf(
'Tried to delete newly built index %s, but also failed: %s',
$newIndexName,
$deleteNewIndexException->getError()
);
}
throw new \RuntimeException(
sprintf(
'Failed to updated index alias: %s. %s',
$renameAliasException->getMessage(),
$additionalError ?: sprintf('Newly built index %s was deleted', $newIndexName)
)
);
}
// Delete the old index after the alias has been switched
if ($oldIndexName) {
$oldIndex = new Index($esIndex->getClient(), $oldIndexName);
try {
$oldIndex->delete();
} catch (ExceptionInterface $deleteOldIndexException) {
throw new \RuntimeException(
sprintf(
'Failed to delete old index %s with message: %s',
$oldIndexName,
$deleteOldIndexException->getMessage()
)
);
}
}
}
/**
* Returns array of indexes which are mapped to given alias
*
* @param Index $esIndex ES Index
* @param string $aliasName Alias name
*
* @return array
*/
private function getAliasedIndexes(Index $esIndex, $aliasName)
{
$aliasesInfo = $esIndex->getClient()->request('_aliases', 'GET')->getData();
$aliasedIndexes = array();
foreach ($aliasesInfo as $indexName => $indexInfo) {
$aliases = array_keys($indexInfo['aliases']);
if (in_array($aliasName, $aliases)) {
$aliasedIndexes[] = $indexName;
}
}
return $aliasedIndexes;
}
}

View file

@ -6,7 +6,7 @@
<parameters>
<parameter key="fos_elastica.client.class">FOS\ElasticaBundle\Client</parameter>
<parameter key="fos_elastica.index.class">Elastica\Index</parameter>
<parameter key="fos_elastica.index.class">FOS\ElasticaBundle\DynamicIndex</parameter>
<parameter key="fos_elastica.type.class">Elastica\Type</parameter>
<parameter key="fos_elastica.logger.class">FOS\ElasticaBundle\Logger\ElasticaLogger</parameter>
<parameter key="fos_elastica.data_collector.class">FOS\ElasticaBundle\DataCollector\ElasticaDataCollector</parameter>