diff --git a/docs/changelog/125659.yaml b/docs/changelog/125659.yaml new file mode 100644 index 000000000000..d0f2266e9456 --- /dev/null +++ b/docs/changelog/125659.yaml @@ -0,0 +1,6 @@ +pr: 125659 +summary: Non existing synonyms sets do not fail shard recovery for indices +area: "Analysis" +type: bug +issues: + - 125603 diff --git a/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/SynonymTokenFilterFactory.java b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/SynonymTokenFilterFactory.java index 9e31fdde4330..3dcbd87820f2 100644 --- a/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/SynonymTokenFilterFactory.java +++ b/modules/analysis-common/src/main/java/org/elasticsearch/analysis/common/SynonymTokenFilterFactory.java @@ -74,7 +74,7 @@ public class SynonymTokenFilterFactory extends AbstractTokenFilterFactory { ); } else { reader = new ReaderWithOrigin( - Analysis.getReaderFromIndex(synonymsSet, factory.synonymsManagementAPIService), + Analysis.getReaderFromIndex(synonymsSet, factory.synonymsManagementAPIService, factory.lenient), "[" + synonymsSet + "] synonyms_set in .synonyms index", synonymsSet ); diff --git a/qa/mixed-cluster/build.gradle b/qa/mixed-cluster/build.gradle index ee85f5df8b5f..50a1f66cc8ac 100644 --- a/qa/mixed-cluster/build.gradle +++ b/qa/mixed-cluster/build.gradle @@ -64,9 +64,6 @@ excludeList.add('cluster.desired_nodes/20_dry_run/Test validation works for dry // Excluded because they create dot-prefixed indices on older versions excludeList.add('indices.resolve_index/20_resolve_system_index/*') -// Can't work until auto-expand replicas is 0-1 for synonyms index -excludeList.add("synonyms/90_synonyms_reloading_for_synset/Reload analyzers for specific synonym set") - def clusterPath = getPath() buildParams.bwcVersions.withWireCompatible { bwcVersion, baseName -> diff --git a/rest-api-spec/build.gradle b/rest-api-spec/build.gradle index a37c0f0b813a..49f2415fac39 100644 --- a/rest-api-spec/build.gradle +++ b/rest-api-spec/build.gradle @@ -278,4 +278,5 @@ tasks.named("yamlRestTestV7CompatTransform").configure({ task -> task.skipTest("search.vectors/160_knn_query_missing_params/kNN search in a dis_max query - missing num_candidates", "waiting for #118774 backport") task.skipTest("migration/10_get_feature_upgrade_status/Get feature upgrade status", "Moved to plugin") task.skipTest("migration/20_post_feature_upgrade/Get feature upgrade status", "Moved to plugin") + task.skipTest("synonyms/80_synonyms_from_index/Fail loading synonyms from index if synonyms_set doesn't exist", "Synonyms do no longer fail if the synonyms_set doesn't exist") }) diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/110_synonyms_invalid.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/110_synonyms_invalid.yml index 7f545b466e65..3baeb39e410c 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/110_synonyms_invalid.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/110_synonyms_invalid.yml @@ -315,3 +315,160 @@ setup: indices.stats: { index: test_index } - length: { indices: 0 } + +--- +"Load index with non existent synonyms set": + - requires: + cluster_features: [ index.synonyms_set_lenient_on_non_existing ] + reason: "requires synonyms_set_lenient_on_non_existing bug fix" + - do: + indices.create: + index: test_index + body: + settings: + index: + number_of_shards: 1 + number_of_replicas: 0 + analysis: + filter: + my_synonym_filter: + type: synonym + synonyms_set: set1 + updateable: true + analyzer: + my_analyzer: + type: custom + tokenizer: whitespace + filter: [ lowercase, my_synonym_filter ] + mappings: + properties: + my_field: + type: text + search_analyzer: my_analyzer + + - match: { acknowledged: true } + - match: { shards_acknowledged: true } + + - do: + indices.stats: { index: test_index } + + - match: { indices.test_index.health: "green" } + + # Synonyms are not applied + - do: + indices.analyze: + index: test_index + body: + analyzer: my_analyzer + text: foo + + - length: { tokens: 1 } + - match: { tokens.0.token: foo } + + + # Create synonyms set and check synonyms are applied + - do: + synonyms.put_synonym: + id: set1 + body: + synonyms_set: + synonyms: "foo => bar, baz" + + # This is to ensure that all index shards (write and read) are available. In serverless this can take some time. + - do: + cluster.health: + index: .synonyms + wait_for_status: green + + + - do: + indices.stats: { index: test_index } + + - match: { indices.test_index.health: "green" } + + # Synonyms are applied + - do: + indices.analyze: + index: test_index + body: + analyzer: my_analyzer + text: foo + + - length: { tokens: 2 } + +--- +"Load index with non existent synonyms set and lenient set to false": + - requires: + test_runner_features: [ allowed_warnings ] + + - do: + indices.create: + index: test_index + body: + settings: + index: + number_of_shards: 1 + number_of_replicas: 0 + analysis: + filter: + my_synonym_filter: + type: synonym + synonyms_set: set1 + updateable: true + lenient: false + analyzer: + my_analyzer: + type: custom + tokenizer: whitespace + filter: [ lowercase, my_synonym_filter ] + mappings: + properties: + my_field: + type: text + search_analyzer: my_analyzer + + - match: { acknowledged: true } + - match: { shards_acknowledged: false } + + - do: + indices.stats: { index: test_index } + + - length: { indices: 0 } + + # Create synonyms set and check synonyms are applied + - do: + synonyms.put_synonym: + id: set1 + body: + synonyms_set: + synonyms: "foo => bar, baz" + + # This is to ensure that all index shards (write and read) are available. In serverless this can take some time. + - do: + cluster.health: + index: .synonyms + # BWC tests won't be able to get green - synonyms index is 0-all, so some shards won't be recovered in older nodes + wait_for_status: yellow + + - do: + # Warning issued in previous versions + allowed_warnings: + - "The [state] field in the response to the reroute API is deprecated and will be removed in a future version. Specify ?metric=none to adopt the future behaviour." + cluster.reroute: + retry_failed: true + + - do: + cluster.health: + index: test_index + wait_for_status: green + + # Synonyms are applied + - do: + indices.analyze: + index: test_index + body: + analyzer: my_analyzer + text: foo + + - length: { tokens: 2 } + diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/80_synonyms_from_index.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/80_synonyms_from_index.yml index 90c35cc8f488..fbbd42ad590d 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/80_synonyms_from_index.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/80_synonyms_from_index.yml @@ -168,34 +168,6 @@ setup: query: hola - match: { hits.total.value: 1 } ---- -"Fail loading synonyms from index if synonyms_set doesn't exist": - - do: - indices.create: - index: another_index - body: - settings: - index: - number_of_shards: 1 - analysis: - filter: - my_synonym_filter: - type: synonym - synonyms_set: set_missing - updateable: true - analyzer: - my_analyzer: - type: custom - tokenizer: standard - filter: [ lowercase, my_synonym_filter ] - mappings: - properties: - my_field: - type: text - search_analyzer: my_analyzer - - match: { acknowledged: true } - - match: { shards_acknowledged: false } - --- "Load empty synonyms set from index for an analyzer": - do: diff --git a/server/src/main/java/org/elasticsearch/index/IndexFeatures.java b/server/src/main/java/org/elasticsearch/index/IndexFeatures.java index f940e87e5139..c1abf1f67075 100644 --- a/server/src/main/java/org/elasticsearch/index/IndexFeatures.java +++ b/server/src/main/java/org/elasticsearch/index/IndexFeatures.java @@ -23,8 +23,10 @@ public class IndexFeatures implements FeatureSpecification { public static final NodeFeature LOGSDB_NO_HOST_NAME_FIELD = new NodeFeature("index.logsdb_no_host_name_field"); + private static final NodeFeature SYNONYMS_SET_LENIENT_ON_NON_EXISTING = new NodeFeature("index.synonyms_set_lenient_on_non_existing"); + @Override public Set getTestFeatures() { - return Set.of(LOGSDB_NO_HOST_NAME_FIELD); + return Set.of(LOGSDB_NO_HOST_NAME_FIELD, SYNONYMS_SET_LENIENT_ON_NON_EXISTING); } } diff --git a/server/src/main/java/org/elasticsearch/index/analysis/Analysis.java b/server/src/main/java/org/elasticsearch/index/analysis/Analysis.java index 2455b4dddd48..a518624fb0ab 100644 --- a/server/src/main/java/org/elasticsearch/index/analysis/Analysis.java +++ b/server/src/main/java/org/elasticsearch/index/analysis/Analysis.java @@ -48,6 +48,7 @@ import org.apache.lucene.analysis.sv.SwedishAnalyzer; import org.apache.lucene.analysis.th.ThaiAnalyzer; import org.apache.lucene.analysis.tr.TurkishAnalyzer; import org.apache.lucene.analysis.util.CSVUtil; +import org.elasticsearch.ResourceNotFoundException; import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.common.Strings; import org.elasticsearch.common.logging.DeprecationCategory; @@ -367,10 +368,39 @@ public class Analysis { } } - public static Reader getReaderFromIndex(String synonymsSet, SynonymsManagementAPIService synonymsManagementAPIService) { + public static Reader getReaderFromIndex( + String synonymsSet, + SynonymsManagementAPIService synonymsManagementAPIService, + boolean ignoreMissing + ) { final PlainActionFuture> synonymsLoadingFuture = new PlainActionFuture<>(); synonymsManagementAPIService.getSynonymSetRules(synonymsSet, synonymsLoadingFuture); - PagedResult results = synonymsLoadingFuture.actionGet(); + + PagedResult results; + + try { + results = synonymsLoadingFuture.actionGet(); + } catch (Exception e) { + if (ignoreMissing == false) { + throw e; + } + + boolean notFound = e instanceof ResourceNotFoundException; + String message = String.format( + Locale.ROOT, + "Synonyms set %s %s. Synonyms will not be applied to search results on indices that use this synonym set", + synonymsSet, + notFound ? "not found" : "could not be loaded" + ); + + if (notFound) { + logger.warn(message); + } else { + logger.error(message, e); + } + + results = new PagedResult<>(0, new SynonymRule[0]); + } SynonymRule[] synonymRules = results.pageResults(); StringBuilder sb = new StringBuilder();