Skip to content

Commit d101448

Browse files
Load FieldInfos from store if not yet initialised through a refresh on IndexShard (elastic#125650)
Load field caps from store if they haven't been initialised through a refresh yet. Keep the plain reads to not mess with performance characteristics too much on the good path but protect against confusing races when loading field infos now (that probably should have been ordered stores in the first place but this was safe due to other locks/volatiles on the refresh path). Closes elastic#125483
1 parent e088eb3 commit d101448

File tree

3 files changed

+42
-10
lines changed

3 files changed

+42
-10
lines changed

docs/changelog/125650.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pr: 125650
2+
summary: Load `FieldInfos` from store if not yet initialised through a refresh on
3+
`IndexShard`
4+
area: Search
5+
type: bug
6+
issues:
7+
- 125483

server/src/main/java/org/elasticsearch/index/shard/IndexShard.java

+30-10
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@
156156
import java.io.Closeable;
157157
import java.io.IOException;
158158
import java.io.PrintStream;
159+
import java.lang.invoke.MethodHandles;
160+
import java.lang.invoke.VarHandle;
159161
import java.nio.channels.ClosedByInterruptException;
160162
import java.nio.charset.StandardCharsets;
161163
import java.util.ArrayList;
@@ -413,7 +415,6 @@ public IndexShard(
413415
this.refreshFieldHasValueListener = new RefreshFieldHasValueListener();
414416
this.relativeTimeInNanosSupplier = relativeTimeInNanosSupplier;
415417
this.indexCommitListener = indexCommitListener;
416-
this.fieldInfos = FieldInfos.EMPTY;
417418
}
418419

419420
public ThreadPool getThreadPool() {
@@ -1011,12 +1012,26 @@ private Engine.IndexResult applyIndexOperation(
10111012
return index(engine, operation);
10121013
}
10131014

1014-
public void setFieldInfos(FieldInfos fieldInfos) {
1015-
this.fieldInfos = fieldInfos;
1015+
private static final VarHandle FIELD_INFOS;
1016+
1017+
static {
1018+
try {
1019+
FIELD_INFOS = MethodHandles.lookup().findVarHandle(IndexShard.class, "fieldInfos", FieldInfos.class);
1020+
} catch (Exception e) {
1021+
throw new ExceptionInInitializerError(e);
1022+
}
10161023
}
10171024

10181025
public FieldInfos getFieldInfos() {
1019-
return fieldInfos;
1026+
var res = fieldInfos;
1027+
if (res == null) {
1028+
// don't replace field infos loaded via the refresh listener to avoid overwriting the field with an older version of the
1029+
// field infos when racing with a refresh
1030+
var read = loadFieldInfos();
1031+
var existing = (FieldInfos) FIELD_INFOS.compareAndExchange(this, null, read);
1032+
return existing == null ? read : existing;
1033+
}
1034+
return res;
10201035
}
10211036

10221037
public static Engine.Index prepareIndex(
@@ -4067,16 +4082,21 @@ public void beforeRefresh() {}
40674082

40684083
@Override
40694084
public void afterRefresh(boolean didRefresh) {
4070-
if (enableFieldHasValue && (didRefresh || fieldInfos == FieldInfos.EMPTY)) {
4071-
try (Engine.Searcher hasValueSearcher = getEngine().acquireSearcher("field_has_value")) {
4072-
setFieldInfos(FieldInfos.getMergedFieldInfos(hasValueSearcher.getIndexReader()));
4073-
} catch (AlreadyClosedException ignore) {
4074-
// engine is closed - no updated FieldInfos is fine
4075-
}
4085+
if (enableFieldHasValue && (didRefresh || fieldInfos == null)) {
4086+
FIELD_INFOS.setRelease(IndexShard.this, loadFieldInfos());
40764087
}
40774088
}
40784089
}
40794090

4091+
private FieldInfos loadFieldInfos() {
4092+
try (Engine.Searcher hasValueSearcher = getEngine().acquireSearcher("field_has_value")) {
4093+
return FieldInfos.getMergedFieldInfos(hasValueSearcher.getIndexReader());
4094+
} catch (AlreadyClosedException ignore) {
4095+
// engine is closed - no update to FieldInfos is fine
4096+
}
4097+
return FieldInfos.EMPTY;
4098+
}
4099+
40804100
/**
40814101
* Returns the shard-level field stats, which includes the number of segments in the latest NRT reader of this shard
40824102
* and the total number of fields across those segments.

x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsSearchIntegTests.java

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package org.elasticsearch.xpack.searchablesnapshots;
99

10+
import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse;
1011
import org.elasticsearch.action.index.IndexRequestBuilder;
1112
import org.elasticsearch.action.search.SearchRequest;
1213
import org.elasticsearch.action.search.SearchType;
@@ -125,5 +126,9 @@ public void testKeywordSortedQueryOnFrozen() throws Exception {
125126
assertThat(searchResponse.getTotalShards(), equalTo(20));
126127
assertThat(searchResponse.getHits().getTotalHits().value, equalTo(4L));
127128
});
129+
130+
// check that field_caps empty field filtering works as well
131+
FieldCapabilitiesResponse response = client().prepareFieldCaps(mountedIndices).setFields("*").setincludeEmptyFields(false).get();
132+
assertNotNull(response.getField("keyword"));
128133
}
129134
}

0 commit comments

Comments
 (0)