diff options
author | Bjørn Christian Seime <bjorncs@verizonmedia.com> | 2020-06-15 13:18:44 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-15 13:18:44 +0200 |
commit | 0a6b384693e07bd4d07757d23e710c7721e6066a (patch) | |
tree | 1f1951b02e5b604eec010d1abc65098896a4de1d | |
parent | f23863f6f7698d4fc34392c59977447988a86754 (diff) | |
parent | a7fc7785961dafa2276c97d207d6a5af322ba2ea (diff) |
Merge pull request #13525 from vespa-engine/bjorncs/container-thread-pool
Bjorncs/container thread pool
8 files changed, 160 insertions, 22 deletions
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java index 2fa3217ef4a..49e7ae39962 100644 --- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java +++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java @@ -106,6 +106,9 @@ public interface ModelContext { // TODO(bjorncs): Temporary feature flag, revisit July 2020 default Duration jdiscHealthCheckProxyClientTimeout() { return Duration.ofSeconds(1); } + + // TODO(bjorncs): Temporary feature flag + default double feedCoreThreadPoolSizeFactor() { return 1.0; } } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/clients/ContainerDocumentApi.java b/config-model/src/main/java/com/yahoo/vespa/model/clients/ContainerDocumentApi.java index 58f03bffb30..cdebf6a8c6c 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/clients/ContainerDocumentApi.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/clients/ContainerDocumentApi.java @@ -5,46 +5,99 @@ import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.container.bundle.BundleInstantiationSpecification; import com.yahoo.osgi.provider.model.ComponentModel; import com.yahoo.vespa.model.container.ContainerCluster; +import com.yahoo.vespa.model.container.ThreadPoolExecutorComponent; import com.yahoo.vespa.model.container.component.Handler; import java.util.Collection; import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; /** * @author Einar M R Rosenvinge + * @author bjorncs */ public class ContainerDocumentApi { - private static final String vespaClientBundleSpecification = "vespaclient-container-plugin"; + private static final int FALLBACK_MAX_POOL_SIZE = 0; // Use fallback based on actual logical core count on host + private static final int FALLBACK_CORE_POOL_SIZE = 0; // Use fallback based on actual logical core count on host + + private final ContainerCluster<?> cluster; private final Options options; + private final Handler<AbstractConfigProducer<?>> feedHandler; + private final Handler<AbstractConfigProducer<?>> restApiHandler; - public ContainerDocumentApi(ContainerCluster cluster, Options options) { + public ContainerDocumentApi(ContainerCluster<?> cluster, Options options) { + this.cluster = cluster; this.options = options; - setupHandlers(cluster); + this.restApiHandler = addRestApiHandler(cluster, options); + this.feedHandler = addFeedHandler(cluster, options); } - private void setupHandlers(ContainerCluster cluster) { - cluster.addComponent(newVespaClientHandler("com.yahoo.document.restapi.resource.RestApi", "document/v1/*")); - cluster.addComponent(newVespaClientHandler("com.yahoo.vespa.http.server.FeedHandler", ContainerCluster.RESERVED_URI_PREFIX + "/feedapi")); + public void addNodesDependentThreadpoolConfiguration() { + if (cluster.getContainers().isEmpty()) throw new IllegalStateException("Cluster is empty"); + feedHandler.addComponent(newExecutorComponent("feedapi-handler", cluster, options)); + restApiHandler.addComponent(newExecutorComponent("restapi-handler", cluster, options)); } - private Handler<AbstractConfigProducer<?>> newVespaClientHandler(String componentId, String bindingSuffix) { - Handler<AbstractConfigProducer<?>> handler = new Handler<>(new ComponentModel( - BundleInstantiationSpecification.getFromStrings(componentId, null, vespaClientBundleSpecification), "")); + private static Handler<AbstractConfigProducer<?>> addFeedHandler(ContainerCluster<?> cluster, Options options) { + String bindingSuffix = ContainerCluster.RESERVED_URI_PREFIX + "/feedapi"; + var handler = newVespaClientHandler( + "com.yahoo.vespa.http.server.FeedHandler", bindingSuffix, options); + cluster.addComponent(handler); + return handler; + } + private static Handler<AbstractConfigProducer<?>> addRestApiHandler(ContainerCluster<?> cluster, Options options) { + var handler = newVespaClientHandler( + "com.yahoo.document.restapi.resource.RestApi", "document/v1/*", options); + cluster.addComponent(handler); + return handler; + } + + private static ThreadPoolExecutorComponent newExecutorComponent(String name, ContainerCluster<?> cluster, Options options) { + int maxPoolSize = maxPoolSize(cluster); + return new ThreadPoolExecutorComponent.Builder(name) + .maxPoolSize(maxPoolSize) + .corePoolSize(corePoolSize(maxPoolSize, options)) + .queueSize(500) + .build(); + } + + private static Handler<AbstractConfigProducer<?>> newVespaClientHandler( + String componentId, + String bindingSuffix, + Options options) { + Handler<AbstractConfigProducer<?>> handler = new Handler<>(new ComponentModel( + BundleInstantiationSpecification.getFromStrings(componentId, null, "vespaclient-container-plugin"), "")); for (String rootBinding : options.bindings) { - handler.addServerBindings(rootBinding + bindingSuffix, - rootBinding + bindingSuffix + '/'); + handler.addServerBindings(rootBinding + bindingSuffix, rootBinding + bindingSuffix + '/'); } return handler; } + private static int maxPoolSize(ContainerCluster<?> cluster) { + List<Double> vcpus = cluster.getContainers().stream() + .map(c -> c.getHostResource().realResources().vcpu()) + .distinct() + .collect(Collectors.toList()); + // We can only use host resource for calculation if all container nodes in the cluster are homogeneous (in terms of vcpu) + if (vcpus.size() != 1 || vcpus.get(0) == 0) return FALLBACK_MAX_POOL_SIZE; + return (int)Math.ceil(vcpus.get(0)); + } + + private static int corePoolSize(int maxPoolSize, Options options) { + if (maxPoolSize == FALLBACK_MAX_POOL_SIZE) return FALLBACK_CORE_POOL_SIZE; + return (int) Math.ceil(options.feedCoreThreadPoolSizeFactor * maxPoolSize); + } + public static final class Options { private final Collection<String> bindings; + private final double feedCoreThreadPoolSizeFactor; - - public Options(Collection<String> bindings) { + public Options(Collection<String> bindings, double feedCoreThreadPoolSizeFactor) { this.bindings = Collections.unmodifiableCollection(bindings); + this.feedCoreThreadPoolSizeFactor = feedCoreThreadPoolSizeFactor; } } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java index b83632a58a0..4cf3108f928 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java @@ -192,7 +192,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { addProcessing(deployState, spec, cluster); addSearch(deployState, spec, cluster); addDocproc(deployState, spec, cluster); - addDocumentApi(spec, cluster); // NOTE: Must be done after addSearch + addDocumentApi(deployState, spec, cluster); // NOTE: Must be done after addSearch cluster.addDefaultHandlersExceptStatus(); addStatusHandlers(cluster, context.getDeployState().isHosted()); @@ -207,6 +207,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { addClientProviders(deployState, spec, cluster); addServerProviders(deployState, spec, cluster); + addHandlerSpecificThreadpools(cluster); addAthensCopperArgos(cluster, context); // Must be added after nodes. } @@ -221,6 +222,13 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { } } + private void addHandlerSpecificThreadpools(ContainerCluster<?> cluster) { + ContainerDocumentApi documentApi = cluster.getDocumentApi(); + if (documentApi != null) { + documentApi.addNodesDependentThreadpoolConfiguration(); + } + } + private void addAthensCopperArgos(ApplicationContainerCluster cluster, ConfigModelContext context) { if ( ! context.getDeployState().isHosted()) return; app.getDeployment().map(DeploymentSpec::fromXml) @@ -409,8 +417,8 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { cluster.addServlet(new ServletBuilder().build(deployState, cluster, servletElem)); } - private void addDocumentApi(Element spec, ApplicationContainerCluster cluster) { - ContainerDocumentApi containerDocumentApi = buildDocumentApi(cluster, spec); + private void addDocumentApi(DeployState deployState, Element spec, ApplicationContainerCluster cluster) { + ContainerDocumentApi containerDocumentApi = buildDocumentApi(deployState, cluster, spec); if (containerDocumentApi == null) return; cluster.setDocumentApi(containerDocumentApi); @@ -839,11 +847,11 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { return result.toArray(new String[result.size()]); } - private ContainerDocumentApi buildDocumentApi(ApplicationContainerCluster cluster, Element spec) { + private ContainerDocumentApi buildDocumentApi(DeployState deployState, ApplicationContainerCluster cluster, Element spec) { Element documentApiElement = XML.getChild(spec, "document-api"); if (documentApiElement == null) return null; - ContainerDocumentApi.Options documentApiOptions = DocumentApiOptionsBuilder.build(documentApiElement); + ContainerDocumentApi.Options documentApiOptions = DocumentApiOptionsBuilder.build(deployState, documentApiElement); return new ContainerDocumentApi(cluster, documentApiOptions); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/DocumentApiOptionsBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/DocumentApiOptionsBuilder.java index ae74dbdb4a7..bbeeffda612 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/DocumentApiOptionsBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/DocumentApiOptionsBuilder.java @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.container.xml; +import com.yahoo.config.model.deploy.DeployState; import com.yahoo.text.XML; import com.yahoo.vespa.model.clients.ContainerDocumentApi; import org.w3c.dom.Element; @@ -20,8 +21,8 @@ public class DocumentApiOptionsBuilder { private static final Logger log = Logger.getLogger(DocumentApiOptionsBuilder.class.getName()); private static final String[] DEFAULT_BINDINGS = {"http://*/"}; - public static ContainerDocumentApi.Options build(Element spec) { - return new ContainerDocumentApi.Options(getBindings(spec)); + public static ContainerDocumentApi.Options build(DeployState deployState, Element spec) { + return new ContainerDocumentApi.Options(getBindings(spec), deployState.getProperties().feedCoreThreadPoolSizeFactor()); } private static List<String> getBindings(Element spec) { diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerDocumentApiBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerDocumentApiBuilderTest.java index ac2e1b88c0b..15a995e08c7 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerDocumentApiBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerDocumentApiBuilderTest.java @@ -1,21 +1,38 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.container.xml; +import com.yahoo.config.model.api.HostProvisioner; import com.yahoo.config.model.builder.xml.test.DomBuilderTest; +import com.yahoo.config.model.provision.Host; +import com.yahoo.config.model.test.MockApplicationPackage; +import com.yahoo.config.model.test.MockRoot; +import com.yahoo.config.provision.Capacity; +import com.yahoo.config.provision.ClusterMembership; +import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.config.provision.HostSpec; +import com.yahoo.config.provision.NodeResources; +import com.yahoo.config.provision.ProvisionLogger; +import com.yahoo.container.handler.ThreadpoolConfig; +import com.yahoo.net.HostName; import com.yahoo.vespa.model.container.ContainerCluster; +import com.yahoo.vespa.model.container.ThreadPoolExecutorComponent; import com.yahoo.vespa.model.container.component.Handler; import org.junit.Test; import org.w3c.dom.Element; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Optional; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; /** @@ -24,7 +41,7 @@ import static org.junit.Assert.assertThat; public class ContainerDocumentApiBuilderTest extends ContainerModelBuilderTestBase { private Map<String, Handler<?>> getHandlers(String clusterName) { - ContainerCluster cluster = (ContainerCluster) root.getChildren().get(clusterName); + ContainerCluster<?> cluster = (ContainerCluster<?>) root.getChildren().get(clusterName); Map<String, Handler<?>> handlerMap = new HashMap<>(); Collection<Handler<?>> handlers = cluster.getHandlers(); for (Handler<?> handler : handlers) { @@ -80,4 +97,47 @@ public class ContainerDocumentApiBuilderTest extends ContainerModelBuilderTestBa assertThat(handlerMap.get("com.yahoo.vespa.http.server.FeedHandler").getServerBindings().contains("http://*/" + ContainerCluster.RESERVED_URI_PREFIX + "/feedapi/"), is(true)); assertThat(handlerMap.get("com.yahoo.vespa.http.server.FeedHandler").getServerBindings().size(), equalTo(2)); } + + @Test + public void feeding_api_have_separate_threadpools() { + Element elem = DomBuilderTest.parse( + "<container id='cluster1' version='1.0'>", + " <document-api />", + nodesXml, + "</container>"); + root = new MockRoot("root", new MockApplicationPackage.Builder().build(), new HostProvisionerWithCustomRealResource()); + createModel(root, elem); + Map<String, Handler<?>> handlers = getHandlers("cluster1"); + Handler<?> feedApiHandler = handlers.get("com.yahoo.vespa.http.server.FeedHandler"); + List<ThreadPoolExecutorComponent> feedApiExecutors = feedApiHandler.getChildrenByTypeRecursive(ThreadPoolExecutorComponent.class); + assertThat(feedApiExecutors, hasSize(1)); + ThreadpoolConfig.Builder configBuilder = new ThreadpoolConfig.Builder(); + feedApiExecutors.get(0).getConfig(configBuilder); + ThreadpoolConfig config = new ThreadpoolConfig(configBuilder); + assertEquals(4, config.maxthreads()); + assertEquals(4, config.corePoolSize()); + } + + private static class HostProvisionerWithCustomRealResource implements HostProvisioner { + + @Override + public HostSpec allocateHost(String alias) { + Host host = new Host(HostName.getLocalhost()); + ClusterMembership membership = ClusterMembership.from( + ClusterSpec + .specification( + ClusterSpec.Type.container, + ClusterSpec.Id.from("id")) + .vespaVersion("") + .group(ClusterSpec.Group.from(0)) + .build(), + 0); + return new HostSpec( + host.hostname(), new NodeResources(4, 0, 0, 0), NodeResources.unspecified(), NodeResources.unspecified(), + membership, Optional.empty(), Optional.empty(), Optional.empty()); + } + + @Override public List<HostSpec> prepare(ClusterSpec cluster, Capacity capacity, ProvisionLogger logger) { return List.of(); } + } + } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java index 3a2bc9f1a81..bb86966640e 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java @@ -158,6 +158,7 @@ public class ModelContextImpl implements ModelContext { private final Optional<AthenzDomain> athenzDomain; private final Optional<ApplicationRoles> applicationRoles; private final int jdiscHealthCheckProxyClientTimeout; + private final double feedCoreThreadPoolSizeFactor; public Properties(ApplicationId applicationId, boolean multitenantFromConfig, @@ -204,6 +205,8 @@ public class ModelContextImpl implements ModelContext { this.applicationRoles = applicationRoles; jdiscHealthCheckProxyClientTimeout = Flags.JDISC_HEALTH_CHECK_PROXY_CLIENT_TIMEOUT.bindTo(flagSource) .with(FetchVector.Dimension.APPLICATION_ID, applicationId.serializedForm()).value(); + feedCoreThreadPoolSizeFactor = Flags.FEED_CORE_THREAD_POOL_SIZE_FACTOR.bindTo(flagSource) + .with(FetchVector.Dimension.APPLICATION_ID, applicationId.serializedForm()).value(); } @Override @@ -283,8 +286,10 @@ public class ModelContextImpl implements ModelContext { } @Override public Duration jdiscHealthCheckProxyClientTimeout() { return Duration.ofMillis(jdiscHealthCheckProxyClientTimeout); } + @Override public String jvmGCOptions() { return jvmGCOPtions; } + @Override public double feedCoreThreadPoolSizeFactor() { return feedCoreThreadPoolSizeFactor; } } } diff --git a/container-core/src/main/java/com/yahoo/container/handler/threadpool/ContainerThreadPool.java b/container-core/src/main/java/com/yahoo/container/handler/threadpool/ContainerThreadPool.java index 6fc9da298a8..edbe12b64c6 100644 --- a/container-core/src/main/java/com/yahoo/container/handler/threadpool/ContainerThreadPool.java +++ b/container-core/src/main/java/com/yahoo/container/handler/threadpool/ContainerThreadPool.java @@ -83,7 +83,9 @@ public class ContainerThreadPool implements AutoCloseable { } private static int computeCoreThreadPoolSize(int corePoolSize, int maxNumThreads) { - return Math.min(corePoolSize, maxNumThreads); + return Math.min( + corePoolSize <= 0 ? Runtime.getRuntime().availableProcessors() * 2 : corePoolSize, + maxNumThreads); } } diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java index 74522f4c517..841cd590663 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java @@ -294,6 +294,12 @@ public class Flags { ZONE_ID ); + public static final UnboundDoubleFlag FEED_CORE_THREAD_POOL_SIZE_FACTOR = defineDoubleFlag( + "feed-core-thread-pool-size-factor", 1.0, + "Number of core threads in threadpool for feeding APIs as factor of max pool size", + "Takes effect on next internal redeployment", + APPLICATION_ID); + /** WARNING: public for testing: All flags should be defined in {@link Flags}. */ public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, String description, String modificationEffect, FetchVector.Dimension... dimensions) { |