Skip to content

Commit 5ccd3e5

Browse files
committed
MAGE-938 Add unused replica clean up option
1 parent 2f5faf6 commit 5ccd3e5

File tree

4 files changed

+82
-56
lines changed

4 files changed

+82
-56
lines changed

Api/Product/ReplicaManagerInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ interface ReplicaManagerInterface
2929
*/
3030
public function syncReplicasToAlgolia(int $storeId, array $primaryIndexSettings): void;
3131

32-
public function deleteReplicasFromAlgolia(int $storeId, bool $unusedOnly = false): void;
32+
public function deleteReplicasFromAlgolia(int $storeId, bool $unused = false): void;
3333

3434
/**
3535
* For standard Magento front end (e.g. Luma) replicas will likely only be needed if InstantSearch is enabled

Console/Command/ReplicaDeleteCommand.php

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ protected function configure(): void
6161
protected function execute(InputInterface $input, OutputInterface $output): int
6262
{
6363
$storeIds = (array) $input->getArgument(self::STORE_ARGUMENT);
64+
$unused = $input->getOption(self::UNUSED_OPTION);
6465

65-
$msg = 'Deleting replicas for ' . ($storeIds ? count($storeIds) : 'all') . ' store' . (!$storeIds || count($storeIds) > 1 ? 's' : '');
66+
$msg = 'Deleting' . ($unused ? ' unused ': ' ') . 'replicas for ' . ($storeIds ? count($storeIds) : 'all') . ' store' . (!$storeIds || count($storeIds) > 1 ? 's' : '');
6667
if ($storeIds) {
6768
$output->writeln("<info>$msg: " . join(", ", $this->storeNameFetcher->getStoreNames($storeIds)) . '</info>');
6869
} else {
@@ -72,34 +73,34 @@ protected function execute(InputInterface $input, OutputInterface $output): int
7273
$this->output = $output;
7374
// $this->state->setAreaCode(Area::AREA_ADMINHTML);
7475

75-
$this->deleteReplicas($storeIds);
76+
$this->deleteReplicas($storeIds, $unused);
7677

7778
return Cli::RETURN_SUCCESS;
7879
}
7980

8081

81-
protected function deleteReplicas(array $storeIds = [], bool $unusedOnly = false): void
82+
protected function deleteReplicas(array $storeIds = [], bool $unused = false): void
8283
{
8384
if (count($storeIds)) {
8485
foreach ($storeIds as $storeId) {
85-
$this->deleteReplicasForStore($storeId);
86+
$this->deleteReplicasForStore($storeId, $unused);
8687
}
8788
} else {
88-
$this->deleteReplicasForAllStores();
89+
$this->deleteReplicasForAllStores($unused);
8990
}
9091
}
9192

92-
protected function deleteReplicasForStore(int $storeId): void
93+
protected function deleteReplicasForStore(int $storeId, bool $unused = false): void
9394
{
94-
$this->output->writeln('<info>Deleting replicas for ' . $this->storeNameFetcher->getStoreName($storeId) . '...</info>');
95-
$this->replicaManager->deleteReplicasFromAlgolia($storeId);
95+
$this->output->writeln('<info>Deleting' . ($unused ? ' unused ': ' ') . 'replicas for ' . $this->storeNameFetcher->getStoreName($storeId) . '...</info>');
96+
$this->replicaManager->deleteReplicasFromAlgolia($storeId, $unused);
9697
}
9798

98-
protected function deleteReplicasForAllStores(): void
99+
protected function deleteReplicasForAllStores(bool $unused = false): void
99100
{
100101
$storeIds = array_keys($this->storeManager->getStores());
101102
foreach ($storeIds as $storeId) {
102-
$this->deleteReplicasForStore($storeId);
103+
$this->deleteReplicasForStore($storeId, $unused);
103104
}
104105
}
105106
}

Service/IndexNameFetcher.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public function __construct(
1818
/** @var string */
1919
public const INDEX_TEMP_SUFFIX = '_tmp';
2020

