You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by th...@apache.org on 2015/02/14 04:03:39 UTC
svn commit: r1659750 - in /lucene/dev/branches/branch_5x: ./ solr/
solr/core/ solr/core/src/java/org/apache/solr/handler/component/
solr/server/ solr/server/solr/configsets/sample_techproducts_configs/conf/
solr/solrj/ solr/solrj/src/java/org/apache/so...
Author: thelabdude
Date: Sat Feb 14 03:03:38 2015
New Revision: 1659750
URL: http://svn.apache.org/r1659750
Log:
SOLR-6832: Queries be served locally rather than being forwarded to another replica
Modified:
lucene/dev/branches/branch_5x/ (props changed)
lucene/dev/branches/branch_5x/solr/ (props changed)
lucene/dev/branches/branch_5x/solr/CHANGES.txt (contents, props changed)
lucene/dev/branches/branch_5x/solr/core/ (props changed)
lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java
lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/component/ResponseBuilder.java
lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/component/ShardRequest.java
lucene/dev/branches/branch_5x/solr/server/ (props changed)
lucene/dev/branches/branch_5x/solr/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml
lucene/dev/branches/branch_5x/solr/solrj/ (props changed)
lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java
lucene/dev/branches/branch_5x/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientTest.java
Modified: lucene/dev/branches/branch_5x/solr/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/CHANGES.txt?rev=1659750&r1=1659749&r2=1659750&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/CHANGES.txt (original)
+++ lucene/dev/branches/branch_5x/solr/CHANGES.txt Sat Feb 14 03:03:38 2015
@@ -63,6 +63,9 @@ New Features
* SOLR-7019: Support changing field key when using interval faceting.
(Tomás Fernández Löbbe)
+* SOLR-6832: Queries be served locally rather than being forwarded to another replica.
+ (Sachin Goyal, Timothy Potter)
+
Bug Fixes
----------------------
Modified: lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java?rev=1659750&r1=1659749&r2=1659750&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java Sat Feb 14 03:03:38 2015
@@ -40,13 +40,18 @@ import org.apache.solr.common.params.Sol
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.core.CoreDescriptor;
+import org.apache.solr.core.SolrCore;
import org.apache.solr.request.SolrQueryRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.net.ConnectException;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
@@ -63,6 +68,7 @@ public class HttpShardHandler extends Sh
private Map<String,List<String>> shardToURLs;
private HttpClient httpClient;
+ protected static Logger log = LoggerFactory.getLogger(HttpShardHandler.class);
public HttpShardHandler(HttpShardHandlerFactory httpShardHandlerFactory, HttpClient httpClient) {
this.httpClient = httpClient;
@@ -101,20 +107,69 @@ public class HttpShardHandler extends Sh
// Not thread safe... don't use in Callable.
// Don't modify the returned URL list.
- private List<String> getURLs(String shard) {
+ private List<String> getURLs(ShardRequest sreq, String shard) {
List<String> urls = shardToURLs.get(shard);
if (urls == null) {
urls = httpShardHandlerFactory.makeURLList(shard);
+ preferCurrentHostForDistributedReq(sreq, urls);
shardToURLs.put(shard, urls);
}
return urls;
}
+ /**
+ * A distributed request is made via {@link LBHttpSolrClient} to the first live server in the URL list.
+ * This means it is just as likely to choose current host as any of the other hosts.
+ * This function makes sure that the cores of current host are always put first in the URL list.
+ * If all nodes prefer local-cores then a bad/heavily-loaded node will receive less requests from healthy nodes.
+ * This will help prevent a distributed deadlock or timeouts in all the healthy nodes due to one bad node.
+ */
+ private void preferCurrentHostForDistributedReq(final ShardRequest sreq, final List<String> urls) {
+ if (sreq == null || sreq.rb == null || sreq.rb.req == null || urls == null || urls.size() <= 1)
+ return;
+
+ SolrQueryRequest req = sreq.rb.req;
+
+ // determine if we should apply the local preference
+ if (!req.getOriginalParams().getBool(CommonParams.PREFER_LOCAL_SHARDS, false))
+ return;
+
+ // Get this node's base URL from ZK
+ SolrCore core = req.getCore();
+ ZkController zkController = (core != null) ? core.getCoreDescriptor().getCoreContainer().getZkController() : null;
+ String currentHostAddress = (zkController != null) ? zkController.getBaseUrl() : null;
+ if (currentHostAddress == null) {
+ log.debug("Couldn't determine current host address to prefer local shards " +
+ "because either core is null? {} or there is no ZkController? {}",
+ Boolean.valueOf(core == null), Boolean.valueOf(zkController == null));
+ return;
+ }
+
+ if (log.isDebugEnabled())
+ log.debug("Trying to prefer local shard on {} among the urls: {}",
+ currentHostAddress, Arrays.toString(urls.toArray()));
+
+ ListIterator<String> itr = urls.listIterator();
+ while (itr.hasNext()) {
+ String url = itr.next();
+ if (url.startsWith(currentHostAddress)) {
+ // move current URL to the fore-front
+ itr.remove();
+ urls.add(0, url);
+
+ if (log.isDebugEnabled())
+ log.debug("Applied local shard preference for urls: {}",
+ Arrays.toString(urls.toArray()));
+
+ break;
+ }
+ }
+ }
@Override
public void submit(final ShardRequest sreq, final String shard, final ModifiableSolrParams params) {
// do this outside of the callable for thread safety reasons
- final List<String> urls = getURLs(shard);
+ final List<String> urls = getURLs(sreq, shard);
Callable<ShardResponse> task = new Callable<ShardResponse>() {
@Override
Modified: lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/component/ResponseBuilder.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/component/ResponseBuilder.java?rev=1659750&r1=1659749&r2=1659750&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/component/ResponseBuilder.java (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/component/ResponseBuilder.java Sat Feb 14 03:03:38 2015
@@ -147,6 +147,7 @@ public class ResponseBuilder
public void addRequest(SearchComponent me, ShardRequest sreq) {
outgoing.add(sreq);
+ sreq.rb = this;
if ((sreq.purpose & ShardRequest.PURPOSE_PRIVATE) == 0) {
// if this isn't a private request, let other components modify it.
for (SearchComponent component : components) {
Modified: lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/component/ShardRequest.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/component/ShardRequest.java?rev=1659750&r1=1659749&r2=1659750&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/component/ShardRequest.java (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/java/org/apache/solr/handler/component/ShardRequest.java Sat Feb 14 03:03:38 2015
@@ -49,6 +49,7 @@ public class ShardRequest {
public ModifiableSolrParams params;
+ public ResponseBuilder rb;
/** list of responses... filled out by framework */
public List<ShardResponse> responses = new ArrayList<>();
Modified: lucene/dev/branches/branch_5x/solr/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml?rev=1659750&r1=1659749&r2=1659750&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml (original)
+++ lucene/dev/branches/branch_5x/solr/server/solr/configsets/sample_techproducts_configs/conf/solrconfig.xml Sat Feb 14 03:03:38 2015
@@ -805,6 +805,21 @@
<lst name="defaults">
<str name="echoParams">explicit</str>
<int name="rows">10</int>
+ <!-- Controls the distribution of a query to shards other than itself.
+ Consider making 'preferLocalShards' true when:
+ 1) maxShardsPerNode > 1
+ 2) Number of shards > 1
+ 3) CloudSolrClient or LbHttpSolrServer is used by clients.
+ Without this option, every core broadcasts the distributed query to
+ a replica of each shard where the replicas are chosen randomly.
+ This option directs the cores to prefer cores hosted locally, thus
+ preventing network delays between machines.
+ This behavior also immunizes a bad/slow machine from slowing down all
+ the good machines (if those good machines were querying this bad one).
+
+ Specify this option=false for clients connecting through HttpSolrServer
+ -->
+ <bool name="preferLocalShards">false</bool>
</lst>
<!-- In addition to defaults, "appends" params can be specified
to identify values which should be appended to the list of
Modified: lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java?rev=1659750&r1=1659749&r2=1659750&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java (original)
+++ lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java Sat Feb 14 03:03:38 2015
@@ -220,5 +220,9 @@ public interface CommonParams {
*/
public static final String REQUEST_PURPOSE = "requestPurpose";
+ /**
+ * When querying a node, prefer local node's cores for distributed queries.
+ */
+ public static final String PREFER_LOCAL_SHARDS = "preferLocalShards";
}
Modified: lucene/dev/branches/branch_5x/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientTest.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientTest.java?rev=1659750&r1=1659749&r2=1659750&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientTest.java (original)
+++ lucene/dev/branches/branch_5x/solr/solrj/src/test/org/apache/solr/client/solrj/impl/CloudSolrClientTest.java Sat Feb 14 03:03:38 2015
@@ -17,6 +17,12 @@ package org.apache.solr.client.solrj.imp
* limitations under the License.
*/
+import static org.apache.solr.cloud.OverseerCollectionProcessor.CREATE_NODE_SET;
+import static org.apache.solr.cloud.OverseerCollectionProcessor.NUM_SLICES;
+import static org.apache.solr.common.cloud.ZkNodeProps.makeMap;
+import static org.apache.solr.common.cloud.ZkStateReader.MAX_SHARDS_PER_NODE;
+import static org.apache.solr.common.cloud.ZkStateReader.REPLICATION_FACTOR;
+
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
@@ -44,6 +50,7 @@ import org.apache.solr.common.params.Com
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.ShardParams;
import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.zookeeper.KeeperException;
import org.junit.AfterClass;
import org.junit.BeforeClass;
@@ -53,7 +60,11 @@ import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -115,6 +126,7 @@ public class CloudSolrClientTest extends
stateVersionParamTest();
customHttpClientTest();
testOverwriteOption();
+ preferLocalShardsTest();
}
private void testOverwriteOption() throws Exception, SolrServerException,
@@ -349,6 +361,117 @@ public class CloudSolrClientTest extends
cloudClient.close();
}
+ /**
+ * Tests if the specification of 'preferLocalShards' in the query-params
+ * limits the distributed query to locally hosted shards only
+ */
+ private void preferLocalShardsTest() throws Exception {
+
+ String collectionName = "localShardsTestColl";
+
+ int liveNodes = getCommonCloudSolrClient()
+ .getZkStateReader().getClusterState().getLiveNodes().size();
+
+ // For preferLocalShards to succeed in a test, every shard should have
+ // all its cores on the same node.
+ // Hence the below configuration for our collection
+ Map<String, Object> props = makeMap(
+ REPLICATION_FACTOR, liveNodes,
+ MAX_SHARDS_PER_NODE, liveNodes,
+ NUM_SLICES, liveNodes);
+ Map<String,List<Integer>> collectionInfos = new HashMap<String,List<Integer>>();
+ createCollection(collectionInfos, collectionName, props, controlClientCloud);
+ waitForRecoveriesToFinish(collectionName, false);
+
+ CloudSolrClient cloudClient = createCloudClient(collectionName);
+ assertNotNull(cloudClient);
+ handle.clear();
+ handle.put("timestamp", SKIPVAL);
+ waitForThingsToLevelOut(30);
+
+ // Remove any documents from previous test (if any)
+ controlClient.deleteByQuery("*:*");
+ cloudClient.deleteByQuery("*:*");
+ controlClient.commit();
+ cloudClient.commit();
+
+ // Add some new documents
+ SolrInputDocument doc1 = new SolrInputDocument();
+ doc1.addField(id, "0");
+ doc1.addField("a_t", "hello1");
+ SolrInputDocument doc2 = new SolrInputDocument();
+ doc2.addField(id, "2");
+ doc2.addField("a_t", "hello2");
+ SolrInputDocument doc3 = new SolrInputDocument();
+ doc3.addField(id, "3");
+ doc3.addField("a_t", "hello2");
+
+ UpdateRequest request = new UpdateRequest();
+ request.add(doc1);
+ request.add(doc2);
+ request.add(doc3);
+ request.setAction(AbstractUpdateRequest.ACTION.COMMIT, false, false);
+
+ // Run the actual test for 'preferLocalShards'
+ queryWithPreferLocalShards(cloudClient, true, collectionName);
+
+ // Cleanup
+ controlClient.deleteByQuery("*:*");
+ cloudClient.deleteByQuery("*:*");
+ controlClient.commit();
+ cloudClient.commit();
+ cloudClient.close();
+ }
+
+ private void queryWithPreferLocalShards(CloudSolrClient cloudClient,
+ boolean preferLocalShards,
+ String collectionName)
+ throws Exception
+ {
+ SolrQuery qRequest = new SolrQuery();
+ qRequest.setQuery("*:*");
+
+ ModifiableSolrParams qParams = new ModifiableSolrParams();
+ qParams.add("preferLocalShards", Boolean.toString(preferLocalShards));
+ qParams.add("shards.info", "true");
+ qRequest.add(qParams);
+
+ // CloudSolrClient sends the request to some node.
+ // And since all the nodes are hosting cores from all shards, the
+ // distributed query formed by this node will select cores from the
+ // local shards only
+ QueryResponse qResponse = cloudClient.query (qRequest);
+
+ Object shardsInfo = qResponse.getResponse().get("shards.info");
+ assertNotNull("Unable to obtain shards.info", shardsInfo);
+
+ // Iterate over shards-info and check what cores responded
+ SimpleOrderedMap<?> shardsInfoMap = (SimpleOrderedMap<?>)shardsInfo;
+ Iterator<Map.Entry<String, ?>> itr = shardsInfoMap.asMap(100).entrySet().iterator();
+ List<String> shardAddresses = new ArrayList<String>();
+ while (itr.hasNext()) {
+ Map.Entry<String, ?> e = itr.next();
+ assertTrue("Did not find map-type value in shards.info", e.getValue() instanceof Map);
+ String shardAddress = (String)((Map)e.getValue()).get("shardAddress");
+ assertNotNull("shards.info did not return 'shardAddress' parameter", shardAddress);
+ shardAddresses.add(shardAddress);
+ }
+ log.info("Shards giving the response: " + Arrays.toString(shardAddresses.toArray()));
+
+ // Make sure the distributed queries were directed to a single node only
+ if (preferLocalShards) {
+ Set<Integer> ports = new HashSet<Integer>();
+ for (String shardAddr: shardAddresses) {
+ URL url = new URL (shardAddr);
+ ports.add(url.getPort());
+ }
+
+ // This assertion would hold true as long as every shard has a core on each node
+ assertTrue ("Response was not received from shards on a single node",
+ shardAddresses.size() > 1 && ports.size()==1);
+ }
+ }
+
private Long getNumRequests(String baseUrl, String collectionName) throws
SolrServerException, IOException {