SOLR-13904: Make Analytics component sensitive to timeAllowed.

This commit is contained in:
Mikhail Khludnev 2019-11-09 17:49:31 +01:00
parent dc031ea382
commit f01b3e97d1
14 changed files with 139 additions and 24 deletions

View File

@ -87,7 +87,7 @@ Other Changes
* SOLR-13655:Upgrade Collections.unModifiableSet to Set.of and Set.copyOf (Atri Sharma via Tomás Fernández Löbbe) * SOLR-13655:Upgrade Collections.unModifiableSet to Set.of and Set.copyOf (Atri Sharma via Tomás Fernández Löbbe)
* SOLR-13797: SolrResourceLoader no longer caches bad results when asked for wrong type (Mike Drob) * SOLR-13797: SolrResourceLoader no longer caches bad results when asked for wrong type (Mike Drob)
================== 8.4.0 ================== ================== 8.4.0 ==================
@ -122,6 +122,8 @@ Upgrade Notes
* SOLR-14025: VelocityResponseWriter has been hardened - only trusted configsets can render configset provided * SOLR-14025: VelocityResponseWriter has been hardened - only trusted configsets can render configset provided
templates and rendering templates from request parameters has been removed. templates and rendering templates from request parameters has been removed.
* SOLR-13904: timeAllowed parameter is allowed to have 0 value (Houston Putman, Mikhail Khludnev)
New Features New Features
--------------------- ---------------------
* SOLR-13821: A Package store to store and load package artifacts (noble, Ishan Chattopadhyaya) * SOLR-13821: A Package store to store and load package artifacts (noble, Ishan Chattopadhyaya)
@ -142,7 +144,8 @@ New Features
* SOLR-13912: Add 'countvals' aggregation in JSON FacetModule (hossman, Munendra S N) * SOLR-13912: Add 'countvals' aggregation in JSON FacetModule (hossman, Munendra S N)
* SOLR-12217: Support shards.preference in SolrJ for single shard collections. The parameter is now used by the CloudSolrClient and Streaming Expressions. (Houston Putman, Tomas Fernandez-Lobbe) * SOLR-12217: Support shards.preference in SolrJ for single shard collections. The parameter
is now used by the CloudSolrClient and Streaming Expressions. (Houston Putman, Tomas Fernandez-Lobbe)
* SOLR-14043: Allow the precision Stream Evaluator to operate on matrices (Joel bernstein) * SOLR-14043: Allow the precision Stream Evaluator to operate on matrices (Joel bernstein)
@ -182,6 +185,9 @@ Improvements
* SOLR-13957: Add sensible defaults for the facet, random, facet2D, timeseries, stats * SOLR-13957: Add sensible defaults for the facet, random, facet2D, timeseries, stats
and update Streaming Expressions (Joel Bernstein) and update Streaming Expressions (Joel Bernstein)
* SOLR-13904: Analytic component abandon requests exceedig limit passed via timeAllowed parameter
(Houston Putman, Mikhail Khludnev).
Optimizations Optimizations
--------------------- ---------------------
(No changes) (No changes)

View File

