Skip to content

Commit

Permalink
Introduce IndexRoutingTable#readyForSearch (elastic#99890)
Browse files Browse the repository at this point in the history
This commit introduces IndexRoutingTable#readyForSearch to determine if a given index is available to for search.
  • Loading branch information
jakelandis authored Oct 3, 2023
1 parent 8aa6964 commit cbe3083
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

package org.elasticsearch.cluster.routing;

import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.Diff;
import org.elasticsearch.cluster.SimpleDiffable;
import org.elasticsearch.cluster.metadata.IndexMetadata;
Expand Down Expand Up @@ -231,6 +232,26 @@ public boolean allPrimaryShardsActive() {
return primaryShardsActive() == shards.length;
}

/**
* @return <code>true</code> if an index is available to service search queries.
*/
public boolean readyForSearch(ClusterState clusterState) {
for (IndexShardRoutingTable shardRoutingTable : this.shards) {
boolean found = false;
for (int idx = 0; idx < shardRoutingTable.size(); idx++) {
ShardRouting shardRouting = shardRoutingTable.shard(idx);
if (shardRouting.active() && OperationRouting.canSearchShard(shardRouting, clusterState)) {
found = true;
break;
}
}
if (found == false) {
return false;
}
}
return true;
}

public boolean allShardsActive() {
return this.allShardsActive;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import org.elasticsearch.TransportVersion;
import org.elasticsearch.TransportVersions;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.routing.RecoverySource.ExistingStoreRecoverySource;
import org.elasticsearch.cluster.routing.RecoverySource.PeerRecoverySource;
Expand Down Expand Up @@ -930,6 +931,11 @@ public boolean isPromotableToPrimary() {
return role.isPromotableToPrimary();
}

/**
* Determine if role searchable. Consumers should prefer {@link OperationRouting#canSearchShard(ShardRouting, ClusterState)} to
* determine if a shard can be searched and {@link IndexRoutingTable#readyForSearch(ClusterState)} to determine if an index
* is ready to be searched.
*/
public boolean isSearchable() {
return role.isSearchable();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

package org.elasticsearch.cluster.routing;

import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.test.ESTestCase;
import org.mockito.Mockito;

import java.util.List;

import static org.elasticsearch.index.IndexSettings.INDEX_FAST_REFRESH_SETTING;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class IndexRoutingTableTests extends ESTestCase {

public void testReadyForSearch() {
innerReadyForSearch(false);
innerReadyForSearch(true);
}

private void innerReadyForSearch(boolean fastRefresh) {
Index index = new Index(randomIdentifier(), UUIDs.randomBase64UUID());
ClusterState clusterState = mock(ClusterState.class, Mockito.RETURNS_DEEP_STUBS);
when(clusterState.metadata().index(any(Index.class)).getSettings()).thenReturn(
Settings.builder().put(INDEX_FAST_REFRESH_SETTING.getKey(), fastRefresh).build()
);
// 2 primaries that are search and index
ShardId p1 = new ShardId(index, 0);
IndexShardRoutingTable shardTable1 = new IndexShardRoutingTable(
p1,
List.of(getShard(p1, true, ShardRoutingState.STARTED, ShardRouting.Role.DEFAULT))
);
ShardId p2 = new ShardId(index, 1);
IndexShardRoutingTable shardTable2 = new IndexShardRoutingTable(
p2,
List.of(getShard(p2, true, ShardRoutingState.STARTED, ShardRouting.Role.DEFAULT))
);
IndexRoutingTable indexRoutingTable = new IndexRoutingTable(index, new IndexShardRoutingTable[] { shardTable1, shardTable2 });
assertTrue(indexRoutingTable.readyForSearch(clusterState));

// 2 primaries that are index only
shardTable1 = new IndexShardRoutingTable(p1, List.of(getShard(p1, true, ShardRoutingState.STARTED, ShardRouting.Role.INDEX_ONLY)));
shardTable2 = new IndexShardRoutingTable(p2, List.of(getShard(p2, true, ShardRoutingState.STARTED, ShardRouting.Role.INDEX_ONLY)));
indexRoutingTable = new IndexRoutingTable(index, new IndexShardRoutingTable[] { shardTable1, shardTable2 });
if (fastRefresh) {
assertTrue(indexRoutingTable.readyForSearch(clusterState));
} else {
assertFalse(indexRoutingTable.readyForSearch(clusterState));
}

// 2 unassigned primaries that are index only
shardTable1 = new IndexShardRoutingTable(
p1,
List.of(getShard(p1, true, ShardRoutingState.UNASSIGNED, ShardRouting.Role.INDEX_ONLY))
);
shardTable2 = new IndexShardRoutingTable(
p2,
List.of(getShard(p2, true, ShardRoutingState.UNASSIGNED, ShardRouting.Role.INDEX_ONLY))
);
indexRoutingTable = new IndexRoutingTable(index, new IndexShardRoutingTable[] { shardTable1, shardTable2 });
assertFalse(indexRoutingTable.readyForSearch(clusterState));

// 2 primaries that are index only with replicas that are not all available
shardTable1 = new IndexShardRoutingTable(
p1,
List.of(
getShard(p1, true, ShardRoutingState.STARTED, ShardRouting.Role.INDEX_ONLY),
getShard(p1, false, ShardRoutingState.STARTED, ShardRouting.Role.SEARCH_ONLY),
getShard(p1, false, ShardRoutingState.STARTED, ShardRouting.Role.SEARCH_ONLY)
)
);
shardTable2 = new IndexShardRoutingTable(
p2,
List.of(
getShard(p2, true, ShardRoutingState.STARTED, ShardRouting.Role.INDEX_ONLY),
getShard(p2, false, ShardRoutingState.UNASSIGNED, ShardRouting.Role.SEARCH_ONLY),
getShard(p2, false, ShardRoutingState.UNASSIGNED, ShardRouting.Role.SEARCH_ONLY)
)
);
indexRoutingTable = new IndexRoutingTable(index, new IndexShardRoutingTable[] { shardTable1, shardTable2 });
if (fastRefresh) {
assertTrue(indexRoutingTable.readyForSearch(clusterState));
} else {
assertFalse(indexRoutingTable.readyForSearch(clusterState));
}

// 2 primaries that are index only with some replicas that are all available
shardTable1 = new IndexShardRoutingTable(
p1,
List.of(
getShard(p1, true, ShardRoutingState.STARTED, ShardRouting.Role.INDEX_ONLY),
getShard(p1, false, ShardRoutingState.STARTED, ShardRouting.Role.SEARCH_ONLY),
getShard(p1, false, ShardRoutingState.STARTED, ShardRouting.Role.SEARCH_ONLY)
)
);
shardTable2 = new IndexShardRoutingTable(
p2,
List.of(
getShard(p2, true, ShardRoutingState.STARTED, ShardRouting.Role.INDEX_ONLY),
getShard(p2, false, ShardRoutingState.STARTED, ShardRouting.Role.SEARCH_ONLY),
getShard(p2, false, ShardRoutingState.STARTED, ShardRouting.Role.SEARCH_ONLY)
)
);
indexRoutingTable = new IndexRoutingTable(index, new IndexShardRoutingTable[] { shardTable1, shardTable2 });
assertTrue(indexRoutingTable.readyForSearch(clusterState));

// 2 unassigned primaries that are index only with some replicas that are all available
// Fast refresh indices do not support replicas so this can not practically happen. If we add support we will want to ensure
// that readyForSearch allows for searching replicas when the index shard is not available.
shardTable1 = new IndexShardRoutingTable(
p1,
List.of(
getShard(p1, true, ShardRoutingState.UNASSIGNED, ShardRouting.Role.INDEX_ONLY),
getShard(p1, false, ShardRoutingState.STARTED, ShardRouting.Role.SEARCH_ONLY),
getShard(p1, false, ShardRoutingState.STARTED, ShardRouting.Role.SEARCH_ONLY)
)
);
shardTable2 = new IndexShardRoutingTable(
p2,
List.of(
getShard(p2, true, ShardRoutingState.UNASSIGNED, ShardRouting.Role.INDEX_ONLY),
getShard(p2, false, ShardRoutingState.STARTED, ShardRouting.Role.SEARCH_ONLY),
getShard(p2, false, ShardRoutingState.STARTED, ShardRouting.Role.SEARCH_ONLY)
)
);
indexRoutingTable = new IndexRoutingTable(index, new IndexShardRoutingTable[] { shardTable1, shardTable2 });
if (fastRefresh) {
assertFalse(indexRoutingTable.readyForSearch(clusterState)); // if we support replicas for fast refreshes this needs to change
} else {
assertTrue(indexRoutingTable.readyForSearch(clusterState));
}

// 2 primaries that are index only with at least 1 replica per primary that is available
shardTable1 = new IndexShardRoutingTable(
p1,
List.of(
getShard(p1, true, ShardRoutingState.STARTED, ShardRouting.Role.INDEX_ONLY),
getShard(p1, false, ShardRoutingState.STARTED, ShardRouting.Role.SEARCH_ONLY),
getShard(p1, false, ShardRoutingState.UNASSIGNED, ShardRouting.Role.SEARCH_ONLY)
)
);
shardTable2 = new IndexShardRoutingTable(
p2,
List.of(
getShard(p2, true, ShardRoutingState.STARTED, ShardRouting.Role.INDEX_ONLY),
getShard(p2, false, ShardRoutingState.UNASSIGNED, ShardRouting.Role.SEARCH_ONLY),
getShard(p2, false, ShardRoutingState.STARTED, ShardRouting.Role.SEARCH_ONLY)
)
);
indexRoutingTable = new IndexRoutingTable(index, new IndexShardRoutingTable[] { shardTable1, shardTable2 });
assertTrue(indexRoutingTable.readyForSearch(clusterState));
}

private ShardRouting getShard(ShardId shardId, boolean isPrimary, ShardRoutingState state, ShardRouting.Role role) {
return new ShardRouting(
shardId,
state == ShardRoutingState.UNASSIGNED ? null : randomIdentifier(),
state == ShardRoutingState.UNASSIGNED || state == ShardRoutingState.STARTED ? null : randomIdentifier(),
isPrimary,
state,
TestShardRouting.buildRecoveryTarget(isPrimary, state),
TestShardRouting.buildUnassignedInfo(state),
TestShardRouting.buildRelocationFailureInfo(state),
TestShardRouting.buildAllocationId(state),
randomLongBetween(-1, 1024),
role
);
}

}

0 comments on commit cbe3083

Please sign in to comment.