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:
commit
5292a2adad
|
@ -32,4 +32,9 @@ class Client extends ElasticaClient
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getIndex($name)
|
||||||
|
{
|
||||||
|
return new DynamicIndex($this, $name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,6 +126,7 @@ class PopulateCommand extends ContainerAwareCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
$output->writeln(sprintf('<info>Refreshing</info> <comment>%s</comment>', $index));
|
$output->writeln(sprintf('<info>Refreshing</info> <comment>%s</comment>', $index));
|
||||||
|
$this->resetter->postPopulate($index);
|
||||||
$this->indexManager->getIndex($index)->refresh();
|
$this->indexManager->getIndex($index)->refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -140,6 +140,7 @@ class Configuration implements ConfigurationInterface
|
||||||
->prototype('array')
|
->prototype('array')
|
||||||
->children()
|
->children()
|
||||||
->scalarNode('index_name')->end()
|
->scalarNode('index_name')->end()
|
||||||
|
->booleanNode('use_alias')->defaultValue(false)->end()
|
||||||
->scalarNode('client')->end()
|
->scalarNode('client')->end()
|
||||||
->scalarNode('finder')
|
->scalarNode('finder')
|
||||||
->treatNullLike(true)
|
->treatNullLike(true)
|
||||||
|
|
|
@ -125,6 +125,7 @@ class FOSElasticaExtension extends Extension
|
||||||
$indexIds[$name] = $indexId;
|
$indexIds[$name] = $indexId;
|
||||||
$this->indexConfigs[$name] = array(
|
$this->indexConfigs[$name] = array(
|
||||||
'index' => new Reference($indexId),
|
'index' => new Reference($indexId),
|
||||||
|
'name_or_alias' => $indexName,
|
||||||
'config' => array(
|
'config' => array(
|
||||||
'mappings' => array()
|
'mappings' => array()
|
||||||
)
|
)
|
||||||
|
@ -135,6 +136,10 @@ class FOSElasticaExtension extends Extension
|
||||||
if (!empty($index['settings'])) {
|
if (!empty($index['settings'])) {
|
||||||
$this->indexConfigs[$name]['config']['settings'] = $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);
|
$this->loadTypes(isset($index['types']) ? $index['types'] : array(), $container, $name, $indexId, $typePrototypeConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
28
DynamicIndex.php
Normal file
28
DynamicIndex.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
136
Resetter.php
136
Resetter.php
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
namespace FOS\ElasticaBundle;
|
namespace FOS\ElasticaBundle;
|
||||||
|
|
||||||
|
use Elastica\Exception\ExceptionInterface;
|
||||||
|
use Elastica\Index;
|
||||||
use Elastica\Exception\ResponseException;
|
use Elastica\Exception\ResponseException;
|
||||||
use Elastica\Type\Mapping;
|
use Elastica\Type\Mapping;
|
||||||
|
|
||||||
|
@ -27,8 +29,8 @@ class Resetter
|
||||||
*/
|
*/
|
||||||
public function resetAllIndexes()
|
public function resetAllIndexes()
|
||||||
{
|
{
|
||||||
foreach ($this->indexConfigsByName as $indexConfig) {
|
foreach (array_keys($this->indexConfigsByName) as $name) {
|
||||||
$indexConfig['index']->create($indexConfig['config'], true);
|
$this->resetIndex($name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +43,17 @@ class Resetter
|
||||||
public function resetIndex($indexName)
|
public function resetIndex($indexName)
|
||||||
{
|
{
|
||||||
$indexConfig = $this->getIndexConfig($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];
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
<parameters>
|
<parameters>
|
||||||
<parameter key="fos_elastica.client.class">FOS\ElasticaBundle\Client</parameter>
|
<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.type.class">Elastica\Type</parameter>
|
||||||
<parameter key="fos_elastica.logger.class">FOS\ElasticaBundle\Logger\ElasticaLogger</parameter>
|
<parameter key="fos_elastica.logger.class">FOS\ElasticaBundle\Logger\ElasticaLogger</parameter>
|
||||||
<parameter key="fos_elastica.data_collector.class">FOS\ElasticaBundle\DataCollector\ElasticaDataCollector</parameter>
|
<parameter key="fos_elastica.data_collector.class">FOS\ElasticaBundle\DataCollector\ElasticaDataCollector</parameter>
|
||||||
|
|
Loading…
Reference in a new issue