fix: store index compat version in ES mapping _meta for correct rollback behaviour

- Write INDEX_COMPAT_VERSION into mapping _meta.index_compat_version on every
  build/promote so the version travels with the index rather than being a
  separate settings value
- saveIndexedConfig now reads compat version back from _meta alongside the
  existing analyzer/min_gram sync, so rollback automatically restores the
  correct value (or null for pre-feature indexes, triggering the reindex warning)
- Replace InvalidArgumentException in buildIndexSettings with $this->error()
  fallback to avoid stack traces in the console
- Fix stale comment in ViewsSearchJob referencing removed CLI flags
This commit is contained in:
Bart van Bragt 2026-04-10 13:46:08 +02:00
parent f486292f15
commit 439c74f0a3
2 changed files with 18 additions and 12 deletions

View File

@ -31,7 +31,9 @@ class BuildCommand extends Command
{
/**
* Bump this when a mapping change requires a full reindex.
* Stored in blomstra-search.index-compatible after a successful build.
* Written into the mapping's _meta.index_compat_version on every build and
* read back into blomstra-search.index-compatible by saveIndexedConfig so the
* value always reflects the live index including after a promote/rollback.
*/
public const INDEX_COMPAT_VERSION = 'v2';
@ -250,7 +252,6 @@ HELP;
$settings->set('blomstra-search.active-index', $staging);
$settings->set('blomstra-search.staging-index', null);
$settings->set('blomstra-search.index-compatible', self::INDEX_COMPAT_VERSION);
$this->saveIndexedConfig($client, $settings, $staging);
}
@ -427,8 +428,6 @@ HELP;
$client->indices()->putAlias(['index' => $concrete, 'name' => $alias]);
$settings->set('blomstra-search.active-index', $concrete);
$settings->set('blomstra-search.index-compatible', self::INDEX_COMPAT_VERSION);
$this->saveIndexedConfig($client, $settings, $concrete);
foreach ($seeders as $seeder) {
@ -488,21 +487,26 @@ HELP;
}
/**
* Persist the analysis config that is actually live in ES for the given index.
* Reading from ES (rather than from Flarum settings) means rollbacks are also
* covered: the stored values always reflect the index that is currently aliased,
* not the settings at the time the command ran.
* Persist the analysis config and compat version that are actually live in ES for the
* given index. Reading from ES (rather than from Flarum settings) means rollbacks are
* also covered: the stored values always reflect the index that is currently aliased,
* not the settings at the time the command ran. Indexes built before _meta tracking
* existed will yield a null compat version, which correctly triggers the reindex warning.
*/
protected function saveIndexedConfig(Client $client, SettingsRepositoryInterface $settings, string $indexName): void
{
$response = $client->indices()->getSettings(['index' => $indexName]);
$analysis = Arr::get($response, "$indexName.settings.index.analysis", []);
$settingsResponse = $client->indices()->getSettings(['index' => $indexName]);
$analysis = Arr::get($settingsResponse, "$indexName.settings.index.analysis", []);
$analyzer = Arr::get($analysis, 'analyzer.flarum_analyzer.type', 'english');
$minGram = (int) Arr::get($analysis, 'filter.partial_search_filter.min_gram', self::DEFAULT_MIN_SEARCH_LENGTH);
$mappingResponse = $client->indices()->getMapping(['index' => $indexName]);
$compatVersion = Arr::get($mappingResponse, "$indexName.mappings._meta.index_compat_version");
$settings->set('blomstra-search.indexed-analyzer', $analyzer);
$settings->set('blomstra-search.indexed-min-search-length', $minGram);
$settings->set('blomstra-search.index-compatible', $compatVersion);
}
protected function buildIndexSettings(SettingsRepositoryInterface $settings): array
@ -512,7 +516,8 @@ HELP;
$maxGram = 10;
if ($minGram >= $maxGram) {
throw new \InvalidArgumentException("min_gram ($minGram) must be less than max_gram ($maxGram).");
$this->error("min_gram ($minGram) must be less than max_gram ($maxGram). Using default.");
$minGram = self::DEFAULT_MIN_SEARCH_LENGTH;
}
return [
@ -543,6 +548,7 @@ HELP;
protected function mappingProperties(): array
{
return [
'_meta' => ['index_compat_version' => self::INDEX_COMPAT_VERSION],
'properties' => [
'join_field' => ['type' => 'join', 'relations' => ['discussion' => 'post']],
'discussion_id' => ['type' => 'integer'],

View File

@ -53,7 +53,7 @@ class ViewsSearchJob extends AbstractJob
],
]);
} catch (\Elasticsearch\Common\Exceptions\Missing404Exception $e) {
// Document not yet indexed; will be picked up on next --seed-missing or --recreate.
// Document not yet indexed; will be picked up on next: blomstra:search:index fill
}
}
}