Add search preference to prefer multiple nodes

The search preference _prefer_node allows specifying a single node to
prefer when routing a request. This functionality can be enhanced by
permitting multiple nodes to be preferred. This commit replaces the
search preference _prefer_node with the search preference _prefer_nodes
which supplants the former by specifying a single node and otherwise
adds functionality.

Relates #18872
This commit is contained in:
Jason Tedor 2016-06-14 21:34:24 -04:00 committed by GitHub
parent d0e4485d42
commit e96722d91c
8 changed files with 107 additions and 28 deletions

View File

@ -375,21 +375,22 @@ public class IndexShardRoutingTable implements Iterable<ShardRouting> {
return new PlainShardIterator(shardId, ordered); return new PlainShardIterator(shardId, ordered);
} }
public ShardIterator preferNodeActiveInitializingShardsIt(String nodeId) { public ShardIterator preferNodeActiveInitializingShardsIt(Set<String> nodeIds) {
ArrayList<ShardRouting> ordered = new ArrayList<>(activeShards.size() + allInitializingShards.size()); ArrayList<ShardRouting> preferred = new ArrayList<>(activeShards.size() + allInitializingShards.size());
ArrayList<ShardRouting> notPreferred = new ArrayList<>(activeShards.size() + allInitializingShards.size());
// fill it in a randomized fashion // fill it in a randomized fashion
for (ShardRouting shardRouting : shuffler.shuffle(activeShards)) { for (ShardRouting shardRouting : shuffler.shuffle(activeShards)) {
ordered.add(shardRouting); if (nodeIds.contains(shardRouting.currentNodeId())) {
if (nodeId.equals(shardRouting.currentNodeId())) { preferred.add(shardRouting);
// switch, its the matching node id } else {
ordered.set(ordered.size() - 1, ordered.get(0)); notPreferred.add(shardRouting);
ordered.set(0, shardRouting);
} }
} }
preferred.addAll(notPreferred);
if (!allInitializingShards.isEmpty()) { if (!allInitializingShards.isEmpty()) {
ordered.addAll(allInitializingShards); preferred.addAll(allInitializingShards);
} }
return new PlainShardIterator(shardId, ordered); return new PlainShardIterator(shardId, preferred);
} }
@Override @Override

View File

