Add support for using aliases to allow hot swapping of indexes when populating

This commit is contained in:
Richard Miller 2014-02-06 21:11:12 +00:00
parent 82e9809ceb
commit 3b1a756e6f
7 changed files with 176 additions and 6 deletions

View file

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

View file

@ -124,6 +124,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

@ -134,6 +134,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)
@ -670,7 +671,7 @@ class Configuration implements ConfigurationInterface
return $node;
}
/**
* Returns the array node used for "_ttl"
*/
@ -689,5 +690,5 @@ class Configuration implements ConfigurationInterface
;
return $node;
}
}
}

View file

@ -124,6 +124,7 @@ class FOSElasticaExtension extends Extension
$indexIds[$name] = $indexId;
$this->indexConfigs[$name] = array(
'index' => new Reference($indexId),
'name_or_alias' => $indexName,
'config' => array(
'mappings' => array()
)
@ -134,6 +135,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\Type\Mapping;
/**
@ -26,8 +28,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);
}
}
@ -40,7 +42,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 .= date('-Y-m-d-Gis');
$esIndex->overrideName($name);
$esIndex->create($indexConfig['config']);
return;
}
$esIndex->create($indexConfig['config'], true);
}
/**
@ -102,4 +114,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 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>