21+
public const INDEX_QUERY_SUGGESTIONS_SUFFIX = '_query_suggestions';
22+
2123
/**
2224
* @param string $indexSuffix
2325
* @param int|null $storeId
@@ -53,4 +55,17 @@ public function isTempIndex($indexName): bool
5355
return str_ends_with($indexName, self::INDEX_TEMP_SUFFIX);
5456
}
5557

58+
/**
59+
* This is the default index name format for query suggestions but it can be overridden
60+
* This is a temporary workaround for delete index operations
61+
* TODO: Revisit this approach when a QuerySuggestionsClient is implemented in algoliasearch-client-php
62+
*
63+
* @param $indexName
64+
* @return bool
65+
*/
66+
public function isQuerySuggestionsIndex($indexName): bool
67+
{
68+
return str_ends_with($indexName, self::INDEX_QUERY_SUGGESTIONS_SUFFIX);
69+
}
70+
5671
}

Service/Product/ReplicaManager.php

Lines changed: 55 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Algolia\AlgoliaSearch\Validator\VirtualReplicaValidatorFactory;
1919
use Magento\Framework\Exception\LocalizedException;
2020
use Magento\Framework\Exception\NoSuchEntityException;
21+
use Magento\Store\Model\StoreManagerInterface;
2122

2223
/**
2324
* This class is responsible for managing the business logic related to translating the
@@ -53,6 +54,7 @@ public function __construct(
5354
protected IndexNameFetcher $indexNameFetcher,
5455
protected StoreNameFetcher $storeNameFetcher,
5556
protected SortingTransformer $sortingTransformer,
57+
protected StoreManagerInterface $storeManager,
5658
protected Logger $logger
5759
)
5860
{}
@@ -160,7 +162,7 @@ function ($algoliaReplicaSetting) use ($baseIndexName) {
160162
protected function isMagentoReplicaIndex(string $replicaIndexName, int|string $storeIdOrIndex): bool
161163
{
162164
$primaryIndexName = is_string($storeIdOrIndex) ? $storeIdOrIndex : $this->indexNameFetcher->getProductIndexName($storeIdOrIndex);
163-
return str_starts_with($replicaIndexName, $primaryIndexName);
165+
return $replicaIndexName !== $primaryIndexName && str_starts_with($replicaIndexName, $primaryIndexName);
164166
}
165167

166168
/**
@@ -404,77 +406,85 @@ public function getMaxVirtualReplicasPerIndex() : int
404406
}
405407

406408
/**
407-
* @param string $indexName
408-
* @param array $replicas
409-
* @param int $setReplicasTaskId
410-
* @return void
411409
* @throws AlgoliaException
412-
* @throws ExceededRetriesException
413410
*/
414-
protected function deleteUnusedReplicas(string $indexName, array $replicas, int $setReplicasTaskId): void
411+
protected function clearReplicasSettingInAlgolia(string $primaryIndexName): void
415412
{
416-
$indicesToDelete = [];
413+
$this->algoliaHelper->setSettings($primaryIndexName, [ self::ALGOLIA_SETTINGS_KEY_REPLICAS => []]);
414+
$this->algoliaHelper->waitLastTask($primaryIndexName);
415+
}
417416