@ -54,6 +54,7 @@ public class AnalyticsRequestManager {
public String analyticsRequest; public String analyticsRequest;
public AnalyticsShardRequestManager shardStream; public AnalyticsShardRequestManager shardStream;
public boolean sendShards; public boolean sendShards;
private boolean partialResults = false;
/** /**
* Create an manager with the given ungrouped expressions. This is straightforward in the new * Create an manager with the given ungrouped expressions. This is straightforward in the new
@ -276,4 +277,14 @@ public class AnalyticsRequestManager {
} }
return analyticsResponse; return analyticsResponse;
} }
public void setPartialResults(boolean b) {
this.partialResults=b;
}
public boolean isPartialResults() {
return partialResults;
}
} }

View File

@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.analytics;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.response.SolrQueryResponse;
@SuppressWarnings("serial")
public final class TimeExceededStubException extends SolrException {
private static final int HTTP_CODE = 524;
public TimeExceededStubException(Throwable th) {
super(HTTP_CODE,
SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY, th);
setMetadata("cause", CommonParams.TIME_ALLOWED+" is exceeded");
}
public static boolean isIt(SolrException e) {
return e.code() == HTTP_CODE && SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY.equals(e.getMessage());
}
}

View File

@ -31,10 +31,11 @@ import java.util.concurrent.Future;
import org.apache.solr.analytics.AnalyticsRequestManager; import org.apache.solr.analytics.AnalyticsRequestManager;
import org.apache.solr.analytics.AnalyticsRequestParser; import org.apache.solr.analytics.AnalyticsRequestParser;
import org.apache.solr.analytics.TimeExceededStubException;
import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.impl.CloudSolrClient; import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.impl.CloudSolrClient.Builder; import org.apache.solr.client.solrj.impl.CloudSolrClient.Builder;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.request.QueryRequest; import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ClusterState; import org.apache.solr.common.cloud.ClusterState;
@ -154,7 +155,11 @@ public class AnalyticsShardRequestManager {
for (Future<SolrException> f : futures) { for (Future<SolrException> f : futures) {
SolrException e = f.get(); SolrException e = f.get();
if (e != null) { if (e != null) {
throw e; if (TimeExceededStubException.isIt(e)) {
manager.setPartialResults(true);
} else {
throw e;
}
} }
} }
} catch (InterruptedException e1) { } catch (InterruptedException e1) {
@ -188,6 +193,7 @@ public class AnalyticsShardRequestManager {
solrParams.add(CommonParams.WT, AnalyticsShardResponseWriter.NAME); solrParams.add(CommonParams.WT, AnalyticsShardResponseWriter.NAME);
solrParams.add(CommonParams.Q, paramsIn.get(CommonParams.Q)); solrParams.add(CommonParams.Q, paramsIn.get(CommonParams.Q));
solrParams.add(CommonParams.FQ, paramsIn.getParams(CommonParams.FQ)); solrParams.add(CommonParams.FQ, paramsIn.getParams(CommonParams.FQ));
solrParams.add(CommonParams.TIME_ALLOWED, paramsIn.get(CommonParams.TIME_ALLOWED,"-1"));
solrParams.add(AnalyticsRequestParser.analyticsParamName, analyticsRequest); solrParams.add(AnalyticsRequestParser.analyticsParamName, analyticsRequest);
return solrParams; return solrParams;

View File

@ -18,12 +18,15 @@ package org.apache.solr.handler;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import org.apache.lucene.index.ExitableDirectoryReader;
import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.apache.solr.analytics.AnalyticsDriver; import org.apache.solr.analytics.AnalyticsDriver;
import org.apache.solr.analytics.AnalyticsRequestManager; import org.apache.solr.analytics.AnalyticsRequestManager;
import org.apache.solr.analytics.AnalyticsRequestParser; import org.apache.solr.analytics.AnalyticsRequestParser;
import org.apache.solr.analytics.ExpressionFactory; import org.apache.solr.analytics.ExpressionFactory;
import org.apache.solr.analytics.TimeExceededStubException;
import org.apache.solr.analytics.stream.AnalyticsShardResponseParser; import org.apache.solr.analytics.stream.AnalyticsShardResponseParser;
import org.apache.solr.client.solrj.io.ModelCache; import org.apache.solr.client.solrj.io.ModelCache;
import org.apache.solr.client.solrj.io.SolrClientCache; import org.apache.solr.client.solrj.io.SolrClientCache;
@ -43,6 +46,7 @@ import org.apache.solr.search.QParser;
import org.apache.solr.search.QParserPlugin; import org.apache.solr.search.QParserPlugin;
import org.apache.solr.search.QueryParsing; import org.apache.solr.search.QueryParsing;
import org.apache.solr.search.SolrIndexSearcher; import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.search.SolrQueryTimeoutImpl;
import org.apache.solr.search.SyntaxError; import org.apache.solr.search.SyntaxError;
import org.apache.solr.security.AuthorizationContext; import org.apache.solr.security.AuthorizationContext;
import org.apache.solr.security.PermissionNameProvider; import org.apache.solr.security.PermissionNameProvider;
@ -74,6 +78,11 @@ public class AnalyticsHandler extends RequestHandlerBase implements SolrCoreAwar
} }
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
long timeAllowed = req.getParams().getLong(CommonParams.TIME_ALLOWED, -1L);
if (timeAllowed >= 0L) {
SolrQueryTimeoutImpl.set(timeAllowed);
}
try { try {
DocSet docs; DocSet docs;
try { try {
@ -95,6 +104,10 @@ public class AnalyticsHandler extends RequestHandlerBase implements SolrCoreAwar
rsp.addResponse(new AnalyticsResponse(manager)); rsp.addResponse(new AnalyticsResponse(manager));
} catch (SolrException e) { } catch (SolrException e) {
rsp.addResponse(new AnalyticsResponse(e)); rsp.addResponse(new AnalyticsResponse(e));
} catch (ExitableDirectoryReader.ExitingReaderException e) {
rsp.addResponse(new AnalyticsResponse(new TimeExceededStubException(e)));
} finally {
SolrQueryTimeoutImpl.reset();
} }
} }

View File

@ -27,6 +27,7 @@ import org.apache.solr.analytics.util.AnalyticsResponseHeadings;
import org.apache.solr.analytics.util.OldAnalyticsParams; import org.apache.solr.analytics.util.OldAnalyticsParams;
import org.apache.solr.analytics.util.OldAnalyticsRequestConverter; import org.apache.solr.analytics.util.OldAnalyticsRequestConverter;
import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.NamedList;
import org.apache.solr.response.SolrQueryResponse;
/** /**
* Computes analytics requests. * Computes analytics requests.
@ -143,6 +144,9 @@ public class AnalyticsComponent extends SearchComponent {
} else { } else {
rb.rsp.add(AnalyticsResponseHeadings.COMPLETED_HEADER, reqManager.createResponse()); rb.rsp.add(AnalyticsResponseHeadings.COMPLETED_HEADER, reqManager.createResponse());
} }
if (reqManager.isPartialResults()) {
rb.rsp.getResponseHeader().asShallowMap().put(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY,true);
}
} }
super.finishStage(rb); super.finishStage(rb);

View File

@ -33,8 +33,9 @@ import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.cloud.SolrCloudTestCase; import org.apache.solr.cloud.SolrCloudTestCase;
import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.NamedList;
import org.junit.After; import org.apache.solr.response.SolrQueryResponse;
import org.junit.Before; import org.junit.AfterClass;
import org.junit.BeforeClass;
public class LegacyAbstractAnalyticsCloudTest extends SolrCloudTestCase { public class LegacyAbstractAnalyticsCloudTest extends SolrCloudTestCase {
@ -42,8 +43,8 @@ public class LegacyAbstractAnalyticsCloudTest extends SolrCloudTestCase {
protected static final int TIMEOUT = DEFAULT_TIMEOUT; protected static final int TIMEOUT = DEFAULT_TIMEOUT;
protected static final String id = "id"; protected static final String id = "id";
@Before @BeforeClass
public void setupCollection() throws Exception { public static void setupCollection() throws Exception {
configureCluster(4) configureCluster(4)
.addConfig("conf", configset("cloud-analytics")) .addConfig("conf", configset("cloud-analytics"))
.configure(); .configure();
@ -52,8 +53,8 @@ public class LegacyAbstractAnalyticsCloudTest extends SolrCloudTestCase {
cluster.waitForActiveCollection(COLLECTIONORALIAS, 2, 2); cluster.waitForActiveCollection(COLLECTIONORALIAS, 2, 2);
} }
@After @AfterClass
public void teardownCollection() throws Exception { public static void teardownCollection() throws Exception {
shutdownCluster(); shutdownCluster();
} }
@ -85,6 +86,7 @@ public class LegacyAbstractAnalyticsCloudTest extends SolrCloudTestCase {
} }
} }
protected NamedList<Object> queryLegacyCloudAnalytics(String[] testParams) throws SolrServerException, IOException, InterruptedException, TimeoutException { protected NamedList<Object> queryLegacyCloudAnalytics(String[] testParams) throws SolrServerException, IOException, InterruptedException, TimeoutException {
ModifiableSolrParams params = new ModifiableSolrParams(); ModifiableSolrParams params = new ModifiableSolrParams();
params.set("q", "*:*"); params.set("q", "*:*");
@ -97,7 +99,23 @@ public class LegacyAbstractAnalyticsCloudTest extends SolrCloudTestCase {
cluster.waitForAllNodes(10000); cluster.waitForAllNodes(10000);
QueryRequest qreq = new QueryRequest(params); QueryRequest qreq = new QueryRequest(params);
QueryResponse resp = qreq.process(cluster.getSolrClient(), COLLECTIONORALIAS); QueryResponse resp = qreq.process(cluster.getSolrClient(), COLLECTIONORALIAS);
return resp.getResponse(); final NamedList<Object> response = resp.getResponse();
assertRequestTimeout(params);
return response;
}
/** caveat: the given params are modified */
protected void assertRequestTimeout(ModifiableSolrParams params)
throws IOException, InterruptedException, TimeoutException, SolrServerException {
params.set("timeAllowed", 0);
cluster.waitForAllNodes(10000);
final QueryResponse maybeTimeout = new QueryRequest(params).process(cluster.getSolrClient(), COLLECTIONORALIAS);
assertEquals(maybeTimeout.getHeader() + "", 0, maybeTimeout.getStatus());
final Boolean partial = maybeTimeout.getHeader()
.getBooleanArg(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY);
assertNotNull("No partial results header returned", partial);
assertTrue("The request " + params
+ "was not stopped halfway through, the partial results header was false", partial);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View File

@ -209,6 +209,10 @@ public class LegacyAbstractAnalyticsTest extends SolrTestCaseJ4 {
return SolrTestCaseJ4.req( ObjectArrays.concat(BASEPARMS, args,String.class) ); return SolrTestCaseJ4.req( ObjectArrays.concat(BASEPARMS, args,String.class) );
} }
public static SolrQueryRequest request(String[] args, String... additional){
return SolrTestCaseJ4.req( ObjectArrays.concat(BASEPARMS, args,String.class), additional );
}
public static String[] fileToStringArr(Class<?> clazz, String fileName) throws FileNotFoundException { public static String[] fileToStringArr(Class<?> clazz, String fileName) throws FileNotFoundException {
InputStream in = clazz.getResourceAsStream("/solr/analytics/legacy/" + fileName); InputStream in = clazz.getResourceAsStream("/solr/analytics/legacy/" + fileName);
if (in == null) throw new FileNotFoundException("Resource not found: " + fileName); if (in == null) throw new FileNotFoundException("Resource not found: " + fileName);

View File

@ -21,7 +21,7 @@ import java.util.List;
import org.apache.solr.client.solrj.request.UpdateRequest; import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.NamedList;
import org.junit.Before; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
public class LegacyNoFacetCloudTest extends LegacyAbstractAnalyticsCloudTest { public class LegacyNoFacetCloudTest extends LegacyAbstractAnalyticsCloudTest {
@ -57,8 +57,8 @@ public class LegacyNoFacetCloudTest extends LegacyAbstractAnalyticsCloudTest {
static ArrayList<String> stringTestStart; static ArrayList<String> stringTestStart;
static long stringMissing = 0; static long stringMissing = 0;
@Before @BeforeClass
public void populate() throws Exception { public static void populate() throws Exception {
intTestStart = new ArrayList<>(); intTestStart = new ArrayList<>();
longTestStart = new ArrayList<>(); longTestStart = new ArrayList<>();
floatTestStart = new ArrayList<>(); floatTestStart = new ArrayList<>();
@ -146,7 +146,7 @@ public class LegacyNoFacetCloudTest extends LegacyAbstractAnalyticsCloudTest {
//Double //Double
Double doubleResult = getValue(response, "sr", "double_dd"); Double doubleResult = getValue(response, "sr", "double_dd");
Double doubleTest = (Double) calculateNumberStat(doubleTestStart, "sum"); Double doubleTest = (Double) calculateNumberStat(doubleTestStart, "sum");
assertEquals(responseStr, doubleResult,doubleTest); assertEquals(responseStr, doubleResult,doubleTest);
} }

View File

@ -24,10 +24,9 @@ import java.util.List;
import org.apache.solr.client.solrj.request.UpdateRequest; import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.NamedList;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
public class LegacyFieldFacetCloudTest extends LegacyAbstractAnalyticsFacetCloudTest { public class LegacyFieldFacetCloudTest extends LegacyAbstractAnalyticsFacetCloudTest {
public static final int INT = 71; public static final int INT = 71;
public static final int LONG = 36; public static final int LONG = 36;
@ -85,8 +84,8 @@ public class LegacyFieldFacetCloudTest extends LegacyAbstractAnalyticsFacetCloud
private static ArrayList<ArrayList<Integer>> multiDateTestStart; private static ArrayList<ArrayList<Integer>> multiDateTestStart;
private static ArrayList<Long> multiDateTestMissing; private static ArrayList<Long> multiDateTestMissing;
@Before @BeforeClass
public void beforeTest() throws Exception { public static void beforeTest() throws Exception {
//INT //INT
intDateTestStart = new ArrayList<>(); intDateTestStart = new ArrayList<>();

View File

@ -24,7 +24,7 @@ import java.util.List;
import org.apache.solr.client.solrj.request.UpdateRequest; import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.NamedList;
import org.junit.Before; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
public class LegacyFieldFacetExtrasCloudTest extends LegacyAbstractAnalyticsFacetCloudTest { public class LegacyFieldFacetExtrasCloudTest extends LegacyAbstractAnalyticsFacetCloudTest {
@ -42,8 +42,8 @@ public class LegacyFieldFacetExtrasCloudTest extends LegacyAbstractAnalyticsFace
static ArrayList<ArrayList<Integer>> intDoubleTestStart; static ArrayList<ArrayList<Integer>> intDoubleTestStart;
static ArrayList<ArrayList<Integer>> intStringTestStart; static ArrayList<ArrayList<Integer>> intStringTestStart;
@Before @BeforeClass
public void beforeTest() throws Exception { public static void beforeTest() throws Exception {
//INT //INT
intLongTestStart = new ArrayList<>(); intLongTestStart = new ArrayList<>();

View File

@ -21,6 +21,9 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.junit.Assert; import org.junit.Assert;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
@ -417,6 +420,20 @@ public class LegacyFieldFacetTest extends LegacyAbstractAnalyticsFacetTest{
setResponse(h.query(request(reqFacetParamas))); setResponse(h.query(request(reqFacetParamas)));
} }
@Test
public void timeAllowedTest() throws Exception {
String query = "int_id: [0 TO " + random().nextInt(INT) + "] AND long_ld: [0 TO " + random().nextInt(LONG) + "]";
try (SolrQueryRequest req = req(fileToStringArr(LegacyFieldFacetTest.class, fileName), "q", query, "timeAllowed", "0", "cache", "false")) {
SolrQueryResponse resp = h.queryAndResponse(req.getParams().get(CommonParams.QT), req);
Assert.assertEquals(resp.getResponseHeader().toString(), 0, resp.getResponseHeader().get("status"));
Boolean partialResults = resp.getResponseHeader().getBooleanArg(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY);
assertNotNull("No partial results header returned: " + resp.getResponseHeader().toString(), partialResults);
assertTrue("The request was not stopped halfway through, the partial results header was false", partialResults);
}
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Test @Test
public void sumTest() throws Exception { public void sumTest() throws Exception {

View File

@ -316,7 +316,7 @@ public class SearchHandler extends RequestHandlerBase implements SolrCoreAware,
// a normal non-distributed request // a normal non-distributed request
long timeAllowed = req.getParams().getLong(CommonParams.TIME_ALLOWED, -1L); long timeAllowed = req.getParams().getLong(CommonParams.TIME_ALLOWED, -1L);
if (timeAllowed > 0L) { if (timeAllowed >= 0L) {
SolrQueryTimeoutImpl.set(timeAllowed); SolrQueryTimeoutImpl.set(timeAllowed);
} }
try { try {

View File

@ -229,8 +229,9 @@ This value is only checked at the time of:
. Query Expansion, and . Query Expansion, and
. Document collection . Document collection
. Doc Values reading
As this check is periodically performed, the actual time for which a request can be processed before it is aborted would be marginally greater than or equal to the value of `timeAllowed`. If the request consumes more time in other stages, custom components, etc., this parameter is not expected to abort the request. As this check is periodically performed, the actual time for which a request can be processed before it is aborted would be marginally greater than or equal to the value of `timeAllowed`. If the request consumes more time in other stages, custom components, etc., this parameter is not expected to abort the request. Regular search, JSON Facet and Analytics handler abandon requests in according to this parameter.
== segmentTerminateEarly Parameter == segmentTerminateEarly Parameter