SOLR-13528: Implement API Based Config For Rate Limiters (#1906)

This commit moves Rate Limiter configurations from web.xml to a new command based approach using clusterprops.json
This commit is contained in:
Atri Sharma 2020-09-28 14:57:06 +05:30 committed by GitHub
parent 6b0149ec1a
commit 4105414c90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 296 additions and 139 deletions

View File

@ -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;
}
}

View File

@ -25,6 +25,7 @@ import org.apache.solr.api.EndPoint;
import org.apache.solr.api.PayloadObj; import org.apache.solr.api.PayloadObj;
import org.apache.solr.client.solrj.request.beans.ClusterPropInfo; 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.CreateConfigInfo;
import org.apache.solr.client.solrj.request.beans.RateLimiterMeta;
import org.apache.solr.cloud.OverseerConfigSetMessageHandler; import org.apache.solr.cloud.OverseerConfigSetMessageHandler;
import org.apache.solr.cluster.placement.impl.PlacementPluginConfigImpl; import org.apache.solr.cluster.placement.impl.PlacementPluginConfigImpl;
import org.apache.solr.common.MapWriterMap; 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.CLUSTERPROP;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.OVERSEERSTATUS; 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.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_EDIT_PERM;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM; import static org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM;
import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_EDIT_PERM; import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_EDIT_PERM;
@ -206,7 +208,7 @@ public class ClusterAPI {
public void setProperty(PayloadObj<Map<String,String>> obj) throws Exception { public void setProperty(PayloadObj<Map<String,String>> obj) throws Exception {
Map<String,Object> m = obj.getDataMap(); Map<String,Object> m = obj.getDataMap();
m.put("action", CLUSTERPROP.toString()); 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") @Command(name = "set-placement-plugin")
@ -228,6 +230,21 @@ public class ClusterAPI {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error in API", e); throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error in API", e);
} }
} }
@Command(name = "set-ratelimiter")
public void setRateLimiters(PayloadObj<RateLimiterMeta> 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 { public static class RoleInfo implements ReflectMapWriter {

View File

@ -17,39 +17,102 @@
package org.apache.solr.servlet; 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.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.core.RateLimiterConfig.RL_CONFIG_KEY;
import static org.apache.solr.servlet.RateLimitManager.DEFAULT_SLOT_ACQUISITION_TIMEOUT_MS;
/** Implementation of RequestRateLimiter specific to query request types. Most of the actual work is delegated /** 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. * to the parent class but specific configurations and parsing are handled by this class.
*/ */
public class QueryRateLimiter extends RequestRateLimiter { public class QueryRateLimiter extends RequestRateLimiter {
final static String IS_QUERY_RATE_LIMITER_ENABLED = "isQueryRateLimiterEnabled"; private static final ObjectMapper mapper = SolrJacksonAnnotationInspector.createObjectMapper();
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";
public QueryRateLimiter(FilterConfig filterConfig) { public QueryRateLimiter(SolrZkClient solrZkClient) {
super(constructQueryRateLimiterConfig(filterConfig)); super(constructQueryRateLimiterConfig(solrZkClient));
} }
protected static RequestRateLimiter.RateLimiterConfig constructQueryRateLimiterConfig(FilterConfig filterConfig) { public void processConfigChange(Map<String, Object> properties) throws IOException {
RequestRateLimiter.RateLimiterConfig queryRateLimiterConfig = new RequestRateLimiter.RateLimiterConfig(); RateLimiterConfig rateLimiterConfig = getRateLimiterConfig();
byte[] configInput = Utils.toJSON(properties.get(RL_CONFIG_KEY));
queryRateLimiterConfig.requestType = SolrRequest.SolrRequestType.QUERY; if (configInput == null) {
queryRateLimiterConfig.isEnabled = getParamAndParseBoolean(filterConfig, IS_QUERY_RATE_LIMITER_ENABLED, false); return;
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);
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<String, Object> clusterPropsJson = (Map<String, Object>) 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();
}
} }
} }

View File

@ -17,8 +17,8 @@
package org.apache.solr.servlet; package org.apache.solr.servlet;
import javax.servlet.FilterConfig;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -26,6 +26,8 @@ import java.util.concurrent.ConcurrentHashMap;
import org.apache.solr.client.solrj.SolrRequest; import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.common.annotation.SolrThreadSafe; 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.Logger;
import org.slf4j.LoggerFactory; 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. * rate limiting is being done for a specific request type.
*/ */
@SolrThreadSafe @SolrThreadSafe
public class RateLimitManager { public class RateLimitManager implements ClusterPropertiesListener {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
public final static int DEFAULT_CONCURRENT_REQUESTS= (Runtime.getRuntime().availableProcessors()) * 3; public final static int DEFAULT_CONCURRENT_REQUESTS= (Runtime.getRuntime().availableProcessors()) * 3;
@ -56,6 +58,23 @@ public class RateLimitManager {
this.activeRequestsMap = new ConcurrentHashMap<>(); this.activeRequestsMap = new ConcurrentHashMap<>();
} }
@Override
public boolean onChange(Map<String, Object> 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 // 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 // identify which (if any) rate limiter can handle this request. Internal requests will not be
// rate limited // rate limited
@ -164,16 +183,16 @@ public class RateLimitManager {
} }
public static class Builder { public static class Builder {
protected FilterConfig config; protected SolrZkClient solrZkClient;
public Builder(FilterConfig config) { public Builder(SolrZkClient solrZkClient) {
this.config = config; this.solrZkClient = solrZkClient;
} }
public RateLimitManager build() { public RateLimitManager build() {
RateLimitManager rateLimitManager = new RateLimitManager(); RateLimitManager rateLimitManager = new RateLimitManager();
rateLimitManager.registerRequestRateLimiter(new QueryRateLimiter(config), SolrRequest.SolrRequestType.QUERY); rateLimitManager.registerRequestRateLimiter(new QueryRateLimiter(solrZkClient), SolrRequest.SolrRequestType.QUERY);
return rateLimitManager; return rateLimitManager;
} }

View File

@ -17,12 +17,11 @@
package org.apache.solr.servlet; package org.apache.solr.servlet;
import javax.servlet.FilterConfig;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.common.annotation.SolrThreadSafe; import org.apache.solr.common.annotation.SolrThreadSafe;
import org.apache.solr.core.RateLimiterConfig;
/** /**
* Handles rate limiting for a specific request type. * Handles rate limiting for a specific request type.
@ -95,58 +94,6 @@ public class RequestRateLimiter {
return rateLimiterConfig; 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 // Represents the metadata for a slot
static class SlotMetadata { static class SlotMetadata {
private final Semaphore usedPool; private final Semaphore usedPool;

View File

@ -64,6 +64,7 @@ import org.apache.http.HttpHeaders;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.lucene.util.Version; import org.apache.lucene.util.Version;
import org.apache.solr.api.V2HttpCall; import org.apache.solr.api.V2HttpCall;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode; import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.cloud.SolrZkClient; import org.apache.solr.common.cloud.SolrZkClient;
@ -187,9 +188,22 @@ public class SolrDispatchFilter extends BaseSolrFilter {
coresInit = createCoreContainer(solrHomePath, extraProperties); coresInit = createCoreContainer(solrHomePath, extraProperties);
this.httpClient = coresInit.getUpdateShardHandler().getDefaultHttpClient(); this.httpClient = coresInit.getUpdateShardHandler().getDefaultHttpClient();
setupJvmMetrics(coresInit); 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(); this.rateLimitManager = builder.build();
if (zkController != null) {
zkController.zkStateReader.registerClusterPropertiesListener(this.rateLimitManager);
}
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("user.dir={}", System.getProperty("user.dir")); log.debug("user.dir={}", System.getProperty("user.dir"));
} }

View File

@ -17,7 +17,6 @@
package org.apache.solr.servlet; package org.apache.solr.servlet;
import javax.servlet.FilterConfig;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable; 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.client.solrj.response.QueryResponse;
import org.apache.solr.cloud.SolrCloudTestCase; import org.apache.solr.cloud.SolrCloudTestCase;
import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.util.ExecutorUtil; import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.core.RateLimiterConfig;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
@ -58,10 +59,10 @@ public class TestRequestRateLimiter extends SolrCloudTestCase {
SolrDispatchFilter solrDispatchFilter = cluster.getJettySolrRunner(0).getSolrDispatchFilter(); 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 */); 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 // 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(); RateLimitManager rateLimitManager = builder.build();
solrDispatchFilter.replaceRateLimitManager(rateLimitManager); solrDispatchFilter.replaceRateLimitManager(rateLimitManager);
@ -91,12 +92,12 @@ public class TestRequestRateLimiter extends SolrCloudTestCase {
SolrDispatchFilter solrDispatchFilter = cluster.getJettySolrRunner(0).getSolrDispatchFilter(); 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 */); 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 */); 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 // 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(); RateLimitManager rateLimitManager = builder.build();
solrDispatchFilter.replaceRateLimitManager(rateLimitManager); solrDispatchFilter.replaceRateLimitManager(rateLimitManager);
@ -205,15 +206,15 @@ public class TestRequestRateLimiter extends SolrCloudTestCase {
private final RequestRateLimiter queryRequestRateLimiter; private final RequestRateLimiter queryRequestRateLimiter;
private final RequestRateLimiter indexRequestRateLimiter; private final RequestRateLimiter indexRequestRateLimiter;
public MockBuilder(FilterConfig config, RequestRateLimiter queryRequestRateLimiter) { public MockBuilder(SolrZkClient zkClient, RequestRateLimiter queryRequestRateLimiter) {
super(config); super(zkClient);
this.queryRequestRateLimiter = queryRequestRateLimiter; this.queryRequestRateLimiter = queryRequestRateLimiter;
this.indexRequestRateLimiter = null; this.indexRequestRateLimiter = null;
} }
public MockBuilder(FilterConfig config, RequestRateLimiter queryRequestRateLimiter, RequestRateLimiter indexRequestRateLimiter) { public MockBuilder(SolrZkClient zkClient, RequestRateLimiter queryRequestRateLimiter, RequestRateLimiter indexRequestRateLimiter) {
super(config); super(zkClient);
this.queryRequestRateLimiter = queryRequestRateLimiter; this.queryRequestRateLimiter = queryRequestRateLimiter;
this.indexRequestRateLimiter = indexRequestRateLimiter; this.indexRequestRateLimiter = indexRequestRateLimiter;

View File

@ -31,37 +31,27 @@ pronounced under high stress in production workloads. The current implementation
resources for indexing. resources for indexing.
== Rate Limiter Configurations == Rate Limiter Configurations
The default rate limiter is search rate limiter. Accordingly, it can be configured in `web.xml` under `initParams` for The default rate limiter is search rate limiter. Accordingly, it can be configured using the following command:
`SolrRequestFilter`.
[source,xml] curl -X POST -H 'Content-type:application/json' -d '{
---- "set-ratelimiter": {
<filter-name>SolrRequestFilter</filter-name> "enabled": true,
---- "guaranteedSlots":5,
"allowedRequests":20,
"slotBorrowingEnabled":true,
"slotAcquisitionTimeoutInMS":70
}
}' http://localhost:8983/api/cluster
=== Enable Query Rate Limiter === Enable Query Rate Limiter
Controls enabling of query rate limiter. Default value is `false`. Controls enabling of query rate limiter. Default value is `false`.
[source,xml] "enabled": true
----
<param-name>isQueryRateLimiterEnabled</param-name>
----
[source,xml]
----
<param-value>true</param-value>
----
=== Maximum Number Of Concurrent Requests === Maximum Number Of Concurrent Requests
Allows setting maximum concurrent search requests at a given point in time. Default value is number of cores * 3. Allows setting maximum concurrent search requests at a given point in time. Default value is number of cores * 3.
[source,xml] "allowedRequests":20
----
<param-name>maxQueryRequests</param-name>
----
[source,xml]
----
<param-value>15</param-value>
----
=== Request Slot Allocation Wait Time === 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, 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 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 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. can lead to larger queue times and can potentially lead to longer wait times for queries.
[source,xml]
---- "slotAcquisitionTimeoutInMS":70
<param-name>queryWaitForSlotAllocationInMS</param-name>
----
[source,xml]
----
<param-value>100</param-value>
----
=== Slot Borrowing Enabled === Slot Borrowing Enabled
If slot borrowing (described below) is enabled or not. Default value is false. 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 NOTE: This feature is experimental and can cause slots to be blocked if the
borrowing request is long lived. borrowing request is long lived.
[source,xml] "slotBorrowingEnabled":true,
----
<param-name>queryAllowSlotBorrowing</param-name>
----
[source,xml]
----
<param-value>true</param-value>
----
=== Guaranteed Slots === Guaranteed Slots
The number of guaranteed slots that the query rate limiter will reserve irrespective 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 NOTE: This feature is experimental and can cause slots to be blocked if the
borrowing request is long lived. borrowing request is long lived.
[source,xml] "guaranteedSlots":5,
----
<param-name>queryGuaranteedSlots</param-name>
----
[source,xml]
----
<param-value>200</param-value>
----
== Salient Points == Salient Points

View File

@ -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);
}
}