Skip to content

Commit 48fea49

Browse files
authored
Assert the stability of custom search preferences (#41150)
Today the `?preference=custom_string_value` search preference will only change its choice of a shard copy if something changes the `IndexShardRoutingTable` for that specific shard. Users can use this behaviour to route searches to a consistent set of shard copies, which means they can reliably hit copies with hot caches, and use the other copies only for redundancy in case of failure. However we do not assert this property anywhere, so we might break it in future. This commit adds a test that shows that searches are routed consistently even if other indices are created/rebalanced/deleted. Relates https://discuss.elastic.co/t/176598, #41115, #26791
1 parent 05c1476 commit 48fea49

File tree

1 file changed

+64
-7
lines changed

1 file changed

+64
-7
lines changed

server/src/test/java/org/elasticsearch/search/preference/SearchPreferenceIT.java

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,13 @@
2525
import org.elasticsearch.action.search.SearchResponse;
2626
import org.elasticsearch.client.Client;
2727
import org.elasticsearch.cluster.health.ClusterHealthStatus;
28+
import org.elasticsearch.cluster.metadata.IndexMetaData;
2829
import org.elasticsearch.cluster.routing.OperationRouting;
30+
import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider;
2931
import org.elasticsearch.common.Strings;
3032
import org.elasticsearch.common.settings.Settings;
3133
import org.elasticsearch.common.xcontent.XContentType;
34+
import org.elasticsearch.node.Node;
3235
import org.elasticsearch.rest.RestStatus;
3336
import org.elasticsearch.test.ESIntegTestCase;
3437

@@ -42,10 +45,10 @@
4245
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
4346
import static org.hamcrest.CoreMatchers.containsString;
4447
import static org.hamcrest.Matchers.equalTo;
45-
import static org.hamcrest.Matchers.hasToString;
46-
import static org.hamcrest.Matchers.not;
4748
import static org.hamcrest.Matchers.greaterThan;
4849
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
50+
import static org.hamcrest.Matchers.hasToString;
51+
import static org.hamcrest.Matchers.not;
4952

5053
@ESIntegTestCase.ClusterScope(minNumDataNodes = 2)
5154
public class SearchPreferenceIT extends ESIntegTestCase {
@@ -57,7 +60,7 @@ public Settings nodeSettings(int nodeOrdinal) {
5760
}
5861

5962
// see #2896
60-
public void testStopOneNodePreferenceWithRedState() throws InterruptedException, IOException {
63+
public void testStopOneNodePreferenceWithRedState() throws IOException {
6164
assertAcked(prepareCreate("test").setSettings(Settings.builder().put("index.number_of_shards", cluster().numDataNodes()+2)
6265
.put("index.number_of_replicas", 0)));
6366
ensureGreen();
@@ -87,7 +90,7 @@ public void testStopOneNodePreferenceWithRedState() throws InterruptedException,
8790
assertThat("_only_local", searchResponse.getFailedShards(), greaterThanOrEqualTo(0));
8891
}
8992

90-
public void testNoPreferenceRandom() throws Exception {
93+
public void testNoPreferenceRandom() {
9194
assertAcked(prepareCreate("test").setSettings(
9295
//this test needs at least a replica to make sure two consecutive searches go to two different copies of the same data
9396
Settings.builder().put(indexSettings()).put(SETTING_NUMBER_OF_REPLICAS, between(1, maximumNumberOfReplicas()))
@@ -106,7 +109,7 @@ public void testNoPreferenceRandom() throws Exception {
106109
assertThat(firstNodeId, not(equalTo(secondNodeId)));
107110
}
108111

109-
public void testSimplePreference() throws Exception {
112+
public void testSimplePreference() {
110113
client().admin().indices().prepareCreate("test").setSettings("{\"number_of_replicas\": 1}", XContentType.JSON).get();
111114
ensureGreen();
112115

@@ -123,7 +126,7 @@ public void testSimplePreference() throws Exception {
123126
assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L));
124127
}
125128

126-
public void testThatSpecifyingNonExistingNodesReturnsUsefulError() throws Exception {
129+
public void testThatSpecifyingNonExistingNodesReturnsUsefulError() {
127130
createIndex("test");
128131
ensureGreen();
129132

@@ -135,7 +138,7 @@ public void testThatSpecifyingNonExistingNodesReturnsUsefulError() throws Except
135138
}
136139
}
137140

138-
public void testNodesOnlyRandom() throws Exception {
141+
public void testNodesOnlyRandom() {
139142
assertAcked(prepareCreate("test").setSettings(
140143
//this test needs at least a replica to make sure two consecutive searches go to two different copies of the same data
141144
Settings.builder().put(indexSettings()).put(SETTING_NUMBER_OF_REPLICAS, between(1, maximumNumberOfReplicas()))));
@@ -193,4 +196,58 @@ private void assertSearchOnRandomNodes(SearchRequestBuilder request) {
193196
}
194197
assertThat(hitNodes.size(), greaterThan(1));
195198
}
199+
200+
public void testCustomPreferenceUnaffectedByOtherShardMovements() {
201+
202+
/*
203+
* Custom preferences can be used to encourage searches to go to a consistent set of shard copies, meaning that other copies' data
204+
* is rarely touched and can be dropped from the filesystem cache. This works best if the set of shards searched doesn't change
205+
* unnecessarily, so this test verifies a consistent routing even as other shards are created/relocated/removed.
206+
*/
207+
208+
assertAcked(prepareCreate("test").setSettings(Settings.builder().put(indexSettings())
209+
.put(SETTING_NUMBER_OF_REPLICAS, between(1, maximumNumberOfReplicas()))
210+
.put(EnableAllocationDecider.INDEX_ROUTING_REBALANCE_ENABLE_SETTING.getKey(), EnableAllocationDecider.Rebalance.NONE)));
211+
ensureGreen();
212+
client().prepareIndex("test", "_doc").setSource("field1", "value1").get();
213+
refresh();
214+
215+
final String customPreference = randomAlphaOfLength(10);
216+
217+
final String nodeId = client().prepareSearch("test").setQuery(matchAllQuery()).setPreference(customPreference)
218+
.get().getHits().getAt(0).getShard().getNodeId();
219+
220+
assertSearchesSpecificNode("test", customPreference, nodeId);
221+
222+
final int replicasInNewIndex = between(1, maximumNumberOfReplicas());
223+
assertAcked(prepareCreate("test2").setSettings(
224+
Settings.builder().put(indexSettings()).put(SETTING_NUMBER_OF_REPLICAS, replicasInNewIndex)));
225+
ensureGreen();
226+
227+
assertSearchesSpecificNode("test", customPreference, nodeId);
228+
229+
assertAcked(client().admin().indices().prepareUpdateSettings("test2").setSettings(Settings.builder()
230+
.put(SETTING_NUMBER_OF_REPLICAS, replicasInNewIndex - 1)));
231+
232+
assertSearchesSpecificNode("test", customPreference, nodeId);
233+
234+
assertAcked(client().admin().indices().prepareUpdateSettings("test2").setSettings(Settings.builder()
235+
.put(SETTING_NUMBER_OF_REPLICAS, 0)
236+
.put(IndexMetaData.INDEX_ROUTING_REQUIRE_GROUP_PREFIX + "._name",
237+
internalCluster().getDataNodeInstance(Node.class).settings().get(Node.NODE_NAME_SETTING.getKey()))));
238+
239+
ensureGreen();
240+
241+
assertSearchesSpecificNode("test", customPreference, nodeId);
242+
243+
assertAcked(client().admin().indices().prepareDelete("test2"));
244+
245+
assertSearchesSpecificNode("test", customPreference, nodeId);
246+
}
247+
248+
private static void assertSearchesSpecificNode(String index, String customPreference, String nodeId) {
249+
final SearchResponse searchResponse = client().prepareSearch(index).setQuery(matchAllQuery()).setPreference(customPreference).get();
250+
assertThat(searchResponse.getHits().getHits().length, equalTo(1));
251+
assertThat(searchResponse.getHits().getAt(0).getShard().getNodeId(), equalTo(nodeId));
252+
}
196253
}

0 commit comments

Comments
 (0)