Skip to content

Commit 632b9e7

Browse files
Load FieldInfos from store if not yet initialised through a refresh on IndexShard (#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 #125483
1 parent 5cfe3cb commit 632b9e7

File tree

3 files changed

+42
-10
lines changed

3 files changed

+42
-10
lines changed

docs/changelog/125650.yaml

Lines changed: 7 additions & 0 deletions
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

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@
158158
import java.io.Closeable;
159159
import java.io.IOException;
160160
import java.io.PrintStream;
161+
import java.lang.invoke.MethodHandles;
162+
import java.lang.invoke.VarHandle;
161163
import java.nio.channels.ClosedByInterruptException;
162164
import java.nio.charset.StandardCharsets;
163165
import java.util.ArrayList;
@@ -427,7 +429,6 @@ public IndexShard(
427429
this.refreshFieldHasValueListener = new RefreshFieldHasValueListener();
428430
this.relativeTimeInNanosSupplier = relativeTimeInNanosSupplier;
429431
this.indexCommitListener = indexCommitListener;
430-
this.fieldInfos = FieldInfos.EMPTY;
431432
}
432433

433434
public ThreadPool getThreadPool() {
@@ -1029,12 +1030,26 @@ private Engine.IndexResult applyIndexOperation(
10291030
return index(engine, operation);
10301031
}
10311032

1032-
public void setFieldInfos(FieldInfos fieldInfos) {
1033-
this.fieldInfos = fieldInfos;
1033+
private static final VarHandle FIELD_INFOS;
1034+
1035+
static {
1036+
try {
1037+
FIELD_INFOS = MethodHandles.lookup().findVarHandle(IndexShard.class, "fieldInfos", FieldInfos.class);
1038+
} catch (Exception e) {
1039+
throw new ExceptionInInitializerError(e);
1040+
}
10341041
}
10351042

10361043
public FieldInfos getFieldInfos() {
1037-
return fieldInfos;
1044+
var res = fieldInfos;
1045+
if (res == null) {
1046+
// don't replace field infos loaded via the refresh listener to avoid overwriting the field with an older version of the
1047+
// field infos when racing with a refresh
1048+
var read = loadFieldInfos();
1049+
var existing = (FieldInfos) FIELD_INFOS.compareAndExchange(this, null, read);
1050+
return existing == null ? read : existing;
1051+
}
1052+
return res;
10381053
}
10391054

10401055
public static Engine.Index prepareIndex(
@@ -4266,16 +4281,21 @@ public void beforeRefresh() {}
42664281

42674282
@Override
42684283
public void afterRefresh(boolean didRefresh) {
4269-
if (enableFieldHasValue && (didRefresh || fieldInfos == FieldInfos.EMPTY)) {
4270-
try (Engine.Searcher hasValueSearcher = getEngine().acquireSearcher("field_has_value")) {
4271-
setFieldInfos(FieldInfos.getMergedFieldInfos(hasValueSearcher.getIndexReader()));
4272-
} catch (AlreadyClosedException ignore) {
4273-
// engine is closed - no updated FieldInfos is fine
4274-
}
4284+
if (enableFieldHasValue && (didRefresh || fieldInfos == null)) {
4285+
FIELD_INFOS.setRelease(IndexShard.this, loadFieldInfos());
42754286
}
42764287
}
42774288
}
42784289

4290+
private FieldInfos loadFieldInfos() {
4291+
try (Engine.Searcher hasValueSearcher = getEngine().acquireSearcher("field_has_value")) {
4292+
return FieldInfos.getMergedFieldInfos(hasValueSearcher.getIndexReader());
4293+
} catch (AlreadyClosedException ignore) {
4294+
// engine is closed - no update to FieldInfos is fine
4295+
}
4296+
return FieldInfos.EMPTY;
4297+
}
4298+
42794299
/**
42804300
* Returns the shard-level field stats, which includes the number of segments in the latest NRT reader of this shard
42814301
* and the total number of fields across those segments.

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

Lines changed: 5 additions & 0 deletions
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)