Skip to content

Commit 8350ff2

Browse files
authored
Extensible Completion Postings Formats (elastic#111494)
Allows the Completion Postings Format to be extensible by providing an implementation of the CompletionsPostingsFormatExtension SPIs.
1 parent f096c31 commit 8350ff2

File tree

6 files changed

+59
-26
lines changed

6 files changed

+59
-26
lines changed

docs/changelog/111494.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 111494
2+
summary: Extensible Completion Postings Formats
3+
area: "Suggesters"
4+
type: enhancement
5+
issues: []

server/src/main/java/module-info.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10+
import org.elasticsearch.internal.CompletionsPostingsFormatExtension;
1011
import org.elasticsearch.plugins.internal.RestExtension;
1112

1213
/** The Elasticsearch Server Module. */
@@ -288,7 +289,8 @@
288289
to
289290
org.elasticsearch.serverless.version,
290291
org.elasticsearch.serverless.buildinfo,
291-
org.elasticsearch.serverless.constants;
292+
org.elasticsearch.serverless.constants,
293+
org.elasticsearch.serverless.codec;
292294
exports org.elasticsearch.lucene.analysis.miscellaneous;
293295
exports org.elasticsearch.lucene.grouping;
294296
exports org.elasticsearch.lucene.queries;
@@ -395,6 +397,7 @@
395397
org.elasticsearch.stateless,
396398
org.elasticsearch.settings.secure,
397399
org.elasticsearch.serverless.constants,
400+
org.elasticsearch.serverless.codec,
398401
org.elasticsearch.serverless.apifiltering,
399402
org.elasticsearch.internal.security;
400403

@@ -414,6 +417,7 @@
414417
uses org.elasticsearch.node.internal.TerminationHandlerProvider;
415418
uses org.elasticsearch.internal.VersionExtension;
416419
uses org.elasticsearch.internal.BuildExtension;
420+
uses CompletionsPostingsFormatExtension;
417421
uses org.elasticsearch.features.FeatureSpecification;
418422
uses org.elasticsearch.plugins.internal.LoggingDataProvider;
419423

server/src/main/java/org/elasticsearch/index/codec/PerFieldFormatSupplier.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,15 @@
2020
import org.elasticsearch.index.codec.bloomfilter.ES87BloomFilterPostingsFormat;
2121
import org.elasticsearch.index.codec.postings.ES812PostingsFormat;
2222
import org.elasticsearch.index.codec.tsdb.ES87TSDBDocValuesFormat;
23+
import org.elasticsearch.index.mapper.CompletionFieldMapper;
2324
import org.elasticsearch.index.mapper.IdFieldMapper;
2425
import org.elasticsearch.index.mapper.Mapper;
2526
import org.elasticsearch.index.mapper.MapperService;
2627
import org.elasticsearch.index.mapper.vectors.DenseVectorFieldMapper;
28+
import org.elasticsearch.internal.CompletionsPostingsFormatExtension;
29+
import org.elasticsearch.plugins.ExtensionLoader;
30+
31+
import java.util.ServiceLoader;
2732

2833
/**
2934
* Class that encapsulates the logic of figuring out the most appropriate file format for a given field, across postings, doc values and
@@ -53,15 +58,28 @@ public PostingsFormat getPostingsFormatForField(String field) {
5358

5459
private PostingsFormat internalGetPostingsFormatForField(String field) {
5560
if (mapperService != null) {
56-
final PostingsFormat format = mapperService.mappingLookup().getPostingsFormat(field);
57-
if (format != null) {
58-
return format;
61+
Mapper mapper = mapperService.mappingLookup().getMapper(field);
62+
if (mapper instanceof CompletionFieldMapper) {
63+
return PostingsFormatHolder.POSTINGS_FORMAT;
5964
}
6065
}
6166
// return our own posting format using PFOR
6267
return es812PostingsFormat;
6368
}
6469

70+
private static class PostingsFormatHolder {
71+
private static final PostingsFormat POSTINGS_FORMAT = getPostingsFormat();
72+
73+
private static PostingsFormat getPostingsFormat() {
74+
String defaultName = "Completion912"; // Caution: changing this name will result in exceptions if a field is created during a
75+
// rolling upgrade and the new codec (specified by the name) is not available on all nodes in the cluster.
76+
String codecName = ExtensionLoader.loadSingleton(ServiceLoader.load(CompletionsPostingsFormatExtension.class))
77+
.map(CompletionsPostingsFormatExtension::getFormatName)
78+
.orElse(defaultName);
79+
return PostingsFormat.forName(codecName);
80+
}
81+
}
82+
6583
boolean useBloomFilter(String field) {
6684
if (mapperService == null) {
6785
return false;

server/src/main/java/org/elasticsearch/index/mapper/CompletionFieldMapper.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
*/
99
package org.elasticsearch.index.mapper;
1010

11-
import org.apache.lucene.codecs.PostingsFormat;
1211
import org.apache.lucene.document.FieldType;
1312
import org.apache.lucene.index.IndexOptions;
1413
import org.apache.lucene.index.Term;
@@ -344,10 +343,6 @@ public CompletionFieldType fieldType() {
344343
return (CompletionFieldType) super.fieldType();
345344
}
346345

347-
static PostingsFormat postingsFormat() {
348-
return PostingsFormat.forName("Completion912");
349-
}
350-
351346
@Override
352347
public boolean parsesArrayValue() {
353348
return true;

server/src/main/java/org/elasticsearch/index/mapper/MappingLookup.java

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
package org.elasticsearch.index.mapper;
1111

12-
import org.apache.lucene.codecs.PostingsFormat;
1312
import org.elasticsearch.cluster.metadata.DataStream;
1413
import org.elasticsearch.cluster.metadata.InferenceFieldMetadata;
1514
import org.elasticsearch.index.IndexSettings;
@@ -21,7 +20,6 @@
2120
import java.util.Collection;
2221
import java.util.Collections;
2322
import java.util.HashMap;
24-
import java.util.HashSet;
2523
import java.util.List;
2624
import java.util.Map;
2725
import java.util.Set;
@@ -58,7 +56,6 @@ private CacheKey() {}
5856
private final Map<String, NamedAnalyzer> indexAnalyzersMap;
5957
private final List<FieldMapper> indexTimeScriptMappers;
6058
private final Mapping mapping;
61-
private final Set<String> completionFields;
6259
private final int totalFieldsCount;
6360

6461
/**
@@ -161,7 +158,6 @@ private MappingLookup(
161158
this.nestedLookup = NestedLookup.build(nestedMappers);
162159

163160
final Map<String, NamedAnalyzer> indexAnalyzersMap = new HashMap<>();
164-
final Set<String> completionFields = new HashSet<>();
165161
final List<FieldMapper> indexTimeScriptMappers = new ArrayList<>();
166162
for (FieldMapper mapper : mappers) {
167163
if (objects.containsKey(mapper.fullPath())) {
@@ -174,9 +170,6 @@ private MappingLookup(
174170
if (mapper.hasScript()) {
175171
indexTimeScriptMappers.add(mapper);
176172
}
177-
if (mapper instanceof CompletionFieldMapper) {
178-
completionFields.add(mapper.fullPath());
179-
}
180173
}
181174

182175
for (FieldAliasMapper aliasMapper : aliasMappers) {
@@ -211,7 +204,6 @@ private MappingLookup(
211204
this.objectMappers = Map.copyOf(objects);
212205
this.runtimeFieldMappersCount = runtimeFields.size();
213206
this.indexAnalyzersMap = Map.copyOf(indexAnalyzersMap);
214-
this.completionFields = Set.copyOf(completionFields);
215207
this.indexTimeScriptMappers = List.copyOf(indexTimeScriptMappers);
216208

217209
runtimeFields.stream().flatMap(RuntimeField::asMappedFieldTypes).map(MappedFieldType::name).forEach(this::validateDoesNotShadow);
@@ -285,15 +277,6 @@ public Iterable<Mapper> fieldMappers() {
285277
return fieldMappers.values();
286278
}
287279

288-
/**
289-
* Gets the postings format for a particular field
290-
* @param field the field to retrieve a postings format for
291-
* @return the postings format for the field, or {@code null} if the default format should be used
292-
*/
293-
public PostingsFormat getPostingsFormat(String field) {
294-
return completionFields.contains(field) ? CompletionFieldMapper.postingsFormat() : null;
295-
}
296-
297280
void checkLimits(IndexSettings settings) {
298281
checkFieldLimit(settings.getMappingTotalFieldsLimit());
299282
checkObjectDepthLimit(settings.getMappingDepthLimit());
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.internal;
11+
12+
import org.apache.lucene.search.suggest.document.CompletionPostingsFormat;
13+
14+
/**
15+
* Allows plugging-in the Completions Postings Format.
16+
*/
17+
public interface CompletionsPostingsFormatExtension {
18+
19+
/**
20+
* Returns the name of the {@link CompletionPostingsFormat} that Elasticsearch should use. Should return null if the extension
21+
* is not enabled.
22+
* <p>
23+
* Note that the name must match a codec that is available on all nodes in the cluster, otherwise IndexCorruptionExceptions will occur.
24+
* A feature can be used to protect against this scenario, or alternatively, the codec code can be rolled out prior to its usage by this
25+
* extension.
26+
*/
27+
String getFormatName();
28+
}

0 commit comments

Comments
 (0)