Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/changelog/125650.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pr: 125650
summary: Load `FieldInfos` from store if not yet initialised through a refresh on
`IndexShard`
area: Search
type: bug
issues:
- 125483
40 changes: 30 additions & 10 deletions server/src/main/java/org/elasticsearch/index/shard/IndexShard.java
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@
import java.io.Closeable;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.nio.channels.ClosedByInterruptException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
Expand Down Expand Up @@ -427,7 +429,6 @@ public IndexShard(
this.refreshFieldHasValueListener = new RefreshFieldHasValueListener();
this.relativeTimeInNanosSupplier = relativeTimeInNanosSupplier;
this.indexCommitListener = indexCommitListener;
this.fieldInfos = FieldInfos.EMPTY;
}

public ThreadPool getThreadPool() {
Expand Down Expand Up @@ -1029,12 +1030,26 @@ private Engine.IndexResult applyIndexOperation(
return index(engine, operation);
}

public void setFieldInfos(FieldInfos fieldInfos) {
this.fieldInfos = fieldInfos;
private static final VarHandle FIELD_INFOS;

static {
try {
FIELD_INFOS = MethodHandles.lookup().findVarHandle(IndexShard.class, "fieldInfos", FieldInfos.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah this is a bit I don't like about VarHandle, and I'd normally prefer an Atomic... for the CAS operation for readability.

However, the tradeoff here is pretty sweet as we avoid creating (a potential alternative) one Atomic... instance for every IndexShard in order to wrap the fieldInfos giving us a sweet memory saving.

Nicely done ! :)

} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}

public FieldInfos getFieldInfos() {
return fieldInfos;
var res = fieldInfos;
if (res == null) {
// don't replace field infos loaded via the refresh listener to avoid overwriting the field with an older version of the
// field infos when racing with a refresh
var read = loadFieldInfos();
var existing = (FieldInfos) FIELD_INFOS.compareAndExchange(this, null, read);
return existing == null ? read : existing;
}
return res;
}

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

@Override
public void afterRefresh(boolean didRefresh) {
if (enableFieldHasValue && (didRefresh || fieldInfos == FieldInfos.EMPTY)) {
try (Engine.Searcher hasValueSearcher = getEngine().acquireSearcher("field_has_value")) {
setFieldInfos(FieldInfos.getMergedFieldInfos(hasValueSearcher.getIndexReader()));
} catch (AlreadyClosedException ignore) {
// engine is closed - no updated FieldInfos is fine
}
if (enableFieldHasValue && (didRefresh || fieldInfos == null)) {
FIELD_INFOS.setRelease(IndexShard.this, loadFieldInfos());
}
}
}

private FieldInfos loadFieldInfos() {
try (Engine.Searcher hasValueSearcher = getEngine().acquireSearcher("field_has_value")) {
return FieldInfos.getMergedFieldInfos(hasValueSearcher.getIndexReader());
} catch (AlreadyClosedException ignore) {
// engine is closed - no update to FieldInfos is fine
}
return FieldInfos.EMPTY;
}

/**
* Returns the shard-level field stats, which includes the number of segments in the latest NRT reader of this shard
* and the total number of fields across those segments.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

package org.elasticsearch.xpack.searchablesnapshots;

import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchType;
Expand Down Expand Up @@ -125,5 +126,9 @@ public void testKeywordSortedQueryOnFrozen() throws Exception {
assertThat(searchResponse.getTotalShards(), equalTo(20));
assertThat(searchResponse.getHits().getTotalHits().value(), equalTo(4L));
});

// check that field_caps empty field filtering works as well
FieldCapabilitiesResponse response = client().prepareFieldCaps(mountedIndices).setFields("*").setincludeEmptyFields(false).get();
assertNotNull(response.getField("keyword"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it makes sense to add the same test to the N-2 indices related tests for indices that are imported as verified read-only. We know the fix addresses the same issue for them too, but it'd be safer to have a specific test for them? These tests are under qa/lucene-index-compatibility .

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

++ definitely. Let me try something, maybe I can get the full field caps suite running for searchable snapshots and there somehow, would be really nice to have coverage across all the implementations that play tricks on the engine :)

}
}
Loading