418-
$allIndices = $this->algoliaHelper->listIndexes();
419-
foreach ($allIndices['items'] as $indexInfo) {
420-
//skip any indices that don't match the primary index
421-
if (mb_strpos($indexInfo['name'], $indexName) !== 0 || $indexInfo['name'] === $indexName) {
422-
continue;
423-
}
417+
/**
418+
* @throws AlgoliaException
419+
* @throws NoSuchEntityException
420+
* @throws LocalizedException
421+
*/
422+
public function deleteReplicasFromAlgolia(int $storeId, bool $unused = false): void
423+
{
424+
$primaryIndexName = $this->indexNameFetcher->getProductIndexName($storeId);
424425

425-
// skip temp indices and expected replicas
426-
if (mb_strpos($indexInfo['name'], IndexNameFetcher::INDEX_TEMP_SUFFIX) === false && in_array($indexInfo['name'], $replicas) === false) {
427-
$indicesToDelete[] = $indexInfo['name'];
428-
}
426+
if ($unused) {
427+
$replicasToDelete = $this->getUnusedReplicaIndices($primaryIndexName);
428+
} else {
429+
$replicasToDelete = $this->getMagentoReplicaIndicesFromAlgolia($primaryIndexName);
430+
$this->clearReplicasSettingInAlgolia($primaryIndexName);
429431
}
430432

431-
if (count($indicesToDelete) > 0) {
432-
$this->algoliaHelper->waitLastTask($indexName, $setReplicasTaskId);
433-
434-
foreach ($indicesToDelete as $indexToDelete) {
435-
$this->algoliaHelper->deleteIndex($indexToDelete);
436-
}
437-
}
433+
$this->deleteReplicaIndices($replicasToDelete);
438434
}
439435

440436
/**
441-
* @throws AlgoliaException
437+
* @throws LocalizedException
442438
*/
443-
protected function clearReplicasSettingInAlgolia(string $primaryIndexName): void
439+
protected function getMagentoReplicaIndicesFromAlgolia(string $primaryIndexName): array
444440
{
445-
$this->algoliaHelper->setSettings($primaryIndexName, [ self::ALGOLIA_SETTINGS_KEY_REPLICAS => []]);
441+
return $this->getBareIndexNamesFromReplicaSetting($this->getMagentoReplicaConfigurationFromAlgolia($primaryIndexName));
446442
}
447443

448444
/**
449-
* @throws AlgoliaException
445+
* @return string[]
450446
* @throws NoSuchEntityException
451447
* @throws LocalizedException
448+
* @throws AlgoliaException
452449
*/
453-
public function deleteReplicasFromAlgolia(int $storeId, bool $unusedOnly = false): void
450+
protected function getUnusedReplicaIndices(string $primaryIndexName): array
454451
{
455-
$primaryIndexName = $this->indexNameFetcher->getProductIndexName($storeId);
456-
457-
// get all possible Magento managed product indices for this store
452+
$currentReplicas = $this->getMagentoReplicaIndicesFromAlgolia($primaryIndexName);
453+
$unusedReplicas = [];
458454
$allIndices = $this->algoliaHelper->listIndexes();
459455

460-
$currentReplicas = $this->getBareIndexNamesFromReplicaSetting($this->getMagentoReplicaConfigurationFromAlgolia($primaryIndexName));
461-
462-
if (!$unusedOnly) {
463-
$this->clearReplicasSettingInAlgolia($primaryIndexName);
464-
}
465-
466-
$replicasToDelete = [];
467-
468456
foreach ($allIndices['items'] as $indexInfo) {
469457
$indexName = $indexInfo['name'];
470458
if ($this->isMagentoReplicaIndex($indexName, $primaryIndexName)
471459
&& !$this->indexNameFetcher->isTempIndex($indexName)
472-
&& (!$unusedOnly || !array_search($indexName, $currentReplicas))
473-
) {
474-
$replicasToDelete[] = $indexName;
460+
&& !$this->indexNameFetcher->isQuerySuggestionsIndex($indexName)
461+
&& !in_array($indexName, $currentReplicas))
462+
{
463+
$unusedReplicas[] = $indexName;
475464
}
476465
}
477466

478-
$this->deleteReplicaIndices($replicasToDelete);
467+
return $unusedReplicas;
468+
}
469+
470+
/**
471+
* Get a list of all replica indices for all Magento managed stores
472+
* (This may be useful in case of cross store replica misconfiguration)
473+
* @return string[]
474+
* @throws NoSuchEntityException
475+
* @throws LocalizedException
476+
*/
477+
protected function getAllReplicaIndices(): array
478+
{
479+
$replicaIndices = [];
480+
$storeIds = array_keys($this->storeManager->getStores());
481+
foreach ($storeIds as $storeId) {
482+
$primaryIndexName = $this->indexNameFetcher->getProductIndexName($storeId);
483+
$replicaIndices = array_merge(
484+
$replicaIndices,
485+
$this->getMagentoReplicaIndicesFromAlgolia($primaryIndexName)
486+
);
487+
}
488+
return array_unique($replicaIndices);
479489
}
480490
}

0 commit comments

Comments
 (0)