Skip to content

Commit 226a8ce

Browse files
authored
[8.16] Don't skip shards in coord rewrite if timestamp is an alias (#117271) (#117854)
* Don't skip shards in coord rewrite if timestamp is an alias (#117271) The coordinator rewrite has logic to skip indices if the provided date range filter is not within the min and max range of all of its shards. This mechanism is enabled for event.ingested and @timestamp fields, against searchable snapshots. We have basic checks that such fields need to be of date field type, yet if they are defined as alias of a date field, their range will be empty, which indicates that the shards are empty, and the coord rewrite logic resolves the alias and ends up skipping shards that may have matching docs. This commit adds an explicit check that declares the range UNKNOWN instead of EMPTY in these circumstances. The same check is also performed in the coord rewrite logic, so that shards are no longer skipped by mistake. * fix compile
1 parent 6315198 commit 226a8ce

File tree

5 files changed

+130
-10
lines changed

5 files changed

+130
-10
lines changed

docs/changelog/117271.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 117271
2+
summary: Don't skip shards in coord rewrite if timestamp is an alias
3+
area: Search
4+
type: bug
5+
issues: []

server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ public String getWriteableName() {
438438
protected MappedFieldType.Relation getRelation(final CoordinatorRewriteContext coordinatorRewriteContext) {
439439
final MappedFieldType fieldType = coordinatorRewriteContext.getFieldType(fieldName);
440440
if (fieldType instanceof final DateFieldMapper.DateFieldType dateFieldType) {
441+
assert fieldName.equals(fieldType.name());
441442
IndexLongFieldRange fieldRange = coordinatorRewriteContext.getFieldRange(fieldName);
442443
if (fieldRange.isComplete() == false || fieldRange == IndexLongFieldRange.EMPTY) {
443444
// if not all shards for this (frozen) index have reported ranges to cluster state, OR if they

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2258,8 +2258,8 @@ private ShardLongFieldRange determineShardLongFieldRange(String fieldName) {
22582258
return ShardLongFieldRange.UNKNOWN; // no mapper service, no idea if the field even exists
22592259
}
22602260
final MappedFieldType mappedFieldType = mapperService().fieldType(fieldName);
2261-
if (mappedFieldType instanceof DateFieldMapper.DateFieldType == false) {
2262-
return ShardLongFieldRange.UNKNOWN; // field missing or not a date
2261+
if (mappedFieldType instanceof DateFieldMapper.DateFieldType == false || mappedFieldType.name().equals(fieldName) == false) {
2262+
return ShardLongFieldRange.UNKNOWN; // field is missing, an alias (as the field type has a different name) or not a date field
22632263
}
22642264
if (mappedFieldType.isIndexed() == false) {
22652265
return ShardLongFieldRange.UNKNOWN; // range information missing

server/src/main/java/org/elasticsearch/indices/TimestampFieldMapperService.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,13 @@ private static DateFieldRangeInfo fromMapperService(MapperService mapperService)
166166
DateFieldMapper.DateFieldType eventIngestedFieldType = null;
167167

168168
MappedFieldType mappedFieldType = mapperService.fieldType(DataStream.TIMESTAMP_FIELD_NAME);
169-
if (mappedFieldType instanceof DateFieldMapper.DateFieldType dateFieldType) {
169+
if (mappedFieldType instanceof DateFieldMapper.DateFieldType dateFieldType
170+
&& dateFieldType.name().equals(DataStream.TIMESTAMP_FIELD_NAME)) {
170171
timestampFieldType = dateFieldType;
171172
}
172173
mappedFieldType = mapperService.fieldType(IndexMetadata.EVENT_INGESTED_FIELD_NAME);
173-
if (mappedFieldType instanceof DateFieldMapper.DateFieldType dateFieldType) {
174+
if (mappedFieldType instanceof DateFieldMapper.DateFieldType dateFieldType
175+
&& dateFieldType.name().equals(IndexMetadata.EVENT_INGESTED_FIELD_NAME)) {
174176
eventIngestedFieldType = dateFieldType;
175177
}
176178
if (timestampFieldType == null && eventIngestedFieldType == null) {

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

Lines changed: 118 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.elasticsearch.action.search.SearchShardsGroup;
1616
import org.elasticsearch.action.search.SearchShardsRequest;
1717
import org.elasticsearch.action.search.SearchShardsResponse;
18+
import org.elasticsearch.action.search.SearchType;
1819
import org.elasticsearch.action.search.TransportSearchShardsAction;
1920
import org.elasticsearch.blobcache.shared.SharedBlobCacheService;
2021
import org.elasticsearch.cluster.metadata.DataStream;
@@ -1100,6 +1101,119 @@ public void testCanMatchSkipsPartiallyMountedIndicesWhenFrozenNodesUnavailable()
11001101
}
11011102
}
11021103

1104+
public void testTimestampAsAlias() throws Exception {
1105+
doTestCoordRewriteWithAliasField("@timestamp");
1106+
}
1107+
1108+
public void testEventIngestedAsAlias() throws Exception {
1109+
doTestCoordRewriteWithAliasField("event.ingested");
1110+
}
1111+
1112+
private void doTestCoordRewriteWithAliasField(String aliasFieldName) throws Exception {
1113+
internalCluster().startMasterOnlyNode();
1114+
internalCluster().startCoordinatingOnlyNode(Settings.EMPTY);
1115+
final String dataNodeHoldingRegularIndex = internalCluster().startDataOnlyNode();
1116+
final String dataNodeHoldingSearchableSnapshot = internalCluster().startDataOnlyNode();
1117+
1118+
String timestampFieldName = randomAlphaOfLengthBetween(3, 10);
1119+
String[] indices = new String[] { "index-0001", "index-0002" };
1120+
for (String index : indices) {
1121+
Settings extraSettings = Settings.builder()
1122+
.put(INDEX_ROUTING_REQUIRE_GROUP_SETTING.getConcreteSettingForNamespace("_name").getKey(), dataNodeHoldingRegularIndex)
1123+
.build();
1124+
1125+
assertAcked(
1126+
indicesAdmin().prepareCreate(index)
1127+
.setMapping(
1128+
XContentFactory.jsonBuilder()
1129+
.startObject()
1130+
.startObject("properties")
1131+
1132+
.startObject(timestampFieldName)
1133+
.field("type", "date")
1134+
.endObject()
1135+
1136+
.startObject(aliasFieldName)
1137+
.field("type", "alias")
1138+
.field("path", timestampFieldName)
1139+
.endObject()
1140+
1141+
.endObject()
1142+
.endObject()
1143+
)
1144+
.setSettings(indexSettingsNoReplicas(1).put(INDEX_SOFT_DELETES_SETTING.getKey(), true).put(extraSettings))
1145+
);
1146+
}
1147+
ensureGreen(indices);
1148+
1149+
for (String index : indices) {
1150+
final List<IndexRequestBuilder> indexRequestBuilders = new ArrayList<>();
1151+
for (int i = 0; i < 10; i++) {
1152+
indexRequestBuilders.add(prepareIndex(index).setSource(timestampFieldName, "2024-11-19T08:08:08Z"));
1153+
}
1154+
indexRandom(true, false, indexRequestBuilders);
1155+
1156+
assertThat(
1157+
indicesAdmin().prepareForceMerge(index).setOnlyExpungeDeletes(true).setFlush(true).get().getFailedShards(),
1158+
equalTo(0)
1159+
);
1160+
refresh(index);
1161+
forceMerge();
1162+
}
1163+
1164+
final String repositoryName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT);
1165+
createRepository(repositoryName, "mock");
1166+
1167+
final SnapshotId snapshotId = createSnapshot(repositoryName, "snapshot-1", List.of(indices[0])).snapshotId();
1168+
assertAcked(indicesAdmin().prepareDelete(indices[0]));
1169+
1170+
// Block the repository for the node holding the searchable snapshot shards
1171+
// to delay its restore
1172+
blockDataNode(repositoryName, dataNodeHoldingSearchableSnapshot);
1173+
1174+
// Force the searchable snapshot to be allocated in a particular node
1175+
Settings restoredIndexSettings = Settings.builder()
1176+
.put(INDEX_ROUTING_REQUIRE_GROUP_SETTING.getConcreteSettingForNamespace("_name").getKey(), dataNodeHoldingSearchableSnapshot)
1177+
.build();
1178+
1179+
String mountedIndex = indices[0] + "-mounted";
1180+
final MountSearchableSnapshotRequest mountRequest = new MountSearchableSnapshotRequest(
1181+
TEST_REQUEST_TIMEOUT,
1182+
mountedIndex,
1183+
repositoryName,
1184+
snapshotId.getName(),
1185+
indices[0],
1186+
restoredIndexSettings,
1187+
Strings.EMPTY_ARRAY,
1188+
false,
1189+
randomFrom(MountSearchableSnapshotRequest.Storage.values())
1190+
);
1191+
client().execute(MountSearchableSnapshotAction.INSTANCE, mountRequest).actionGet();
1192+
1193+
// Allow the searchable snapshots to be finally mounted
1194+
unblockNode(repositoryName, dataNodeHoldingSearchableSnapshot);
1195+
waitUntilRecoveryIsDone(mountedIndex);
1196+
ensureGreen(mountedIndex);
1197+
1198+
String[] fieldsToQuery = new String[] { timestampFieldName, aliasFieldName };
1199+
for (String fieldName : fieldsToQuery) {
1200+
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(fieldName).from("2024-11-01T00:00:00.000000000Z", true);
1201+
SearchRequest request = new SearchRequest().searchType(SearchType.QUERY_THEN_FETCH)
1202+
.source(new SearchSourceBuilder().query(rangeQuery));
1203+
if (randomBoolean()) {
1204+
// pre_filter_shard_size default to 1 because there are read-only indices in the mix. It does not hurt to force it though.
1205+
request.setPreFilterShardSize(1);
1206+
}
1207+
assertResponse(client().search(request), searchResponse -> {
1208+
assertThat(searchResponse.getSuccessfulShards(), equalTo(2));
1209+
assertThat(searchResponse.getFailedShards(), equalTo(0));
1210+
assertThat(searchResponse.getSkippedShards(), equalTo(0));
1211+
assertThat(searchResponse.getTotalShards(), equalTo(2));
1212+
assertThat(searchResponse.getHits().getTotalHits().value, equalTo(20L));
1213+
});
1214+
}
1215+
}
1216+
11031217
private void createIndexWithTimestampAndEventIngested(String indexName, int numShards, Settings extraSettings) throws IOException {
11041218
assertAcked(
11051219
indicesAdmin().prepareCreate(indexName)
@@ -1148,8 +1262,7 @@ private void createIndexWithOnlyOneTimestampField(String timestampField, String
11481262
ensureGreen(index);
11491263
}
11501264

1151-
private void indexDocumentsWithOnlyOneTimestampField(String timestampField, String index, int docCount, String timestampTemplate)
1152-
throws Exception {
1265+
private void indexDocumentsWithOnlyOneTimestampField(String timestampField, String index, int docCount, String timestampTemplate) {
11531266
final List<IndexRequestBuilder> indexRequestBuilders = new ArrayList<>();
11541267
for (int i = 0; i < docCount; i++) {
11551268
indexRequestBuilders.add(
@@ -1173,8 +1286,7 @@ private void indexDocumentsWithOnlyOneTimestampField(String timestampField, Stri
11731286
forceMerge();
11741287
}
11751288

1176-
private void indexDocumentsWithTimestampAndEventIngestedDates(String indexName, int docCount, String timestampTemplate)
1177-
throws Exception {
1289+
private void indexDocumentsWithTimestampAndEventIngestedDates(String indexName, int docCount, String timestampTemplate) {
11781290

11791291
final List<IndexRequestBuilder> indexRequestBuilders = new ArrayList<>();
11801292
for (int i = 0; i < docCount; i++) {
@@ -1211,7 +1323,7 @@ private void indexDocumentsWithTimestampAndEventIngestedDates(String indexName,
12111323
forceMerge();
12121324
}
12131325

1214-
private IndexMetadata getIndexMetadata(String indexName) {
1326+
private static IndexMetadata getIndexMetadata(String indexName) {
12151327
return clusterAdmin().prepareState(TEST_REQUEST_TIMEOUT)
12161328
.clear()
12171329
.setMetadata(true)
@@ -1222,7 +1334,7 @@ private IndexMetadata getIndexMetadata(String indexName) {
12221334
.index(indexName);
12231335
}
12241336

1225-
private void waitUntilRecoveryIsDone(String index) throws Exception {
1337+
private static void waitUntilRecoveryIsDone(String index) throws Exception {
12261338
assertBusy(() -> {
12271339
RecoveryResponse recoveryResponse = indicesAdmin().prepareRecoveries(index).get();
12281340
assertThat(recoveryResponse.hasRecoveries(), equalTo(true));

0 commit comments

Comments
 (0)