diff --git a/solr/core/src/java/org/apache/solr/core/RateLimiterConfig.java b/solr/core/src/java/org/apache/solr/core/RateLimiterConfig.java new file mode 100644 index 00000000000..a115ba5d974 --- /dev/null +++ b/solr/core/src/java/org/apache/solr/core/RateLimiterConfig.java @@ -0,0 +1,53 @@ +/* + * 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.core; + +import org.apache.solr.client.solrj.SolrRequest; + +import static org.apache.solr.servlet.RateLimitManager.DEFAULT_CONCURRENT_REQUESTS; +import static org.apache.solr.servlet.RateLimitManager.DEFAULT_SLOT_ACQUISITION_TIMEOUT_MS; + +public class RateLimiterConfig { + public static final String RL_CONFIG_KEY = "rate-limiters"; + + public SolrRequest.SolrRequestType requestType; + public boolean isEnabled; + public long waitForSlotAcquisition; + public int allowedRequests; + public boolean isSlotBorrowingEnabled; + public int guaranteedSlotsThreshold; + + public RateLimiterConfig(SolrRequest.SolrRequestType requestType) { + this.requestType = requestType; + this.isEnabled = false; + this.allowedRequests = DEFAULT_CONCURRENT_REQUESTS; + this.isSlotBorrowingEnabled = false; + this.guaranteedSlotsThreshold = this.allowedRequests / 2; + this.waitForSlotAcquisition = DEFAULT_SLOT_ACQUISITION_TIMEOUT_MS; + } + + public RateLimiterConfig(SolrRequest.SolrRequestType requestType, boolean isEnabled, int guaranteedSlotsThreshold, + long waitForSlotAcquisition, int allowedRequests, boolean isSlotBorrowingEnabled) { + this.requestType = requestType; + this.isEnabled = isEnabled; + this.guaranteedSlotsThreshold = guaranteedSlotsThreshold; + this.waitForSlotAcquisition = waitForSlotAcquisition; + this.allowedRequests = allowedRequests; + this.isSlotBorrowingEnabled = isSlotBorrowingEnabled; + } +} diff --git a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java index 8667a87f4ad..81ab2b72c79 100644 --- a/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java +++ b/solr/core/src/java/org/apache/solr/handler/ClusterAPI.java @@ -25,6 +25,7 @@ import org.apache.solr.api.EndPoint; import org.apache.solr.api.PayloadObj; import org.apache.solr.client.solrj.request.beans.ClusterPropInfo; import org.apache.solr.client.solrj.request.beans.CreateConfigInfo; +import org.apache.solr.client.solrj.request.beans.RateLimiterMeta; import org.apache.solr.cloud.OverseerConfigSetMessageHandler; import org.apache.solr.cluster.placement.impl.PlacementPluginConfigImpl; import org.apache.solr.common.MapWriterMap; @@ -51,6 +52,7 @@ import static org.apache.solr.common.params.CollectionParams.CollectionAction.AD import static org.apache.solr.common.params.CollectionParams.CollectionAction.CLUSTERPROP; import static org.apache.solr.common.params.CollectionParams.CollectionAction.OVERSEERSTATUS; import static org.apache.solr.common.params.CollectionParams.CollectionAction.REMOVEROLE; +import static org.apache.solr.core.RateLimiterConfig.RL_CONFIG_KEY; import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM; import static org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM; import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_EDIT_PERM; @@ -206,7 +208,7 @@ public class ClusterAPI { public void setProperty(PayloadObj> obj) throws Exception { Map m = obj.getDataMap(); m.put("action", CLUSTERPROP.toString()); - collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(),m ), obj.getResponse()); + collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), m), obj.getResponse()); } @Command(name = "set-placement-plugin") @@ -228,6 +230,21 @@ public class ClusterAPI { throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error in API", e); } } + + @Command(name = "set-ratelimiter") + public void setRateLimiters(PayloadObj payLoad) { + RateLimiterMeta rateLimiterConfig = payLoad.get(); + ClusterProperties clusterProperties = new ClusterProperties(getCoreContainer().getZkController().getZkClient()); + + try { + clusterProperties.update(rateLimiterConfig == null? + null: + rateLimiterConfig, + RL_CONFIG_KEY); + } catch (Exception e) { + throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error in API", e); + } + } } public static class RoleInfo implements ReflectMapWriter { diff --git a/solr/core/src/java/org/apache/solr/servlet/QueryRateLimiter.java b/solr/core/src/java/org/apache/solr/servlet/QueryRateLimiter.java index ca2f6960b31..f746aca1322 100644 --- a/solr/core/src/java/org/apache/solr/servlet/QueryRateLimiter.java +++ b/solr/core/src/java/org/apache/solr/servlet/QueryRateLimiter.java @@ -17,39 +17,102 @@ package org.apache.solr.servlet; -import javax.servlet.FilterConfig; +import java.io.IOException; +import java.util.Map; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.solr.client.solrj.SolrRequest; +import org.apache.solr.client.solrj.request.beans.RateLimiterMeta; +import org.apache.solr.common.cloud.SolrZkClient; +import org.apache.solr.common.cloud.ZkStateReader; +import org.apache.solr.common.util.Utils; +import org.apache.solr.core.RateLimiterConfig; +import org.apache.solr.util.SolrJacksonAnnotationInspector; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.data.Stat; -import static org.apache.solr.servlet.RateLimitManager.DEFAULT_CONCURRENT_REQUESTS; -import static org.apache.solr.servlet.RateLimitManager.DEFAULT_SLOT_ACQUISITION_TIMEOUT_MS; +import static org.apache.solr.core.RateLimiterConfig.RL_CONFIG_KEY; /** Implementation of RequestRateLimiter specific to query request types. Most of the actual work is delegated * to the parent class but specific configurations and parsing are handled by this class. */ public class QueryRateLimiter extends RequestRateLimiter { - final static String IS_QUERY_RATE_LIMITER_ENABLED = "isQueryRateLimiterEnabled"; - final static String MAX_QUERY_REQUESTS = "maxQueryRequests"; - final static String QUERY_WAIT_FOR_SLOT_ALLOCATION_INMS = "queryWaitForSlotAllocationInMS"; - final static String QUERY_GUARANTEED_SLOTS = "queryGuaranteedSlots"; - final static String QUERY_ALLOW_SLOT_BORROWING = "queryAllowSlotBorrowing"; + private static final ObjectMapper mapper = SolrJacksonAnnotationInspector.createObjectMapper(); - public QueryRateLimiter(FilterConfig filterConfig) { - super(constructQueryRateLimiterConfig(filterConfig)); + public QueryRateLimiter(SolrZkClient solrZkClient) { + super(constructQueryRateLimiterConfig(solrZkClient)); } - protected static RequestRateLimiter.RateLimiterConfig constructQueryRateLimiterConfig(FilterConfig filterConfig) { - RequestRateLimiter.RateLimiterConfig queryRateLimiterConfig = new RequestRateLimiter.RateLimiterConfig(); + public void processConfigChange(Map properties) throws IOException { + RateLimiterConfig rateLimiterConfig = getRateLimiterConfig(); + byte[] configInput = Utils.toJSON(properties.get(RL_CONFIG_KEY)); - queryRateLimiterConfig.requestType = SolrRequest.SolrRequestType.QUERY; - queryRateLimiterConfig.isEnabled = getParamAndParseBoolean(filterConfig, IS_QUERY_RATE_LIMITER_ENABLED, false); - queryRateLimiterConfig.waitForSlotAcquisition = getParamAndParseLong(filterConfig, QUERY_WAIT_FOR_SLOT_ALLOCATION_INMS, - DEFAULT_SLOT_ACQUISITION_TIMEOUT_MS); - queryRateLimiterConfig.allowedRequests = getParamAndParseInt(filterConfig, MAX_QUERY_REQUESTS, - DEFAULT_CONCURRENT_REQUESTS); - queryRateLimiterConfig.isSlotBorrowingEnabled = getParamAndParseBoolean(filterConfig, QUERY_ALLOW_SLOT_BORROWING, false); - queryRateLimiterConfig.guaranteedSlotsThreshold = getParamAndParseInt(filterConfig, QUERY_GUARANTEED_SLOTS, queryRateLimiterConfig.allowedRequests / 2); + if (configInput == null) { + return; + } - return queryRateLimiterConfig; + RateLimiterMeta rateLimiterMeta = mapper.readValue(configInput, RateLimiterMeta.class); + + constructQueryRateLimiterConfigInternal(rateLimiterMeta, rateLimiterConfig); + } + + // To be used in initialization + @SuppressWarnings({"unchecked"}) + private static RateLimiterConfig constructQueryRateLimiterConfig(SolrZkClient zkClient) { + try { + + if (zkClient == null) { + return new RateLimiterConfig(SolrRequest.SolrRequestType.QUERY); + } + + RateLimiterConfig rateLimiterConfig = new RateLimiterConfig(SolrRequest.SolrRequestType.QUERY); + Map clusterPropsJson = (Map) Utils.fromJSON(zkClient.getData(ZkStateReader.CLUSTER_PROPS, null, new Stat(), true)); + byte[] configInput = Utils.toJSON(clusterPropsJson.get(RL_CONFIG_KEY)); + + if (configInput.length == 0) { + // No Rate Limiter configuration defined in clusterprops.json. Return default configuration values + return rateLimiterConfig; + } + + RateLimiterMeta rateLimiterMeta = mapper.readValue(configInput, RateLimiterMeta.class); + + constructQueryRateLimiterConfigInternal(rateLimiterMeta, rateLimiterConfig); + + return rateLimiterConfig; + } catch (KeeperException.NoNodeException e) { + return new RateLimiterConfig(SolrRequest.SolrRequestType.QUERY); + } catch (KeeperException | InterruptedException e) { + throw new RuntimeException("Error reading cluster property", SolrZkClient.checkInterrupted(e)); + } catch (IOException e) { + throw new RuntimeException("Encountered an IOException " + e.getMessage()); + } + } + + private static void constructQueryRateLimiterConfigInternal(RateLimiterMeta rateLimiterMeta, RateLimiterConfig rateLimiterConfig) { + + if (rateLimiterMeta == null) { + // No Rate limiter configuration defined in clusterprops.json + return; + } + + if (rateLimiterMeta.allowedRequests != null) { + rateLimiterConfig.allowedRequests = rateLimiterMeta.allowedRequests.intValue(); + } + + if (rateLimiterMeta.enabled != null) { + rateLimiterConfig.isEnabled = rateLimiterMeta.enabled; + } + + if (rateLimiterMeta.guaranteedSlots != null) { + rateLimiterConfig.guaranteedSlotsThreshold = rateLimiterMeta.guaranteedSlots; + } + + if (rateLimiterMeta.slotBorrowingEnabled != null) { + rateLimiterConfig.isSlotBorrowingEnabled = rateLimiterMeta.slotBorrowingEnabled; + } + + if (rateLimiterMeta.slotAcquisitionTimeoutInMS != null) { + rateLimiterConfig.waitForSlotAcquisition = rateLimiterMeta.slotAcquisitionTimeoutInMS.longValue(); + } } } diff --git a/solr/core/src/java/org/apache/solr/servlet/RateLimitManager.java b/solr/core/src/java/org/apache/solr/servlet/RateLimitManager.java index 0f4618a9c20..aa9b87ee306 100644 --- a/solr/core/src/java/org/apache/solr/servlet/RateLimitManager.java +++ b/solr/core/src/java/org/apache/solr/servlet/RateLimitManager.java @@ -17,8 +17,8 @@ package org.apache.solr.servlet; -import javax.servlet.FilterConfig; import javax.servlet.http.HttpServletRequest; +import java.io.IOException; import java.lang.invoke.MethodHandles; import java.util.HashMap; import java.util.Map; @@ -26,6 +26,8 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.common.annotation.SolrThreadSafe; +import org.apache.solr.common.cloud.ClusterPropertiesListener; +import org.apache.solr.common.cloud.SolrZkClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,7 +44,7 @@ import static org.apache.solr.common.params.CommonParams.SOLR_REQUEST_TYPE_PARAM * rate limiting is being done for a specific request type. */ @SolrThreadSafe -public class RateLimitManager { +public class RateLimitManager implements ClusterPropertiesListener { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); public final static int DEFAULT_CONCURRENT_REQUESTS= (Runtime.getRuntime().availableProcessors()) * 3; @@ -56,6 +58,23 @@ public class RateLimitManager { this.activeRequestsMap = new ConcurrentHashMap<>(); } + @Override + public boolean onChange(Map properties) { + + // Hack: We only support query rate limiting for now + QueryRateLimiter queryRateLimiter = (QueryRateLimiter) requestRateLimiterMap.get(SolrRequest.SolrRequestType.QUERY); + + if (queryRateLimiter != null) { + try { + queryRateLimiter.processConfigChange(properties); + } catch (IOException e) { + throw new RuntimeException("Encountered IOException: " + e.getMessage()); + } + } + + return false; + } + // Handles an incoming request. The main orchestration code path, this method will // identify which (if any) rate limiter can handle this request. Internal requests will not be // rate limited @@ -164,16 +183,16 @@ public class RateLimitManager { } public static class Builder { - protected FilterConfig config; + protected SolrZkClient solrZkClient; - public Builder(FilterConfig config) { - this.config = config; + public Builder(SolrZkClient solrZkClient) { + this.solrZkClient = solrZkClient; } public RateLimitManager build() { RateLimitManager rateLimitManager = new RateLimitManager(); - rateLimitManager.registerRequestRateLimiter(new QueryRateLimiter(config), SolrRequest.SolrRequestType.QUERY); + rateLimitManager.registerRequestRateLimiter(new QueryRateLimiter(solrZkClient), SolrRequest.SolrRequestType.QUERY); return rateLimitManager; } diff --git a/solr/core/src/java/org/apache/solr/servlet/RequestRateLimiter.java b/solr/core/src/java/org/apache/solr/servlet/RequestRateLimiter.java index f68b31266ac..c43362bbba8 100644 --- a/solr/core/src/java/org/apache/solr/servlet/RequestRateLimiter.java +++ b/solr/core/src/java/org/apache/solr/servlet/RequestRateLimiter.java @@ -17,12 +17,11 @@ package org.apache.solr.servlet; -import javax.servlet.FilterConfig; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; -import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.common.annotation.SolrThreadSafe; +import org.apache.solr.core.RateLimiterConfig; /** * Handles rate limiting for a specific request type. @@ -95,58 +94,6 @@ public class RequestRateLimiter { return rateLimiterConfig; } - static long getParamAndParseLong(FilterConfig config, String parameterName, long defaultValue) { - String tempBuffer = config.getInitParameter(parameterName); - - if (tempBuffer != null) { - return Long.parseLong(tempBuffer); - } - - return defaultValue; - } - - static int getParamAndParseInt(FilterConfig config, String parameterName, int defaultValue) { - String tempBuffer = config.getInitParameter(parameterName); - - if (tempBuffer != null) { - return Integer.parseInt(tempBuffer); - } - - return defaultValue; - } - - static boolean getParamAndParseBoolean(FilterConfig config, String parameterName, boolean defaultValue) { - String tempBuffer = config.getInitParameter(parameterName); - - if (tempBuffer != null) { - return Boolean.parseBoolean(tempBuffer); - } - - return defaultValue; - } - - /* Rate limiter config for a specific request rate limiter instance */ - static class RateLimiterConfig { - public SolrRequest.SolrRequestType requestType; - public boolean isEnabled; - public long waitForSlotAcquisition; - public int allowedRequests; - public boolean isSlotBorrowingEnabled; - public int guaranteedSlotsThreshold; - - public RateLimiterConfig() { } - - public RateLimiterConfig(SolrRequest.SolrRequestType requestType, boolean isEnabled, int guaranteedSlotsThreshold, - long waitForSlotAcquisition, int allowedRequests, boolean isSlotBorrowingEnabled) { - this.requestType = requestType; - this.isEnabled = isEnabled; - this.guaranteedSlotsThreshold = guaranteedSlotsThreshold; - this.waitForSlotAcquisition = waitForSlotAcquisition; - this.allowedRequests = allowedRequests; - this.isSlotBorrowingEnabled = isSlotBorrowingEnabled; - } - } - // Represents the metadata for a slot static class SlotMetadata { private final Semaphore usedPool; diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java index c560208fa35..967dffbed30 100644 --- a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java +++ b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java @@ -64,6 +64,7 @@ import org.apache.http.HttpHeaders; import org.apache.http.client.HttpClient; import org.apache.lucene.util.Version; import org.apache.solr.api.V2HttpCall; +import org.apache.solr.cloud.ZkController; import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException.ErrorCode; import org.apache.solr.common.cloud.SolrZkClient; @@ -187,9 +188,22 @@ public class SolrDispatchFilter extends BaseSolrFilter { coresInit = createCoreContainer(solrHomePath, extraProperties); this.httpClient = coresInit.getUpdateShardHandler().getDefaultHttpClient(); setupJvmMetrics(coresInit); - RateLimitManager.Builder builder = new RateLimitManager.Builder(config); + + SolrZkClient zkClient = null; + ZkController zkController = coresInit.getZkController(); + + if (zkController != null) { + zkClient = zkController.getZkClient(); + } + + RateLimitManager.Builder builder = new RateLimitManager.Builder(zkClient); this.rateLimitManager = builder.build(); + + if (zkController != null) { + zkController.zkStateReader.registerClusterPropertiesListener(this.rateLimitManager); + } + if (log.isDebugEnabled()) { log.debug("user.dir={}", System.getProperty("user.dir")); } diff --git a/solr/core/src/test/org/apache/solr/servlet/TestRequestRateLimiter.java b/solr/core/src/test/org/apache/solr/servlet/TestRequestRateLimiter.java index f8023a6a8bb..8a1587c55c6 100644 --- a/solr/core/src/test/org/apache/solr/servlet/TestRequestRateLimiter.java +++ b/solr/core/src/test/org/apache/solr/servlet/TestRequestRateLimiter.java @@ -17,7 +17,6 @@ package org.apache.solr.servlet; -import javax.servlet.FilterConfig; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; @@ -32,7 +31,9 @@ import org.apache.solr.client.solrj.request.CollectionAdminRequest; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.cloud.SolrCloudTestCase; import org.apache.solr.common.SolrInputDocument; +import org.apache.solr.common.cloud.SolrZkClient; import org.apache.solr.common.util.ExecutorUtil; +import org.apache.solr.core.RateLimiterConfig; import org.junit.BeforeClass; import org.junit.Test; @@ -58,10 +59,10 @@ public class TestRequestRateLimiter extends SolrCloudTestCase { SolrDispatchFilter solrDispatchFilter = cluster.getJettySolrRunner(0).getSolrDispatchFilter(); - RequestRateLimiter.RateLimiterConfig rateLimiterConfig = new RequestRateLimiter.RateLimiterConfig(SolrRequest.SolrRequestType.QUERY, + RateLimiterConfig rateLimiterConfig = new RateLimiterConfig(SolrRequest.SolrRequestType.QUERY, true, 1, DEFAULT_SLOT_ACQUISITION_TIMEOUT_MS, 5 /* allowedRequests */, true /* isSlotBorrowing */); // We are fine with a null FilterConfig here since we ensure that MockBuilder never invokes its parent here - RateLimitManager.Builder builder = new MockBuilder(null /* dummy FilterConfig */, new MockRequestRateLimiter(rateLimiterConfig, 5)); + RateLimitManager.Builder builder = new MockBuilder(null /* dummy SolrZkClient */, new MockRequestRateLimiter(rateLimiterConfig, 5)); RateLimitManager rateLimitManager = builder.build(); solrDispatchFilter.replaceRateLimitManager(rateLimitManager); @@ -91,12 +92,12 @@ public class TestRequestRateLimiter extends SolrCloudTestCase { SolrDispatchFilter solrDispatchFilter = cluster.getJettySolrRunner(0).getSolrDispatchFilter(); - RequestRateLimiter.RateLimiterConfig queryRateLimiterConfig = new RequestRateLimiter.RateLimiterConfig(SolrRequest.SolrRequestType.QUERY, + RateLimiterConfig queryRateLimiterConfig = new RateLimiterConfig(SolrRequest.SolrRequestType.QUERY, true, 1, DEFAULT_SLOT_ACQUISITION_TIMEOUT_MS, 5 /* allowedRequests */, true /* isSlotBorrowing */); - RequestRateLimiter.RateLimiterConfig indexRateLimiterConfig = new RequestRateLimiter.RateLimiterConfig(SolrRequest.SolrRequestType.UPDATE, + RateLimiterConfig indexRateLimiterConfig = new RateLimiterConfig(SolrRequest.SolrRequestType.UPDATE, true, 1, DEFAULT_SLOT_ACQUISITION_TIMEOUT_MS, 5 /* allowedRequests */, true /* isSlotBorrowing */); // We are fine with a null FilterConfig here since we ensure that MockBuilder never invokes its parent - RateLimitManager.Builder builder = new MockBuilder(null /*dummy FilterConfig */, new MockRequestRateLimiter(queryRateLimiterConfig, 5), new MockRequestRateLimiter(indexRateLimiterConfig, 5)); + RateLimitManager.Builder builder = new MockBuilder(null /*dummy SolrZkClient */, new MockRequestRateLimiter(queryRateLimiterConfig, 5), new MockRequestRateLimiter(indexRateLimiterConfig, 5)); RateLimitManager rateLimitManager = builder.build(); solrDispatchFilter.replaceRateLimitManager(rateLimitManager); @@ -205,15 +206,15 @@ public class TestRequestRateLimiter extends SolrCloudTestCase { private final RequestRateLimiter queryRequestRateLimiter; private final RequestRateLimiter indexRequestRateLimiter; - public MockBuilder(FilterConfig config, RequestRateLimiter queryRequestRateLimiter) { - super(config); + public MockBuilder(SolrZkClient zkClient, RequestRateLimiter queryRequestRateLimiter) { + super(zkClient); this.queryRequestRateLimiter = queryRequestRateLimiter; this.indexRequestRateLimiter = null; } - public MockBuilder(FilterConfig config, RequestRateLimiter queryRequestRateLimiter, RequestRateLimiter indexRequestRateLimiter) { - super(config); + public MockBuilder(SolrZkClient zkClient, RequestRateLimiter queryRequestRateLimiter, RequestRateLimiter indexRequestRateLimiter) { + super(zkClient); this.queryRequestRateLimiter = queryRequestRateLimiter; this.indexRequestRateLimiter = indexRequestRateLimiter; diff --git a/solr/solr-ref-guide/src/rate-limiters.adoc b/solr/solr-ref-guide/src/rate-limiters.adoc index 6c0d75a0ed9..708f1f8883f 100644 --- a/solr/solr-ref-guide/src/rate-limiters.adoc +++ b/solr/solr-ref-guide/src/rate-limiters.adoc @@ -31,37 +31,27 @@ pronounced under high stress in production workloads. The current implementation resources for indexing. == Rate Limiter Configurations -The default rate limiter is search rate limiter. Accordingly, it can be configured in `web.xml` under `initParams` for -`SolrRequestFilter`. +The default rate limiter is search rate limiter. Accordingly, it can be configured using the following command: -[source,xml] ----- -SolrRequestFilter ----- + curl -X POST -H 'Content-type:application/json' -d '{ + "set-ratelimiter": { + "enabled": true, + "guaranteedSlots":5, + "allowedRequests":20, + "slotBorrowingEnabled":true, + "slotAcquisitionTimeoutInMS":70 + } + }' http://localhost:8983/api/cluster === Enable Query Rate Limiter Controls enabling of query rate limiter. Default value is `false`. -[source,xml] ----- -isQueryRateLimiterEnabled ----- -[source,xml] ----- -true ----- + "enabled": true === Maximum Number Of Concurrent Requests Allows setting maximum concurrent search requests at a given point in time. Default value is number of cores * 3. -[source,xml] ----- -maxQueryRequests ----- -[source,xml] ----- -15 ----- + "allowedRequests":20 === Request Slot Allocation Wait Time Wait time in ms for which a request will wait for a slot to be available when all slots are full, @@ -69,14 +59,8 @@ before the request is put into the wait queue. This allows requests to have a ch the unavailability of the request slots for this rate limiter is a transient phenomenon. Default value is -1, indicating no wait. 0 will represent the same -- no wait. Note that higher request allocation times can lead to larger queue times and can potentially lead to longer wait times for queries. -[source,xml] ----- -queryWaitForSlotAllocationInMS ----- -[source,xml] ----- -100 ----- + + "slotAcquisitionTimeoutInMS":70 === Slot Borrowing Enabled If slot borrowing (described below) is enabled or not. Default value is false. @@ -84,14 +68,7 @@ If slot borrowing (described below) is enabled or not. Default value is false. NOTE: This feature is experimental and can cause slots to be blocked if the borrowing request is long lived. -[source,xml] ----- -queryAllowSlotBorrowing ----- -[source,xml] ----- -true ----- + "slotBorrowingEnabled":true, === Guaranteed Slots The number of guaranteed slots that the query rate limiter will reserve irrespective @@ -102,14 +79,7 @@ borrow slots from its quota. Default value is allowed number of concurrent reque NOTE: This feature is experimental and can cause slots to be blocked if the borrowing request is long lived. -[source,xml] ----- -queryGuaranteedSlots ----- -[source,xml] ----- -200 ----- + "guaranteedSlots":5, == Salient Points diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/RateLimiterMeta.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/RateLimiterMeta.java new file mode 100644 index 00000000000..7cf70fd8742 --- /dev/null +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/RateLimiterMeta.java @@ -0,0 +1,73 @@ +/* + * 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.client.solrj.request.beans; + +import java.util.Objects; + +import org.apache.solr.common.annotation.JsonProperty; +import org.apache.solr.common.util.ReflectMapWriter; + +/** + * POJO for Rate Limiter Metadata Configuration + */ +public class RateLimiterMeta implements ReflectMapWriter { + @JsonProperty + public Boolean enabled; + + @JsonProperty + public Integer guaranteedSlots; + + @JsonProperty + public Integer allowedRequests; + + @JsonProperty + public Boolean slotBorrowingEnabled; + + @JsonProperty + public Integer slotAcquisitionTimeoutInMS; + + public RateLimiterMeta copy() { + RateLimiterMeta result = new RateLimiterMeta(); + + result.enabled = enabled; + result.guaranteedSlots = guaranteedSlots; + result.allowedRequests = allowedRequests; + result.slotBorrowingEnabled = slotBorrowingEnabled; + result.slotAcquisitionTimeoutInMS = slotAcquisitionTimeoutInMS; + + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof RateLimiterMeta) { + RateLimiterMeta that = (RateLimiterMeta) obj; + return Objects.equals(this.enabled, that.enabled) && + Objects.equals(this.guaranteedSlots, that.guaranteedSlots) && + Objects.equals(this.allowedRequests, that.allowedRequests) && + Objects.equals(this.slotBorrowingEnabled, that.slotBorrowingEnabled) && + Objects.equals(this.slotAcquisitionTimeoutInMS, that.slotAcquisitionTimeoutInMS); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(enabled, guaranteedSlots, allowedRequests, slotBorrowingEnabled, slotAcquisitionTimeoutInMS); + } +}