Zero-Downtime Reindexing
Before going live, you want to refactor your reindexing strategy to ensure that your reindexing process creates no downtime.
To do so in your WordPress setup, you can use iterators. It lets you create a temporary index, index your data, then rename the temporary index to replace the production one. The renaming operation is atomic, making it transparent for your end users.
Reindexing records
To reindex atomically, you need to change the reindex_posts
command. You first want to extract the query loop to an iterator, then use the replaceAllObjects
method instead of saveObjects
.
First, you need to create a new Algolia_Post_Iterator
class in your wp-cli.php
file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class Algolia_Post_Iterator implements Iterator {
/**
* @var array
*/
private $queryArgs;
private $key;
private $paged;
private $posts;
private $type;
public function __construct($type, array $queryArgs = []) {
$this->type = $type;
$this->queryArgs = ['post_type' => $type] + $queryArgs;
}
public function current() {
return $this->serialize($this->posts[$this->key]);
}
public function next() {
$this->key++;
}
public function key() {
$this->key;
}
public function valid() {
if (isset($this->posts[$this->key])) {
return true;
}
$this->paged++;
$query = new WP_Query(['paged' => $this->paged] + $this->queryArgs);
if (!$query->have_posts()) {
return false;
}
$this->posts = $query->posts;
$this->key = 0;
return true;
}
public function rewind() {
$this->key = 0;
$this->paged = 0;
$this->posts = [];
}
private function serialize( WP_Post $post ) {
$record = (array) apply_filters($this->type.'_to_record', $post);
if (!isset($record['objectID'])) {
$record['objectID'] = implode('#', [$post->post_type, $post->ID]);
}
return $record;
}
}
Now you can simplify your indexing command. You can specify the query parameters to use, pass them to the iterator, then pass the iterator to replaceAllObjects
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public function reindex_post_atomic($args, $assoc_args) {
global $algolia;
$type = isset($assoc_args['type']) ? $assoc_args['type'] : 'post';
$index = $algolia->initIndex(
apply_filters('algolia_index_name', $type)
);
$queryArgs = [
'posts_per_page' => 100,
'post_status' => 'publish',
];
$iterator = new Algolia_Post_Iterator($type, $queryArgs);
$index->replaceAllObjects($iterator);
WP_CLI::success("Reindexed $type posts in Algolia");
}
Reindexing records and configuration
If you want to send settings, synonyms, and Rules when reindexing, you can’t use replaceAllObjects
directly, and need to perform each step explicitly.
Note that the following example uses the same iterator as the earlier example.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public function reindex_post_atomic_with_config($args, $assoc_args) {
global $algolia;
$type = isset($assoc_args['type']) ? $assoc_args['type'] : 'post';
$temporaryName = sha1($type.time().mt_rand(0, 100));
$finalIndexName = apply_filters('algolia_index_name', $type);
$index = $algolia->initIndex($temporaryName);
$settings = (array) apply_filters('get_'.$type.'_settings', []);
unset($settings['replicas']);
if ($settings) {
$index->setSettings($settings);
}
$synonyms = (array) apply_filters('get_'.$type.'_synonyms', []);
if ($synonyms) {
$index->saveSynonyms($synonyms);
}
$rules = (array) apply_filters('get_'.$type.'$rules', []);
if ($rules) {
$index->saveRules($rules);
}
$queryArgs = [
'posts_per_page' => 100,
'post_status' => 'publish',
];
$iterator = new Algolia_Post_Iterator($type, $queryArgs);
$index->saveObjects($iterator);
$algolia->moveIndex($temporaryName, $finalIndexName);
WP_CLI::success("Reindexed $type posts in Algolia");
}