Skip to content

Commit 73c74fb

Browse files
authored
Improve scalability of get-license action (#134457)
Today this action runs on the transport worker thread and forwards the request on to the master by default. It turns out that Elastic Agent uses this API as a readiness check whenever opening a new connection, so a thundering herd of 1000s of agents can prevent the transport worker threads from doing more useful work for far too long, leading to high latency and timeouts. This commit changes the default behaviour to run the action on the local node rather than forwarding to the master (although the option remains to specify `?local=false`) and dispatches the work off of the transport worker early.
1 parent fc358eb commit 73c74fb

File tree

4 files changed

+15
-6
lines changed

4 files changed

+15
-6
lines changed

docs/changelog/134457.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 134457
2+
summary: Improve scalability of get-license action
3+
area: License
4+
type: enhancement
5+
issues: []

x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestGetLicenseAction.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package org.elasticsearch.license;
99

10+
import org.elasticsearch.action.ActionRunnable;
1011
import org.elasticsearch.client.internal.node.NodeClient;
1112
import org.elasticsearch.common.logging.DeprecationCategory;
1213
import org.elasticsearch.common.logging.DeprecationLogger;
@@ -20,6 +21,7 @@
2021
import org.elasticsearch.rest.Scope;
2122
import org.elasticsearch.rest.ServerlessScope;
2223
import org.elasticsearch.rest.action.RestBuilderListener;
24+
import org.elasticsearch.threadpool.ThreadPool;
2325
import org.elasticsearch.xcontent.ToXContent;
2426
import org.elasticsearch.xcontent.XContentBuilder;
2527

@@ -77,9 +79,10 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC
7779
final ToXContent.Params params = new ToXContent.DelegatingMapParams(overrideParams, request);
7880
GetLicenseRequest getLicenseRequest = new GetLicenseRequest(RestUtils.getMasterNodeTimeout(request));
7981
getLicenseRequest.local(request.paramAsBoolean("local", getLicenseRequest.local()));
80-
return channel -> client.admin()
81-
.cluster()
82-
.execute(GetLicenseAction.INSTANCE, getLicenseRequest, new RestBuilderListener<>(channel) {
82+
return channel -> client.threadPool()
83+
.executor(ThreadPool.Names.MANAGEMENT)
84+
// dispatching to MANAGEMENT here as a workaround for https://github.com/elastic/elasticsearch/issues/97916
85+
.execute(ActionRunnable.wrap(new RestBuilderListener<GetLicenseResponse>(channel) {
8386
@Override
8487
public RestResponse buildResponse(GetLicenseResponse response, XContentBuilder builder) throws Exception {
8588
// Default to pretty printing, but allow ?pretty=false to disable
@@ -96,7 +99,7 @@ public RestResponse buildResponse(GetLicenseResponse response, XContentBuilder b
9699
builder.endObject();
97100
return new RestResponse(hasLicense ? OK : NOT_FOUND, builder);
98101
}
99-
});
102+
}, responseListener -> client.admin().cluster().execute(GetLicenseAction.INSTANCE, getLicenseRequest, responseListener)));
100103
}
101104

102105
}

x-pack/plugin/core/src/main/java/org/elasticsearch/license/TransportGetLicenseAction.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import org.elasticsearch.cluster.block.ClusterBlockException;
1515
import org.elasticsearch.cluster.block.ClusterBlockLevel;
1616
import org.elasticsearch.cluster.service.ClusterService;
17-
import org.elasticsearch.common.util.concurrent.EsExecutors;
1817
import org.elasticsearch.injection.guice.Inject;
1918
import org.elasticsearch.protocol.xpack.license.GetLicenseRequest;
2019
import org.elasticsearch.tasks.Task;
@@ -41,7 +40,7 @@ public TransportGetLicenseAction(
4140
actionFilters,
4241
GetLicenseRequest::new,
4342
GetLicenseResponse::new,
44-
EsExecutors.DIRECT_EXECUTOR_SERVICE
43+
threadPool.executor(ThreadPool.Names.MANAGEMENT)
4544
);
4645
this.licenseService = licenseService;
4746
}
@@ -58,6 +57,7 @@ protected void masterOperation(
5857
ClusterState state,
5958
final ActionListener<GetLicenseResponse> listener
6059
) throws ElasticsearchException {
60+
assert ThreadPool.assertCurrentThreadPool(ThreadPool.Names.MANAGEMENT);
6161
if (licenseService instanceof ClusterStateLicenseService clusterStateLicenseService) {
6262
listener.onResponse(new GetLicenseResponse(clusterStateLicenseService.getLicense(state.metadata())));
6363
} else {

x-pack/plugin/core/src/main/java/org/elasticsearch/protocol/xpack/license/GetLicenseRequest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class GetLicenseRequest extends MasterNodeReadRequest<GetLicenseRequest>
1717

1818
public GetLicenseRequest(TimeValue masterNodeTimeout) {
1919
super(masterNodeTimeout);
20+
local = true; // default to a local response, no need to forward to the master in most cases
2021
}
2122

2223
public GetLicenseRequest(StreamInput in) throws IOException {

0 commit comments

Comments
 (0)