@ -33,17 +33,15 @@ import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.index.shard.ShardNotFoundException; import org.elasticsearch.index.shard.ShardNotFoundException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
/**
*
*/
public class OperationRouting extends AbstractComponent { public class OperationRouting extends AbstractComponent {
private final AwarenessAllocationDecider awarenessAllocationDecider; private final AwarenessAllocationDecider awarenessAllocationDecider;
@Inject @Inject
@ -158,10 +156,14 @@ public class OperationRouting extends AbstractComponent {
} }
preferenceType = Preference.parse(preference); preferenceType = Preference.parse(preference);
switch (preferenceType) { switch (preferenceType) {
case PREFER_NODE: case PREFER_NODES:
return indexShard.preferNodeActiveInitializingShardsIt(preference.substring(Preference.PREFER_NODE.type().length() + 1)); final Set<String> nodesIds =
Arrays.stream(
preference.substring(Preference.PREFER_NODES.type().length() + 1).split(",")
).collect(Collectors.toSet());
return indexShard.preferNodeActiveInitializingShardsIt(nodesIds);
case LOCAL: case LOCAL:
return indexShard.preferNodeActiveInitializingShardsIt(localNodeId); return indexShard.preferNodeActiveInitializingShardsIt(Collections.singleton(localNodeId));
case PRIMARY: case PRIMARY:
return indexShard.primaryActiveInitializingShardIt(); return indexShard.primaryActiveInitializingShardIt();
case REPLICA: case REPLICA:

View File

@ -30,9 +30,9 @@ public enum Preference {
SHARDS("_shards"), SHARDS("_shards"),
/** /**
* Route to preferred node, if possible * Route to preferred nodes, if possible
*/ */
PREFER_NODE("_prefer_node"), PREFER_NODES("_prefer_nodes"),
/** /**
* Route to local node, if possible * Route to local node, if possible
@ -98,8 +98,8 @@ public enum Preference {
switch (preferenceType) { switch (preferenceType) {
case "_shards": case "_shards":
return SHARDS; return SHARDS;
case "_prefer_node": case "_prefer_nodes":
return PREFER_NODE; return PREFER_NODES;
case "_only_node": case "_only_node":
return ONLY_NODE; return ONLY_NODE;
case "_local": case "_local":
@ -123,6 +123,7 @@ public enum Preference {
throw new IllegalArgumentException("no Preference for [" + preferenceType + "]"); throw new IllegalArgumentException("no Preference for [" + preferenceType + "]");
} }
} }
} }

View File

@ -18,16 +18,31 @@
*/ */
package org.elasticsearch.cluster.routing; package org.elasticsearch.cluster.routing;
import org.apache.lucene.util.IOUtils;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.action.support.replication.ClusterStateCreationUtils;
import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.routing.allocation.decider.AwarenessAllocationDecider;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.test.ClusterServiceUtils;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.TestThreadPool;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
public class OperationRoutingTests extends ESTestCase{ public class OperationRoutingTests extends ESTestCase{
public void testGenerateShardId() { public void testGenerateShardId() {
@ -170,4 +185,48 @@ public class OperationRoutingTests extends ESTestCase{
assertEquals(shard, entry.getValue().intValue()); assertEquals(shard, entry.getValue().intValue());
} }
} }
public void testPreferNodes() throws InterruptedException, IOException {
TestThreadPool threadPool = null;
ClusterService clusterService = null;
try {
threadPool = new TestThreadPool("testPreferNodes");
clusterService = ClusterServiceUtils.createClusterService(threadPool);
final String indexName = "test";
ClusterServiceUtils.setState(clusterService, ClusterStateCreationUtils.stateWithActivePrimary(indexName, true, randomInt(8)));
final Index index = clusterService.state().metaData().index(indexName).getIndex();
final List<ShardRouting> shards = clusterService.state().getRoutingNodes().assignedShards(new ShardId(index, 0));
final int count = randomIntBetween(1, shards.size());
int position = 0;
final List<String> nodes = new ArrayList<>();
final List<ShardRouting> expected = new ArrayList<>();
for (int i = 0; i < count; i++) {
if (randomBoolean() && !shards.get(position).initializing()) {
nodes.add(shards.get(position).currentNodeId());
expected.add(shards.get(position));
position++;
} else {
nodes.add("missing_" + i);
}
}
final ShardIterator it =
new OperationRouting(Settings.EMPTY, new AwarenessAllocationDecider())
.getShards(clusterService.state(), indexName, 0, "_prefer_nodes:" + String.join(",", nodes));
final List<ShardRouting> all = new ArrayList<>();
ShardRouting shard;
while ((shard = it.nextOrNull()) != null) {
all.add(shard);
}
final Set<ShardRouting> preferred = new HashSet<>();
preferred.addAll(all.subList(0, expected.size()));
// the preferred shards should be at the front of the list
assertThat(preferred, containsInAnyOrder(expected.toArray()));
// verify all the shards are there
assertThat(all.size(), equalTo(shards.size()));
} finally {
IOUtils.close(clusterService);
terminate(threadPool);
}
}
} }

View File

@ -43,6 +43,7 @@ import org.elasticsearch.test.ESAllocationTestCase;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import static java.util.Collections.singletonMap; import static java.util.Collections.singletonMap;
@ -401,15 +402,23 @@ public class RoutingIteratorTests extends ESAllocationTestCase {
assertThat(shardIterators.iterator().next().shardId().id(), equalTo(0)); assertThat(shardIterators.iterator().next().shardId().id(), equalTo(0));
assertThat(shardIterators.iterator().next().nextOrNull().currentNodeId(), not(equalTo(firstRoundNodeId))); assertThat(shardIterators.iterator().next().nextOrNull().currentNodeId(), not(equalTo(firstRoundNodeId)));
shardIterators = operationRouting.searchShards(clusterState, new String[]{"test"}, null, "_shards:0;_prefer_node:node1"); shardIterators = operationRouting.searchShards(clusterState, new String[]{"test"}, null, "_shards:0;_prefer_nodes:node1");
assertThat(shardIterators.size(), equalTo(1)); assertThat(shardIterators.size(), equalTo(1));
assertThat(shardIterators.iterator().next().shardId().id(), equalTo(0)); assertThat(shardIterators.iterator().next().shardId().id(), equalTo(0));
assertThat(shardIterators.iterator().next().nextOrNull().currentNodeId(), equalTo("node1")); assertThat(shardIterators.iterator().next().nextOrNull().currentNodeId(), equalTo("node1"));
shardIterators = operationRouting.searchShards(clusterState, new String[]{"test"}, null, "_shards:0;_prefer_node:node1"); shardIterators = operationRouting.searchShards(clusterState, new String[]{"test"}, null, "_shards:0;_prefer_nodes:node1,node2");
assertThat(shardIterators.size(), equalTo(1)); assertThat(shardIterators.size(), equalTo(1));
assertThat(shardIterators.iterator().next().shardId().id(), equalTo(0)); Iterator<ShardIterator> iterator = shardIterators.iterator();
assertThat(shardIterators.iterator().next().nextOrNull().currentNodeId(), equalTo("node1")); final ShardIterator it = iterator.next();
assertThat(it.shardId().id(), equalTo(0));
final String firstNodeId = it.nextOrNull().currentNodeId();
assertThat(firstNodeId, anyOf(equalTo("node1"), equalTo("node2")));
if ("node1".equals(firstNodeId)) {
assertThat(it.nextOrNull().currentNodeId(), equalTo("node2"));
} else {
assertThat(it.nextOrNull().currentNodeId(), equalTo("node1"));
}
} }
public void testReplicaShardPreferenceIters() throws Exception { public void testReplicaShardPreferenceIters() throws Exception {

View File

@ -56,7 +56,7 @@ public class SearchPreferenceIT extends ESIntegTestCase {
refresh(); refresh();
internalCluster().stopRandomDataNode(); internalCluster().stopRandomDataNode();
client().admin().cluster().prepareHealth().setWaitForStatus(ClusterHealthStatus.RED).execute().actionGet(); client().admin().cluster().prepareHealth().setWaitForStatus(ClusterHealthStatus.RED).execute().actionGet();
String[] preferences = new String[] {"_primary", "_local", "_primary_first", "_prefer_node:somenode", "_prefer_node:server2"}; String[] preferences = new String[] {"_primary", "_local", "_primary_first", "_prefer_nodes:somenode", "_prefer_nodes:server2", "_prefer_nodes:somenode,server2"};
for (String pref : preferences) { for (String pref : preferences) {
logger.info("--> Testing out preference={}", pref); logger.info("--> Testing out preference={}", pref);
SearchResponse searchResponse = client().prepareSearch().setSize(0).setPreference(pref).execute().actionGet(); SearchResponse searchResponse = client().prepareSearch().setSize(0).setPreference(pref).execute().actionGet();

View File

@ -185,3 +185,10 @@ This is now consistent for source filtering on other places in the search API.
In the response for profiling queries, the `query_type` has been renamed to `type` and `lucene` has been renamed to In the response for profiling queries, the `query_type` has been renamed to `type` and `lucene` has been renamed to
`description`. These changes have been made so the response format is more friendly to supporting other types of profiling `description`. These changes have been made so the response format is more friendly to supporting other types of profiling
in the future. in the future.
==== Search prefernce
The <<search-request-preference,search preference>> `_prefer_node` has
been superseded by `_prefer_nodes`. By specifying a single node,
`_prefer_nodes` provides the same functionality as `_prefer_node` but
also supports specifying multiple nodes.

View File

@ -31,9 +31,9 @@ The `preference` is a query string parameter which can be set to:
Restricts the search to execute only on a node with Restricts the search to execute only on a node with
the provided node id (`xyz` in this case). the provided node id (`xyz` in this case).
`_prefer_node:xyz`:: `_prefer_nodes:abc,xyz`::
Prefers execution on the node with the provided Prefers execution on the nodes with the provided
node id (`xyz` in this case) if applicable. node ids (`abc` or `xyz` in this case) if applicable.
`_shards:2,3`:: `_shards:2,3`::
Restricts the operation to the specified shards. (`2` Restricts the operation to the specified shards. (`2`