Skip to content

Commit a98ce67

Browse files
committed
MAGE-1251 Initial merge of replica enhancements introduced on 3.14.5
2 parents 06386c0 + 01c5985 commit a98ce67

File tree

8 files changed

+464
-45
lines changed

8 files changed

+464
-45
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: 71 additions & 8 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

@@ -272,7 +273,7 @@ protected function setReplicasOnPrimaryIndex(int $storeId): array
272273
$setReplicasTaskId = $this->algoliaHelper->getLastTaskId($storeId);
273274
$this->algoliaHelper->waitLastTask($storeId, $indexName, $setReplicasTaskId);
274275
$this->clearAlgoliaReplicaSettingCache($indexName);
275-
$this->deleteIndices($replicasToDelete, false, $storeId);
276+
$this->deleteReplicas($replicasToDelete, false, false, $storeId);
276277

277278
if (self::_DEBUG) {
278279
$this->logger->log(
@@ -366,23 +367,85 @@ protected function getBareIndexNameFromReplicaSetting(string $replicaSetting): s
366367
}
367368

368369
/**
369-
* @param array $replicasToDelete
370-
* @param bool $waitLastTask
371-
* @param null $storeId
370+
* Delete replica indices
371+
*
372+
* @param array $replicasToDelete - which replicas to delete
373+
* @param bool $waitLastTask - wait until deleting next replica (default: false)
374+
* @param bool $prevalidate - verify replica is not attached to a primary index before attempting to delete (default: false)
372375
* @return void
373376
* @throws AlgoliaException
374377
* @throws ExceededRetriesException
375378
*/
376-
protected function deleteIndices(array $replicasToDelete, bool $waitLastTask = false, $storeId = null): void
379+
protected function deleteReplicas(
380+
array $replicasToDelete,
381+
bool $waitLastTask = false,
382+
bool $prevalidate = false,
383+
?int $storeId = null): void
377384
{
378385
foreach ($replicasToDelete as $deletedReplica) {
379-
$this->algoliaHelper->deleteIndex($deletedReplica, $storeId);
386+
$this->deleteReplica($deletedReplica, $prevalidate);
380387
if ($waitLastTask) {
381388
$this->algoliaHelper->waitLastTask($storeId, $deletedReplica);
382389
}
383390
}
384391
}
385392

393+
/**
394+
* @throws AlgoliaException
395+
* @throws ExceededRetriesException
396+
*/
397+
protected function deleteReplica(string $replicaIndexName, bool $precheck = false, ?int $storeId = null): void
398+
{
399+
if ($precheck) {
400+
$settings = $this->algoliaHelper->getSettings($replicaIndexName);
401+
if (isset($settings[self::ALGOLIA_SETTINGS_KEY_PRIMARY])) {
402+
$this->detachReplica($settings[self::ALGOLIA_SETTINGS_KEY_PRIMARY], $replicaIndexName, $storeId);
403+
}
404+
}
405+
406+
$this->algoliaHelper->deleteIndex($replicaIndexName, $storeId);
407+
}
408+
409+
/**
410+
* Detach a single replica from its primary index
411+
*
412+
* @throws ExceededRetriesException
413+
* @throws AlgoliaException
414+
*/
415+
protected function detachReplica(string $primaryIndexName, string $replicaIndexName, ?int $storeId = null): void
416+
{
417+
$settings = $this->algoliaHelper->getSettings($primaryIndexName);
418+
if (!isset($settings[self::ALGOLIA_SETTINGS_KEY_REPLICAS])) {
419+
return;
420+
}
421+
$newReplicas = $this->removeReplicaFromReplicaSetting($settings[self::ALGOLIA_SETTINGS_KEY_REPLICAS], $replicaIndexName);
422+
$this->algoliaHelper->setSettings(
423+
$primaryIndexName,
424+
[ self::ALGOLIA_SETTINGS_KEY_REPLICAS => $newReplicas],
425+
false,
426+
false,
427+
'',
428+
$storeId
429+
);
430+
$this->algoliaHelper->waitLastTask($storeId, $primaryIndexName);
431+
}
432+
433+
/**
434+
* Remove a single replica from the replica setting array
435+
* (Can feature virtual or standard)
436+
*/
437+
protected function removeReplicaFromReplicaSetting(array $replicaSetting, string $replicaToRemove): array
438+
{
439+
return array_filter(
440+
$replicaSetting,
441+
function ($replicaIndexSetting) use ($replicaToRemove) {
442+
$escaped = preg_quote($replicaToRemove);
443+
$regex = '/^' . $escaped . '|virtual\(' . $escaped . '\)$/';
444+
return !preg_match($regex, $replicaToRemove);
445+
}
446+
);
447+
}
448+
386449
/**
387450
* Apply ranking settings to the added replica indices
388451
* @param int $storeId
@@ -438,7 +501,7 @@ function($replica) use ($replicas) {
438501
*/
439502
public function isReplicaSyncEnabled(int $storeId): bool
440503
{
441-
return $this->configHelper->isInstantEnabled($storeId);
504+
return $this->configHelper->isInstantEnabled($storeId) && $this->configHelper->isEnabledBackend($storeId);
442505
}
443506

444507
/**
@@ -479,7 +542,7 @@ public function deleteReplicasFromAlgolia(int $storeId, bool $unused = false): v
479542
$this->clearReplicasSettingInAlgolia($primaryIndexName, $storeId);
480543
}
481544

482-
$this->deleteIndices($replicasToDelete);
545+
$this->deleteReplicas($replicasToDelete, true, true, $storeId);
483546

484547
if ($unused) {
485548
$this->clearUnusedReplicaIndicesCache($storeId);

Setup/Patch/Data/RebuildReplicasPatch.php

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

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

5+
use Algolia\AlgoliaSearch\Exceptions\AlgoliaException;
6+
use Algolia\AlgoliaSearch\Exceptions\ExceededRetriesException;
7+
use Algolia\AlgoliaSearch\Helper\ConfigHelper;
58
use Algolia\AlgoliaSearch\Helper\Entity\ProductHelper;
69
use Algolia\AlgoliaSearch\Registry\ReplicaState;
710
use Algolia\AlgoliaSearch\Service\AlgoliaCredentialsManager;
@@ -59,28 +62,49 @@ public function apply(): PatchInterface
5962
// Area code is already set - nothing to do
6063
}
6164

62-
$storeIds = array_keys($this->storeManager->getStores());
63-
// Delete all replicas before resyncing in case of incorrect replica assignments
64-
foreach ($storeIds as $storeId) {
65-
if (!$this->algoliaCredentialsManager->checkCredentialsWithSearchOnlyAPIKey($storeId)) {
66-
$this->logger->warning("Algolia credentials are not configured for store $storeId. Skipping auto replica rebuild for this store. If you need to rebuild your replicas run `bin/magento algolia:replicas:rebuild`");
67-
continue;
68-
}
65+
$storeIds = array_filter(
66+
array_keys($this->storeManager->getStores()),
67+
function (int $storeId) { return $this->replicaManager->isReplicaSyncEnabled($storeId); }
68+
);
6969

70-
$this->replicaManager->deleteReplicasFromAlgolia($storeId);
71-
}
70+
try {
71+
// Delete all replicas before resyncing in case of incorrect replica assignments
72+
foreach ($storeIds as $storeId) {
73+
if (!$this->algoliaCredentialsManager->checkCredentialsWithSearchOnlyAPIKey($storeId)) {
74+
$this->logger->warning("Algolia credentials are not configured for store $storeId. Skipping auto replica rebuild for this store. If you need to rebuild your replicas run `bin/magento algolia:replicas:rebuild`");
75+
continue;
76+
}
7277

73-
foreach ($storeIds as $storeId) {
74-
if (!$this->algoliaCredentialsManager->checkCredentialsWithSearchOnlyAPIKey($storeId)) {
75-
continue;
78+
$this->retryDeleteReplica($storeId);
7679
}
77-
78-
$this->replicaState->setChangeState(ReplicaState::REPLICA_STATE_CHANGED, $storeId); // avoids latency
79-
$this->replicaManager->syncReplicasToAlgolia($storeId, $this->productHelper->getIndexSettings($storeId));
80+
foreach ($storeIds as $storeId) {
81+
if (!$this->algoliaCredentialsManager->checkCredentialsWithSearchOnlyAPIKey($storeId)) {
82+
continue;
83+
}
84+
$this->replicaState->setChangeState(ReplicaState::REPLICA_STATE_CHANGED, $storeId); // avoids latency
85+
$this->replicaManager->syncReplicasToAlgolia($storeId, $this->productHelper->getIndexSettings($storeId));
86+
}
87+
} catch (AlgoliaException $e) {
88+
// Log the error but do not prevent setup:update
89+
$this->logger->error("Could not rebuild replicas - a full reindex may be required.");
8090
}
8191

8292
$this->moduleDataSetup->getConnection()->endSetup();
8393

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

0 commit comments

Comments
 (0)