Skip to content

Commit 2a7cefe

Browse files
authored
Merge pull request #1718 from algolia/fix/MAGE-1218-rebuild-replicas
MAGE-1218 Replica management enhancements - data patch fix / tests
2 parents 2f766a3 + 01c5985 commit 2a7cefe

File tree

8 files changed

+453
-37
lines changed

8 files changed

+453
-37
lines changed

Api/Product/ReplicaManagerInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function syncReplicasToAlgolia(int $storeId, array $primaryIndexSettings)
3131
/**
3232
* Delete the replica indices on a store index
3333
* @param int $storeId
34-
* @param bool $unused Defaults to false - if true identifies any straggler indices and deletes those, otherwise deletes the replicas it knows aobut
34+
* @param bool $unused Defaults to false - if true identifies any straggler indices and deletes those, otherwise deletes the replicas it knows about
3535
* @return void
3636
*
3737
* @throws LocalizedException

Service/Product/ReplicaManager.php

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
class ReplicaManager implements ReplicaManagerInterface
4242
{
4343
public const ALGOLIA_SETTINGS_KEY_REPLICAS = 'replicas';
44+
public const ALGOLIA_SETTINGS_KEY_PRIMARY = 'primary';
4445

4546
protected const _DEBUG = true;
4647

@@ -260,7 +261,7 @@ protected function setReplicasOnPrimaryIndex(int $storeId): array
260261
$setReplicasTaskId = $this->algoliaHelper->getLastTaskId();
261262
$this->algoliaHelper->waitLastTask($indexName, $setReplicasTaskId);
262263
$this->clearAlgoliaReplicaSettingCache($indexName);
263-
$this->deleteIndices($replicasToDelete);
264+
$this->deleteReplicas($replicasToDelete);
264265

265266
if (self::_DEBUG) {
266267
$this->logger->log(
@@ -354,21 +355,74 @@ protected function getBareIndexNameFromReplicaSetting(string $replicaSetting): s
354355
}
355356

356357
/**
357-
* @param array $replicasToDelete
358-
* @param bool $waitLastTask
358+
* Delete replica indices
359+
*
360+
* @param array $replicasToDelete - which replicas to delete
361+
* @param bool $waitLastTask - wait until deleting next replica (default: false)
362+
* @param bool $prevalidate - verify replica is not attached to a primary index before attempting to delete (default: false)
359363
* @return void
360364
* @throws AlgoliaException
365+
* @throws ExceededRetriesException
361366
*/
362-
protected function deleteIndices(array $replicasToDelete, bool $waitLastTask = false): void
367+
protected function deleteReplicas(array $replicasToDelete, bool $waitLastTask = false, bool $prevalidate = false): void
363368
{
364369
foreach ($replicasToDelete as $deletedReplica) {
365-
$this->algoliaHelper->deleteIndex($deletedReplica);
370+
$this->deleteReplica($deletedReplica, $prevalidate);
366371
if ($waitLastTask) {
367372
$this->algoliaHelper->waitLastTask($deletedReplica);
368373
}
369374
}
370375
}
371376

377+
/**
378+
* @throws AlgoliaException
379+
* @throws ExceededRetriesException
380+
*/
381+
protected function deleteReplica(string $replicaIndexName, bool $precheck = false): void
382+
{
383+
if ($precheck) {
384+
$settings = $this->algoliaHelper->getSettings($replicaIndexName);
385+
if (isset($settings[self::ALGOLIA_SETTINGS_KEY_PRIMARY])) {
386+
$this->detachReplica($settings[self::ALGOLIA_SETTINGS_KEY_PRIMARY], $replicaIndexName);
387+
}
388+
}
389+
390+
$this->algoliaHelper->deleteIndex($replicaIndexName);
391+
}
392+
393+
/**
394+
* Detach a single replica from its primary index
395+
*
396+
* @throws ExceededRetriesException
397+
* @throws AlgoliaException
398+
*/
399+
protected function detachReplica(string $primaryIndexName, string $replicaIndexName): void
400+
{
401+
$settings = $this->algoliaHelper->getSettings($primaryIndexName);
402+
if (!isset($settings[self::ALGOLIA_SETTINGS_KEY_REPLICAS])) {
403+
return;
404+
}
405+
$newReplicas = $this->removeReplicaFromReplicaSetting($settings[self::ALGOLIA_SETTINGS_KEY_REPLICAS], $replicaIndexName);
406+
$this->algoliaHelper->setSettings($primaryIndexName, [ self::ALGOLIA_SETTINGS_KEY_REPLICAS => $newReplicas]);
407+
$this->algoliaHelper->waitLastTask($primaryIndexName);
408+
}
409+
410+
/**
411+
* Remove a single replica from the replica setting array
412+
* (Can feature virtual or standard)
413+
*/
414+
protected function removeReplicaFromReplicaSetting(array $replicaSetting, string $replicaToRemove): array
415+
{
416+
return array_filter(
417+
$replicaSetting,
418+
function ($replicaIndexSetting) use ($replicaToRemove) {
419+
$escaped = preg_quote($replicaToRemove);
420+
$regex = '/^' . $escaped . '|virtual\(' . $escaped . '\)$/';
421+
return !preg_match($regex, $replicaToRemove);
422+
}
423+
);
424+
}
425+
372426
/**
373427
* Apply ranking settings to the added replica indices
374428
* @param int $storeId
@@ -416,7 +470,7 @@ function($replica) use ($replicas) {
416470
*/
417471
public function isReplicaSyncEnabled(int $storeId): bool
418472
{
419-
return $this->configHelper->isInstantEnabled($storeId);
473+
return $this->configHelper->isInstantEnabled($storeId) && $this->configHelper->isEnabledBackend($storeId);
420474
}
421475

