Skip to content

Commit a6ee705

Browse files
Merge #354
354: Add an option to use index swap in import command r=norkunas a=thislg # Pull Request ## Related issue Fixes #277 ## What does this PR do? - This PR adds an option to use index swap to prevent downtime while importing data in an existing index. ## PR checklist Please check if your PR fulfills the following requirements: - [x] Does this PR fix an existing issue, or have you listed the changes applied in the PR description (and why they are needed)? - [x] Have you read the contributing guidelines? - [x] Have you made sure that the title is accurate and descriptive of the changes? Co-authored-by: Thibaut Selingue <[email protected]>
2 parents d510d87 + 33fa9cb commit a6ee705

File tree

3 files changed

+77
-3
lines changed

3 files changed

+77
-3
lines changed

src/Command/MeilisearchImportCommand.php

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
final class MeilisearchImportCommand extends IndexCommand
2323
{
24+
private const TEMP_INDEX_PREFIX = '_tmp_';
25+
2426
private Client $searchClient;
2527
private ManagerRegistry $managerRegistry;
2628
private SettingsUpdater $settingsUpdater;
@@ -73,6 +75,12 @@ protected function configure(): void
7375
'Timeout (in ms) to get response from the search engine',
7476
self::DEFAULT_RESPONSE_TIMEOUT
7577
)
78+
->addOption(
79+
'swap-indices',
80+
null,
81+
InputOption::VALUE_NONE,
82+
'Import to temporary indices and use index swap to prevent downtime'
83+
)
7684
;
7785
}
7886

@@ -83,6 +91,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int
8391
$indexes = $this->getEntitiesFromArgs($input, $output);
8492
$entitiesToIndex = $this->entitiesToIndex($indexes);
8593
$config = $this->searchService->getConfiguration();
94+
$swapIndices = $input->getOption('swap-indices');
95+
$initialPrefix = $config['prefix'] ?? '';
96+
$prefix = null;
97+
98+
if ($swapIndices) {
99+
$prefix = self::TEMP_INDEX_PREFIX;
100+
$config['prefix'] = $prefix.($config['prefix'] ?? '');
101+
}
102+
86103
$updateSettings = $input->getOption('update-settings');
87104
$batchSize = $input->getOption('batch-size') ?? '';
88105
$batchSize = ctype_digit($batchSize) ? (int) $batchSize : $config->get('batchSize');
@@ -149,10 +166,16 @@ protected function execute(InputInterface $input, OutputInterface $output): int
149166
$manager->clear();
150167

151168
if ($updateSettings) {
152-
$this->settingsUpdater->update($index['prefixed_name'], $responseTimeout);
169+
$this->settingsUpdater->update($index['prefixed_name'], $responseTimeout, $prefix ? $prefix.$index['prefixed_name'] : null);
153170
}
154171
}
155172

173+
if ($swapIndices) {
174+
$this->swapIndices($indexes, $prefix, $output);
175+
176+
$config['prefix'] = $initialPrefix;
177+
}
178+
156179
$output->writeln('<info>Done!</info>');
157180

158181
return 0;
@@ -210,4 +233,32 @@ private function entitiesToIndex($indexes): array
210233

211234
return array_unique($indexes->all(), SORT_REGULAR);
212235
}
236+
237+
private function swapIndices(Collection $indexes, string $prefix, OutputInterface $output): void
238+
{
239+
$indexPairs = [];
240+
241+
foreach ($indexes as $index) {
242+
$tempIndex = $index;
243+
$tempIndex['name'] = $prefix.$tempIndex['name'];
244+
$pair = [$tempIndex['name'], $index['name']];
245+
246+
// Indexes must be declared only once during a swap
247+
if (!\in_array($pair, $indexPairs, true)) {
248+
$indexPairs[] = $pair;
249+
}
250+
}
251+
252+
// swap indexes
253+
$output->writeln('<info>Swapping indices...</info>');
254+
$this->searchClient->swapIndexes($indexPairs);
255+
$output->writeln('<info>Indices swapped.</info>');
256+
$output->writeln('<info>Deleting temporary indices...</info>');
257+
258+
// delete temp indexes
259+
foreach ($indexPairs as $pair) {
260+
$this->searchService->deleteByIndexName($pair[0]);
261+
$output->writeln('<info>Deleted '.$pair[0].'</info>');
262+
}
263+
}
213264
}

src/Services/SettingsUpdater.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public function __construct(SearchService $searchService, Client $searchClient,
3333
* @param non-empty-string $indice
3434
* @param positive-int|null $responseTimeout
3535
*/
36-
public function update(string $indice, ?int $responseTimeout = null): void
36+
public function update(string $indice, ?int $responseTimeout = null, ?string $prefixedName = null): void
3737
{
3838
$index = (new Collection($this->configuration->get('indices')))->firstWhere('prefixed_name', $indice);
3939

@@ -45,7 +45,7 @@ public function update(string $indice, ?int $responseTimeout = null): void
4545
return;
4646
}
4747

48-
$indexName = $index['prefixed_name'];
48+
$indexName = $prefixedName ?? $index['prefixed_name'];
4949
$indexInstance = $this->searchClient->index($indexName);
5050
$responseTimeout = $responseTimeout ?? self::DEFAULT_RESPONSE_TIMEOUT;
5151

tests/Integration/Command/MeilisearchImportCommandTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,4 +391,27 @@ public function testAlias(): void
391391

392392
self::assertSame(['meili:import'], $command->getAliases());
393393
}
394+
395+
public function testImportingIndexWithSwap(): void
396+
{
397+
for ($i = 0; $i <= 5; ++$i) {
398+
$this->createPost();
399+
}
400+
401+
$command = $this->application->find('meilisearch:import');
402+
$commandTester = new CommandTester($command);
403+
$return = $commandTester->execute([
404+
'--swap-indices' => true,
405+
]);
406+
407+
$output = $commandTester->getDisplay();
408+
$this->assertStringContainsString('Importing for index Meilisearch\Bundle\Tests\Entity\Post', $output);
409+
$this->assertStringContainsString('Indexed a batch of 6 / 6 Meilisearch\Bundle\Tests\Entity\Post entities into _tmp_sf_phpunit__'.self::$indexName.' index (6 indexed since start)', $output);
410+
$this->assertStringContainsString('Swapping indices...', $output);
411+
$this->assertStringContainsString('Indices swapped.', $output);
412+
$this->assertStringContainsString('Deleting temporary indices...', $output);
413+
$this->assertStringContainsString('Deleted _tmp_sf_phpunit__'.self::$indexName, $output);
414+
$this->assertStringContainsString('Done!', $output);
415+
$this->assertSame(0, $return);
416+
}
394417
}

0 commit comments

Comments
 (0)