Skip to content

Commit 1b05a37

Browse files
authored
Merge pull request #1744 from algolia/fix/MAGE-1251-rebuild-replicas-3.16
MAGE-1251 Replica enhancements ported to 3.16
2 parents 28d72ce + 5aa05a7 commit 1b05a37

File tree

9 files changed

+582
-129
lines changed

9 files changed

+582
-129
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: 123 additions & 75 deletions
Large diffs are not rendered by default.

Setup/Patch/Data/RebuildReplicasPatch.php

Lines changed: 44 additions & 16 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,53 @@ 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-
}
69-
70-
$this->replicaManager->deleteReplicasFromAlgolia($storeId);
71-
}
65+
$storeIds = $this->getStoreIdsEligibleForPatch();
7266

73-
foreach ($storeIds as $storeId) {
74-
if (!$this->algoliaCredentialsManager->checkCredentialsWithSearchOnlyAPIKey($storeId)) {
75-
continue;
67+
try {
68+
// Delete all replicas before resyncing in case of incorrect replica assignments
69+
foreach ($storeIds as $storeId) {
70+
$this->retryDeleteReplica($storeId);
7671
}
77-
78-
$this->replicaState->setChangeState(ReplicaState::REPLICA_STATE_CHANGED, $storeId); // avoids latency
79-
$this->replicaManager->syncReplicasToAlgolia($storeId, $this->productHelper->getIndexSettings($storeId));
72+
foreach ($storeIds as $storeId) {
73+
$this->replicaState->setChangeState(ReplicaState::REPLICA_STATE_CHANGED, $storeId); // avoids latency
74+
$this->replicaManager->syncReplicasToAlgolia($storeId, $this->productHelper->getIndexSettings($storeId));
75+
}
76+
} catch (AlgoliaException $e) {
77+
// Log the error but do not prevent setup:update
78+
$this->logger->error("Could not rebuild replicas - a full reindex may be required.");
8079
}
8180

8281
$this->moduleDataSetup->getConnection()->endSetup();
8382

8483
return $this;
8584
}
85+
86+
protected function getStoreIdsEligibleForPatch(): array
87+
{
88+
return array_filter(
89+
array_keys($this->storeManager->getStores()),
90+
function (int $storeId) {
91+
if (!$this->replicaManager->isReplicaSyncEnabled($storeId)) return false;
92+
if (!$this->algoliaCredentialsManager->checkCredentials($storeId)) {
93+
$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`");
94+
return false;
95+
}
96+
return true;
97+
}
98+
);
99+
}
100+
101+
protected function retryDeleteReplica(int $storeId, int $maxRetries = 3, int $interval = 5)
102+
{
103+
for ($tries = $maxRetries - 1; $tries >= 0; $tries--) {
104+
try {
105+
$this->replicaManager->deleteReplicasFromAlgolia($storeId);
106+
return;
107+
} catch (AlgoliaException $e) {
108+
$this->logger->warning(__("Unable to delete replicas, %1 tries remaining: %2", $tries, $e->getMessage()));
109+
sleep($interval);
110+
}
111+
}
112+
throw new ExceededRetriesException('Unable to delete old replica indices after $maxRetries retries.');
113+
}
86114
}

Test/Integration/Indexing/Product/MultiStoreReplicaTest.php

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Algolia\AlgoliaSearch\Console\Command\ReplicaRebuildCommand;
77
use Algolia\AlgoliaSearch\Console\Command\ReplicaSyncCommand;
88
use Algolia\AlgoliaSearch\Helper\ConfigHelper;
9+
use Algolia\AlgoliaSearch\Service\Product\IndexOptionsBuilder;
910
use Algolia\AlgoliaSearch\Service\Product\SortingTransformer;
1011
use Algolia\AlgoliaSearch\Test\Integration\Indexing\Config\Traits\ConfigAssertionsTrait;
1112
use Algolia\AlgoliaSearch\Test\Integration\Indexing\MultiStoreTestCase;
@@ -32,11 +33,14 @@ class MultiStoreReplicaTest extends MultiStoreTestCase
3233

3334
protected ?SerializerInterface $serializer = null;
3435

36+
protected ?IndexOptionsBuilder $indexOptionsBuilder = null;
37+
3538
protected function setUp(): void
3639
{
3740
parent::setUp();
3841
$this->replicaManager = $this->objectManager->get(ReplicaManagerInterface::class);
3942
$this->serializer = $this->objectManager->get(SerializerInterface::class);
43+
$this->indexOptionsBuilder = $this->objectManager->get(IndexOptionsBuilder::class);
4044

4145
$stores = $this->storeManager->getStores();
4246

@@ -72,7 +76,7 @@ public function testCustomerGroupsConfig()
7276
// Enable customer groups for second fixture store and save configuration
7377
$this->setConfig( ConfigHelper::CUSTOMER_GROUPS_ENABLE, 1, $fixtureSecondStore->getCode());
7478
$this->indicesConfigurator->saveConfigurationToAlgolia($fixtureSecondStore->getId());
75-
$this->algoliaHelper->waitLastTask($fixtureSecondStore->getId());
79+
$this->algoliaConnector->waitLastTask($fixtureSecondStore->getId());
7680

7781
// 7 indices for default store and third store:
7882
// - 1 for categories
@@ -95,8 +99,8 @@ public function testReplicaCommands()
9599
$defaultStore = $this->storeRepository->get('default');
96100
$fixtureSecondStore = $this->storeRepository->get('fixture_second_store');
97101

98-
$defaultIndexName = $this->indexPrefix . $defaultStore->getCode() . '_products';
99-
$fixtureIndexName = $this->indexPrefix . $fixtureSecondStore->getCode() . '_products';
102+
$defaultIndexName = $this->indexNameFetcher->getProductIndexName($defaultStore->getId());
103+
$fixtureIndexName = $this->indexNameFetcher->getProductIndexName($fixtureSecondStore->getId());
100104

101105
// Update store config for fixture only
102106
$this->mockSortUpdate('price', 'desc', ['virtualReplica' => 1], $fixtureSecondStore);
@@ -119,8 +123,8 @@ public function testReplicaCommands()
119123
$syncCmd = $this->objectManager->get(ReplicaSyncCommand::class);
120124
$this->mockProperty($syncCmd, 'output', OutputInterface::class);
121125
$syncCmd->syncReplicas();
122-
$this->algoliaHelper->waitLastTask($defaultStore->getId());
123-
$this->algoliaHelper->waitLastTask($fixtureSecondStore->getId());
126+
$this->algoliaConnector->waitLastTask($defaultStore->getId());
127+
$this->algoliaConnector->waitLastTask($fixtureSecondStore->getId());
124128

125129
$rebuildCmd = $this->objectManager->get(ReplicaRebuildCommand::class);
126130
$this->invokeMethod(
@@ -131,12 +135,18 @@ public function testReplicaCommands()
131135
$this->createMock(OutputInterface::class)
132136
]
133137
);
134-
$this->algoliaHelper->waitLastTask($defaultStore->getId());
135-
$this->algoliaHelper->waitLastTask($fixtureSecondStore->getId());
138+
$this->algoliaConnector->waitLastTask($defaultStore->getId());
139+
$this->algoliaConnector->waitLastTask($fixtureSecondStore->getId());
136140
// Executing commands - End
137141

138142
$currentDefaultSettings = $this->algoliaHelper->getSettings($defaultIndexName, $defaultStore->getId());
143+
$currentDefaultSettings = $this->algoliaConnector->getSettings(
144+
$this->indexOptionsBuilder->buildEntityIndexOptions($defaultStore->getId())
145+
);
139146
$currentFixtureSettings = $this->algoliaHelper->getSettings($fixtureIndexName, $fixtureSecondStore->getId());
147+
$currentFixtureSettings = $this->algoliaConnector->getSettings(
148+
$this->indexOptionsBuilder->buildEntityIndexOptions($fixtureSecondStore->getId())
149+
);
140150

141151
$this->assertArrayHasKey('replicas', $currentDefaultSettings);
142152
$this->assertArrayHasKey('replicas', $currentFixtureSettings);
@@ -176,7 +186,9 @@ protected function checkReplicaConfigByStore(StoreInterface $store, $sortAttr, $
176186
{
177187
$indexName = $this->indexPrefix . $store->getCode() . '_products';
178188

179-
$settings = $this->algoliaHelper->getSettings($indexName, $store->getId());
189+
$settings = $this->algoliaConnector->getSettings(
190+
$this->indexOptionsBuilder->buildWithEnforcedIndex($indexName, $store->getId())
191+
);
180192

181193
$this->assertArrayHasKey('replicas', $settings);
182194

@@ -216,7 +228,7 @@ protected function addSortingByStore(StoreInterface $store, $attr, $dir, $isVir
216228

217229
$this->assertSortingAttribute($attr, $dir);
218230
$this->indicesConfigurator->saveConfigurationToAlgolia($store->getId());
219-
$this->algoliaHelper->waitLastTask($store->getId());
231+
$this->algoliaConnector->waitLastTask($store->getId());
220232
}
221233

222234
protected function resetAllSortings()

0 commit comments

Comments
 (0)