422476
/**
@@ -449,7 +503,7 @@ public function deleteReplicasFromAlgolia(int $storeId, bool $unused = false): v
449503
$this->clearReplicasSettingInAlgolia($primaryIndexName);
450504
}
451505

452-
$this->deleteIndices($replicasToDelete);
506+
$this->deleteReplicas($replicasToDelete, true, true);
453507

454508
if ($unused) {
455509
$this->clearUnusedReplicaIndicesCache($storeId);

Setup/Patch/Data/RebuildReplicasPatch.php

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22

33
namespace Algolia\AlgoliaSearch\Setup\Patch\Data;
44

5+
use Algolia\AlgoliaSearch\Exceptions\AlgoliaException;
6+
use Algolia\AlgoliaSearch\Exceptions\ExceededRetriesException;
57
use Algolia\AlgoliaSearch\Helper\ConfigHelper;
68
use Algolia\AlgoliaSearch\Helper\Entity\ProductHelper;
79
use Algolia\AlgoliaSearch\Registry\ReplicaState;
810
use Algolia\AlgoliaSearch\Service\Product\ReplicaManager;
911
use Magento\Framework\App\Area;
1012
use Magento\Framework\App\State as AppState;
1113
use Magento\Framework\Exception\LocalizedException;
14+
use Magento\Framework\Exception\NoSuchEntityException;
1215
use Magento\Framework\Setup\ModuleDataSetupInterface;
1316
use Magento\Framework\Setup\Patch\DataPatchInterface;
1417
use Magento\Framework\Setup\Patch\PatchInterface;
@@ -64,19 +67,43 @@ public function apply(): PatchInterface
6467
// Area code is already set - nothing to do
6568
}
6669

67-
$storeIds = array_keys($this->storeManager->getStores());
68-
// Delete all replicas before resyncing in case of incorrect replica assignments
69-
foreach ($storeIds as $storeId) {
70-
$this->replicaManager->deleteReplicasFromAlgolia($storeId);
71-
}
70+
$storeIds = array_filter(
71+
array_keys($this->storeManager->getStores()),
72+
function (int $storeId) { return $this->replicaManager->isReplicaSyncEnabled($storeId); }
73+
);
74+
75+
try {
76+
// Delete all replicas before resyncing in case of incorrect replica assignments
77+
foreach ($storeIds as $storeId) {
78+
$this->retryDeleteReplica($storeId);
79+
}
7280

73-
foreach ($storeIds as $storeId) {
74-
$this->replicaState->setChangeState(ReplicaState::REPLICA_STATE_CHANGED, $storeId); // avoids latency
75-
$this->replicaManager->syncReplicasToAlgolia($storeId, $this->productHelper->getIndexSettings($storeId));
81+
foreach ($storeIds as $storeId) {
82+
$this->replicaState->setChangeState(ReplicaState::REPLICA_STATE_CHANGED, $storeId); // avoids latency
83+
$this->replicaManager->syncReplicasToAlgolia($storeId, $this->productHelper->getIndexSettings($storeId));
84+
}
85+
}
86+
catch (AlgoliaException $e) {
87+
// Log the error but do not prevent setup:update
88+
$this->logger->error("Could not rebuild replicas - a full reindex may be required.");
7689
}
7790

7891
$this->moduleDataSetup->getConnection()->endSetup();
7992

8093
return $this;
8194
}
95+
96+
protected function retryDeleteReplica(int $storeId, int $maxRetries = 3, int $interval = 5)
97+
{
98+
for ($tries = $maxRetries - 1; $tries >= 0; $tries--) {
99+
try {
100+
$this->replicaManager->deleteReplicasFromAlgolia($storeId);
101+
return;
102+
} catch (AlgoliaException $e) {
103+
$this->logger->warning(__("Unable to delete replicas, %1 tries remaining: %2", $tries, $e->getMessage()));
104+
sleep($interval);
105+
}
106+
}
107+
throw new ExceededRetriesException('Unable to delete old replica indices after $maxRetries retries.');
108+
}
82109
}

0 commit comments

Comments
 (0)