diff options
22 files changed, 314 insertions, 96 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/AccessControlFilterExcludeValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/AccessControlFilterExcludeValidator.java new file mode 100644 index 00000000000..ef695770987 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/AccessControlFilterExcludeValidator.java @@ -0,0 +1,44 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.model.application.validation; + +import com.yahoo.config.model.deploy.DeployState; +import com.yahoo.config.provision.CloudName; +import com.yahoo.vespa.model.VespaModel; +import com.yahoo.vespa.model.container.http.AccessControl; +import com.yahoo.vespa.model.container.http.Http; + +import java.util.logging.Level; + +/** + * Validates that 'access-control' does not include any exclusions unless explicitly allowed. + * Logs in Yahoo clouds and fails in AWS clouds + * + * @author mortent + */ +public class AccessControlFilterExcludeValidator extends Validator { + + @Override + public void validate(VespaModel model, DeployState deployState) { + if (!deployState.isHosted() || deployState.zone().system().isPublic()) return; + if (deployState.getProperties().allowDisableMtls()) return; + model.getContainerClusters().forEach((id, cluster) -> { + Http http = cluster.getHttp(); + if (http != null) { + if (http.getAccessControl().isPresent()) { + verifyNoExclusions(id, http.getAccessControl().get(), deployState); + } + } + }); + } + + private void verifyNoExclusions(String clusterId, AccessControl accessControl, DeployState deployState) { + if (!accessControl.excludedBindings().isEmpty()) { + String message = "Application cluster %s excludes paths from access control, this is not allowed and should be removed.".formatted(clusterId); + if (deployState.zone().cloud().name() == CloudName.AWS) { + throw new IllegalArgumentException(message); + } else { + deployState.getDeployLogger().log(Level.WARNING, message); + } + } + } +} diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java index 781675b9a30..a558902d6ff 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java @@ -88,6 +88,7 @@ public class Validation { new QuotaValidator().validate(model, deployState); new UriBindingsValidator().validate(model, deployState); new CloudDataPlaneFilterValidator().validate(model, deployState); + new AccessControlFilterExcludeValidator().validate(model, deployState); additionalValidators.forEach(v -> v.validate(model, deployState)); diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/CertificateRemovalChangeValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/CertificateRemovalChangeValidator.java index 1df33ab8517..5e5d5e3437c 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/CertificateRemovalChangeValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/change/CertificateRemovalChangeValidator.java @@ -1,3 +1,4 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.application.validation.change; import com.yahoo.config.application.api.ValidationId; @@ -10,8 +11,19 @@ import java.security.cert.X509Certificate; import java.time.Instant; import java.util.Collection; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +/** + * Check that data plane certificates are not removed from a cluster. + * + * @author mortent + */ public class CertificateRemovalChangeValidator implements ChangeValidator { + + private static final Logger logger = Logger.getLogger(CertificateRemovalChangeValidator.class.getName()); + @Override public List<ConfigChangeAction> validate(VespaModel current, VespaModel next, ValidationOverrides overrides, Instant now) { @@ -25,7 +37,6 @@ public class CertificateRemovalChangeValidator implements ChangeValidator { } void validateClients(String clusterId, List<Client> current, List<Client> next, ValidationOverrides overrides, Instant now) { - List<X509Certificate> currentCertificates = current.stream() .map(Client::certificates) .flatMap(Collection::stream) @@ -35,6 +46,11 @@ public class CertificateRemovalChangeValidator implements ChangeValidator { .flatMap(Collection::stream) .toList(); + logger.log(Level.FINE, "Certificates for cluster %s: Current: [%s], Next: [%s]" + .formatted(clusterId, + currentCertificates.stream().map(cert -> cert.getSubjectX500Principal().getName()).collect(Collectors.joining(", ")), + nextCertificates.stream().map(cert -> cert.getSubjectX500Principal().getName()).collect(Collectors.joining(", ")))); + List<X509Certificate> missingCerts = currentCertificates.stream().filter(cert -> !nextCertificates.contains(cert)).toList(); if (!missingCerts.isEmpty()) { overrides.invalid(ValidationId.certificateRemoval, diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/AccessControlFilterExcludeValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/AccessControlFilterExcludeValidatorTest.java new file mode 100644 index 00000000000..511d3da7b17 --- /dev/null +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/AccessControlFilterExcludeValidatorTest.java @@ -0,0 +1,112 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.model.application.validation; + +import com.yahoo.config.application.api.DeployLogger; +import com.yahoo.config.model.MapConfigModelRegistry; +import com.yahoo.config.model.deploy.DeployState; +import com.yahoo.config.model.deploy.TestProperties; +import com.yahoo.config.model.test.MockApplicationPackage; +import com.yahoo.config.model.test.ModelBuilderAddingAccessControlFilter; +import com.yahoo.config.provision.AthenzDomain; +import com.yahoo.config.provision.Cloud; +import com.yahoo.config.provision.CloudAccount; +import com.yahoo.config.provision.CloudName; +import com.yahoo.config.provision.Environment; +import com.yahoo.config.provision.RegionName; +import com.yahoo.config.provision.SystemName; +import com.yahoo.config.provision.Zone; +import com.yahoo.vespa.model.VespaModel; +import org.junit.jupiter.api.Test; +import org.xml.sax.SAXException; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author mortent + */ +public class AccessControlFilterExcludeValidatorTest { + private static final String SERVICES_XML = """ + <services version='1.0'> + <container id='container-cluster-with-access-control' version='1.0'> + <http> + <filtering> + <access-control> + <exclude> + <binding>http://*/foo/</binding> + </exclude> + </access-control> + </filtering> + </http> + </container> + </services>"""; + + + @Test + public void validator_rejects_excludes_in_cloud() throws IOException, SAXException { + DeployState deployState = createDeployState(zone(CloudName.AWS, SystemName.main), new StringBuffer(), false); + VespaModel model = new VespaModel( + MapConfigModelRegistry.createFromList(new ModelBuilderAddingAccessControlFilter()), + deployState); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> new AccessControlFilterExcludeValidator().validate(model, deployState)); + String expectedMessage = "Application cluster container-cluster-with-access-control excludes paths from access control, this is not allowed and should be removed."; + assertEquals(expectedMessage, exception.getMessage()); + } + + @Test + public void validator_warns_excludes_in_cloud() throws IOException, SAXException { + StringBuffer logOutput = new StringBuffer(); + DeployState deployState = createDeployState(zone(CloudName.YAHOO, SystemName.main), logOutput, false); + VespaModel model = new VespaModel( + MapConfigModelRegistry.createFromList(new ModelBuilderAddingAccessControlFilter()), + deployState); + + new AccessControlFilterExcludeValidator().validate(model, deployState); + String expectedMessage = "Application cluster container-cluster-with-access-control excludes paths from access control, this is not allowed and should be removed."; + assertTrue(logOutput.toString().contains(expectedMessage)); + } + + @Test + public void validator_accepts_when_allowed_to_exclude() throws IOException, SAXException { + DeployState deployState = createDeployState(zone(CloudName.AWS, SystemName.main), new StringBuffer(), true); + VespaModel model = new VespaModel( + MapConfigModelRegistry.createFromList(new ModelBuilderAddingAccessControlFilter()), + deployState); + new AccessControlFilterExcludeValidator().validate(model, deployState); + } + + @Test + public void validator_accepts_public_deployments() throws IOException, SAXException { + DeployState deployState = createDeployState(zone(CloudName.AWS, SystemName.Public), new StringBuffer(), false); + VespaModel model = new VespaModel( + MapConfigModelRegistry.createFromList(new ModelBuilderAddingAccessControlFilter()), + deployState); + + new AccessControlFilterExcludeValidator().validate(model, deployState); + } + + private static DeployState createDeployState(Zone zone, StringBuffer buffer, boolean allowExcludes) { + DeployLogger logger = (__, message) -> buffer.append(message).append('\n'); + return new DeployState.Builder() + .applicationPackage(new MockApplicationPackage.Builder().withServices(SERVICES_XML).build()) + .properties( + new TestProperties() + .setHostedVespa(true) + .setAthenzDomain(AthenzDomain.from("foo.bar")) + .allowDisableMtls(allowExcludes)) + .deployLogger(logger) + .zone(zone) + .build(); + } + + private static Zone zone(CloudName cloudName, SystemName systemName) { + Cloud.Builder cloudBuilder = Cloud.builder().name(cloudName); + if (cloudName == CloudName.AWS) cloudBuilder.account(CloudAccount.from("123456789012")); + return new Zone(cloudBuilder.build(), systemName, Environment.prod, RegionName.defaultName()); + + } +} diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/CertificateRemovalChangeValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/CertificateRemovalChangeValidatorTest.java index f89c75362da..b6815db8b99 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/CertificateRemovalChangeValidatorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/CertificateRemovalChangeValidatorTest.java @@ -1,3 +1,4 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.model.application.validation.change; import com.yahoo.config.application.api.ValidationOverrides; @@ -15,6 +16,9 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.assertThrows; +/** + * @author mortent + */ public class CertificateRemovalChangeValidatorTest { private static final String validationOverrides = diff --git a/model-evaluation/src/test/java/ai/vespa/models/handler/ModelsEvaluationHandlerTest.java b/model-evaluation/src/test/java/ai/vespa/models/handler/ModelsEvaluationHandlerTest.java index d804e50c67d..c52bf66626a 100644 --- a/model-evaluation/src/test/java/ai/vespa/models/handler/ModelsEvaluationHandlerTest.java +++ b/model-evaluation/src/test/java/ai/vespa/models/handler/ModelsEvaluationHandlerTest.java @@ -14,6 +14,7 @@ import com.yahoo.vespa.config.search.core.OnnxModelsConfig; import com.yahoo.vespa.config.search.core.RankingConstantsConfig; import com.yahoo.vespa.config.search.core.RankingExpressionsConfig; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import java.util.HashMap; @@ -261,11 +262,13 @@ public class ModelsEvaluationHandlerTest { "tensor(a[2],b[2],c{},d[2]):{a:[[[1.0, 2.0], [3.0, 4.0]], [[5.0, 6.0], [7.0, 8.0]]], b:[[[1.0, 2.0], [3.0, 4.0]], [[5.0, 6.0], [7.0, 8.0]]]}"); } + @Ignore @Test public void testMnistSavedEvaluateSpecificFunction() { assumeTrue(OnnxEvaluator.isRuntimeAvailable()); Map<String, String> properties = new HashMap<>(); properties.put("input", inputTensor()); + properties.put("format.tensors", "long"); String url = "http://localhost/model-evaluation/v1/mnist_saved/serving_default.y/eval"; String expected = "{\"cells\":[{\"address\":{\"d0\":\"0\",\"d1\":\"0\"},\"value\":-0.6319251673007533},{\"address\":{\"d0\":\"0\",\"d1\":\"1\"},\"value\":-7.577770600619843E-4},{\"address\":{\"d0\":\"0\",\"d1\":\"2\"},\"value\":-0.010707969042025622},{\"address\":{\"d0\":\"0\",\"d1\":\"3\"},\"value\":-0.6344759233540788},{\"address\":{\"d0\":\"0\",\"d1\":\"4\"},\"value\":-0.17529455385847528},{\"address\":{\"d0\":\"0\",\"d1\":\"5\"},\"value\":0.7490809723192187},{\"address\":{\"d0\":\"0\",\"d1\":\"6\"},\"value\":-0.022790284182901716},{\"address\":{\"d0\":\"0\",\"d1\":\"7\"},\"value\":0.26799240657608936},{\"address\":{\"d0\":\"0\",\"d1\":\"8\"},\"value\":-0.3152438845465862},{\"address\":{\"d0\":\"0\",\"d1\":\"9\"},\"value\":0.05949304847735276}]}"; handler.assertResponse(url, properties, 200, expected); diff --git a/model-evaluation/src/test/java/ai/vespa/models/handler/OnnxEvaluationHandlerTest.java b/model-evaluation/src/test/java/ai/vespa/models/handler/OnnxEvaluationHandlerTest.java index 8ab282668da..a9a36abe337 100644 --- a/model-evaluation/src/test/java/ai/vespa/models/handler/OnnxEvaluationHandlerTest.java +++ b/model-evaluation/src/test/java/ai/vespa/models/handler/OnnxEvaluationHandlerTest.java @@ -12,6 +12,7 @@ import com.yahoo.vespa.config.search.core.OnnxModelsConfig; import com.yahoo.vespa.config.search.core.RankingConstantsConfig; import com.yahoo.vespa.config.search.core.RankingExpressionsConfig; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import java.io.File; @@ -31,6 +32,7 @@ public class OnnxEvaluationHandlerTest { handler = new HandlerTester(createModels()); } + @Ignore @Test public void testListModels() { String url = "http://localhost/model-evaluation/v1"; @@ -40,6 +42,7 @@ public class OnnxEvaluationHandlerTest { handler.assertResponse(url, 200, expected); } + @Ignore @Test public void testModelInfo() { String url = "http://localhost/model-evaluation/v1/add_mul"; @@ -80,6 +83,7 @@ public class OnnxEvaluationHandlerTest { Map<String, String> properties = new HashMap<>(); properties.put("input1", "tensor<float>(d0[1]):[2]"); properties.put("input2", "tensor<float>(d0[1]):[3]"); + properties.put("format.tensors", "long"); String url = "http://localhost/model-evaluation/v1/add_mul/output1/eval"; String expected = "{\"cells\":[{\"address\":{\"d0\":\"0\"},\"value\":6.0}]}"; // output1 is a mul handler.assertResponse(url, properties, 200, expected); @@ -90,6 +94,7 @@ public class OnnxEvaluationHandlerTest { Map<String, String> properties = new HashMap<>(); properties.put("input1", "tensor<float>(d0[1]):[2]"); properties.put("input2", "tensor<float>(d0[1]):[3]"); + properties.put("format.tensors", "long"); String url = "http://localhost/model-evaluation/v1/add_mul/output2/eval"; String expected = "{\"cells\":[{\"address\":{\"d0\":\"0\"},\"value\":5.0}]}"; // output2 is an add handler.assertResponse(url, properties, 200, expected); @@ -108,6 +113,7 @@ public class OnnxEvaluationHandlerTest { handler.assertResponse(url, 200, expected); } + @Ignore @Test public void testBatchDimensionEvaluation() { Map<String, String> properties = new HashMap<>(); diff --git a/model-integration/src/main/java/ai/vespa/modelintegration/evaluator/OnnxEvaluator.java b/model-integration/src/main/java/ai/vespa/modelintegration/evaluator/OnnxEvaluator.java index bdcceddb04f..125707c9aaa 100644 --- a/model-integration/src/main/java/ai/vespa/modelintegration/evaluator/OnnxEvaluator.java +++ b/model-integration/src/main/java/ai/vespa/modelintegration/evaluator/OnnxEvaluator.java @@ -37,6 +37,9 @@ public class OnnxEvaluator { environment = OrtEnvironment.getEnvironment(); session = environment.createSession(modelPath, options.getOptions()); } catch (OrtException e) { + if (e.getCode() == OrtException.OrtErrorCode.ORT_NO_SUCHFILE) { + throw new IllegalArgumentException("No such file: "+modelPath); + } throw new RuntimeException("ONNX Runtime exception", e); } } @@ -101,6 +104,11 @@ public class OnnxEvaluator { try { new OnnxEvaluator(modelPath); return true; + } catch (IllegalArgumentException e) { + if (e.getMessage().equals("No such file: ")) { + return true; + } + return false; } catch (UnsatisfiedLinkError | RuntimeException | NoClassDefFoundError e) { return false; } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java index 0facc6d37ea..1928a784763 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java @@ -57,6 +57,7 @@ public class ClusterModel { // Lazily initialized members private Double queryFractionOfMax = null; private Double maxQueryGrowthRate = null; + private OptionalDouble averageQueryRate = null; public ClusterModel(Zone zone, Application application, @@ -131,19 +132,25 @@ public class ClusterModel { /** * Returns the predicted max query growth rate per minute as a fraction of the average traffic - * in the scaling window + * in the scaling window. */ public double maxQueryGrowthRate() { if (maxQueryGrowthRate != null) return maxQueryGrowthRate; return maxQueryGrowthRate = clusterTimeseries().maxQueryGrowthRate(scalingDuration(), clock); } - /** Returns the average query rate in the scaling window as a fraction of the max observed query rate */ + /** Returns the average query rate in the scaling window as a fraction of the max observed query rate. */ public double queryFractionOfMax() { if (queryFractionOfMax != null) return queryFractionOfMax; return queryFractionOfMax = clusterTimeseries().queryFractionOfMax(scalingDuration(), clock); } + /** Returns the average query rate in the scaling window. */ + public OptionalDouble averageQueryRate() { + if (averageQueryRate != null) return averageQueryRate; + return averageQueryRate = clusterTimeseries().queryRate(scalingDuration(), clock); + } + /** Returns the average of the last load measurement from each node. */ public Load currentLoad() { return nodeTimeseries().currentLoad(); } @@ -239,7 +246,8 @@ public class ClusterModel { // Cap headroom at 10% above the historical observed peak if (queryFractionOfMax() != 0) growthRateHeadroom = Math.min(growthRateHeadroom, 1 / queryFractionOfMax() + 0.1); - return growthRateHeadroom; + + return adjustByConfidence(growthRateHeadroom); } /** @@ -255,15 +263,23 @@ public class ClusterModel { trafficShiftHeadroom = 1/application.status().maxReadShare(); else trafficShiftHeadroom = application.status().maxReadShare() / application.status().currentReadShare(); - return Math.min(trafficShiftHeadroom, 1/application.status().maxReadShare()); + return adjustByConfidence(Math.min(trafficShiftHeadroom, 1/application.status().maxReadShare())); + } + + /** + * Headroom values are a multiplier of the current query rate. + * Adjust this value closer to 1 if the query rate is too low to derive statistical conclusions + * with high confidence to avoid large adjustments caused by random noise due to low traffic numbers. + */ + private double adjustByConfidence(double headroom) { + return ( (headroom -1 ) * Math.min(1, averageQueryRate().orElse(0) / 100.0) ) + 1; } /** The estimated fraction of cpu usage which goes to processing queries vs. writes */ public double queryCpuFraction() { - OptionalDouble queryRate = clusterTimeseries().queryRate(scalingDuration(), clock); OptionalDouble writeRate = clusterTimeseries().writeRate(scalingDuration(), clock); - if (queryRate.orElse(0) == 0 && writeRate.orElse(0) == 0) return queryCpuFraction(0.5); - return queryCpuFraction(queryRate.orElse(0) / (queryRate.orElse(0) + writeRate.orElse(0))); + if (averageQueryRate().orElse(0) == 0 && writeRate.orElse(0) == 0) return queryCpuFraction(0.5); + return queryCpuFraction(averageQueryRate().orElse(0) / (averageQueryRate().orElse(0) + writeRate.orElse(0))); } private double queryCpuFraction(double queryRateFraction) { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java index 40bad7022d6..1a3ac17c7ef 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java @@ -74,7 +74,8 @@ public class GroupPreparer { public PrepareResult prepare(ApplicationId application, ClusterSpec cluster, NodeSpec requestedNodes, List<Node> surplusActiveNodes, NodeIndices indices, int wantedGroups, NodesAndHosts<LockedNodeList> allNodesAndHosts) { - log.log(Level.FINE, () -> "Preparing " + cluster.type().name() + " " + cluster.id() + " with requested resources " + requestedNodes.resources().orElse(NodeResources.unspecified())); + log.log(Level.FINE, () -> "Preparing " + cluster.type().name() + " " + cluster.id() + " with requested resources " + + requestedNodes.resources().orElse(NodeResources.unspecified())); // Try preparing in memory without global unallocated lock. Most of the time there should be no changes, // and we can return nodes previously allocated. NodeAllocation probeAllocation = prepareAllocation(application, cluster, requestedNodes, surplusActiveNodes, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java index eda677c6e59..f6c393a6f4d 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java @@ -457,7 +457,7 @@ public class AutoscalingTest { fixture.tester().clock().advance(Duration.ofDays(2)); Duration timePassed = fixture.loader().addCpuMeasurements(0.25, 120); fixture.tester().clock().advance(timePassed.negated()); - fixture.loader().addLoadMeasurements(10, t -> t == 0 ? 20.0 : 10.0, t -> 1.0); + fixture.loader().addLoadMeasurements(10, t -> t == 0 ? 200.0 : 100.0, t -> 10.0); fixture.tester().assertResources("Scaling up cpu, others down, changing to 1 group is cheaper", 8, 1, 2.8, 36.2, 56.4, fixture.autoscale()); @@ -496,7 +496,7 @@ public class AutoscalingTest { fixture.tester().clock().advance(Duration.ofDays(1)); fixture.loader().applyMemLoad(1.0, 1000); fixture.tester().assertResources("Increase group size to reduce memory load", - 8, 2, 4.5, 97.1, 74.7, + 8, 2, 13.9, 97.1, 66.6, fixture.autoscale()); } @@ -564,7 +564,7 @@ public class AutoscalingTest { var fixture = AutoscalingTester.fixture().awsProdSetup(true).build(); fixture.tester().clock().advance(Duration.ofDays(2)); - Duration timeAdded = fixture.loader().addLoadMeasurements(100, t -> t == 0 ? 20.0 : 10.0, t -> 0.0); + Duration timeAdded = fixture.loader().addLoadMeasurements(100, t -> t == 0 ? 200.0 : 100.0, t -> 0.0); fixture.tester.clock().advance(timeAdded.negated()); fixture.loader().addCpuMeasurements(0.25, 200); @@ -574,17 +574,17 @@ public class AutoscalingTest { fixture.setScalingDuration(Duration.ofMinutes(5)); fixture.tester().clock().advance(Duration.ofDays(2)); - timeAdded = fixture.loader().addLoadMeasurements(100, t -> 10.0 + (t < 50 ? t : 100 - t), t -> 0.0); + timeAdded = fixture.loader().addLoadMeasurements(100, t -> 100.0 + (t < 50 ? t : 100 - t), t -> 0.0); fixture.tester.clock().advance(timeAdded.negated()); fixture.loader().addCpuMeasurements(0.25, 200); fixture.tester().assertResources("Scale down since observed growth is slower than scaling time", - 5, 1, 2.2, 13.3, 83.2, + 5, 1, 2.1, 13.3, 83.2, fixture.autoscale()); fixture.setScalingDuration(Duration.ofMinutes(60)); fixture.tester().clock().advance(Duration.ofDays(2)); timeAdded = fixture.loader().addLoadMeasurements(100, - t -> 10.0 + (t < 50 ? t * t * t : 125000 - (t - 49) * (t - 49) * (t - 49)), + t -> 100.0 + (t < 50 ? t * t * t : 125000 - (t - 49) * (t - 49) * (t - 49)), t -> 0.0); fixture.tester.clock().advance(timeAdded.negated()); fixture.loader().addCpuMeasurements(0.25, 200); @@ -594,6 +594,23 @@ public class AutoscalingTest { } @Test + public void test_autoscaling_weights_growth_rate_by_confidence() { + var fixture = AutoscalingTester.fixture().awsProdSetup(true).build(); + + double scalingFactor = 1.0/6000; // To make the average query rate low + fixture.setScalingDuration(Duration.ofMinutes(60)); + fixture.tester().clock().advance(Duration.ofDays(2)); + Duration timeAdded = fixture.loader().addLoadMeasurements(100, + t -> scalingFactor * (100.0 + (t < 50 ? t * t * t : 125000 - (t - 49) * (t - 49) * (t - 49))), + t -> 0.0); + fixture.tester.clock().advance(timeAdded.negated()); + fixture.loader().addCpuMeasurements(0.7, 200); + fixture.tester().assertResources("Scale up slightly since observed growth is faster than scaling time, but we are not confident", + 5, 1, 2.1, 13.3, 83.2, + fixture.autoscale()); + } + + @Test public void test_autoscaling_considers_query_vs_write_rate() { var fixture = AutoscalingTester.fixture().awsProdSetup(true).build(); @@ -603,7 +620,7 @@ public class AutoscalingTest { // This makes headroom for queries doubling, which we want to observe the effect of here fixture.tester().clock().advance(Duration.ofDays(2)); - var timeAdded = fixture.loader().addLoadMeasurements(100, t -> t == 0 ? 20.0 : 10.0, t -> 10.0); + var timeAdded = fixture.loader().addLoadMeasurements(100, t -> t == 0 ? 200.0 : 100.0, t -> 100.0); fixture.tester.clock().advance(timeAdded.negated()); fixture.loader().addCpuMeasurements(0.4, 200); fixture.tester.assertResources("Query and write load is equal -> scale up somewhat", @@ -611,7 +628,7 @@ public class AutoscalingTest { fixture.autoscale()); fixture.tester().clock().advance(Duration.ofDays(2)); - timeAdded = fixture.loader().addLoadMeasurements(100, t -> t == 0 ? 80.0 : 40.0, t -> 10.0); + timeAdded = fixture.loader().addLoadMeasurements(100, t -> t == 0 ? 800.0 : 400.0, t -> 100.0); fixture.tester.clock().advance(timeAdded.negated()); fixture.loader().addCpuMeasurements(0.4, 200); // TODO: Ackhually, we scale down here - why? @@ -620,7 +637,7 @@ public class AutoscalingTest { fixture.autoscale()); fixture.tester().clock().advance(Duration.ofDays(2)); - timeAdded = fixture.loader().addLoadMeasurements(100, t -> t == 0 ? 20.0 : 10.0, t -> 100.0); + timeAdded = fixture.loader().addLoadMeasurements(100, t -> t == 0 ? 200.0 : 100.0, t -> 1000.0); fixture.tester.clock().advance(timeAdded.negated()); fixture.loader().addCpuMeasurements(0.4, 200); fixture.tester().assertResources("Write load is 10x query load -> scale down", @@ -628,7 +645,7 @@ public class AutoscalingTest { fixture.autoscale()); fixture.tester().clock().advance(Duration.ofDays(2)); - timeAdded = fixture.loader().addLoadMeasurements(100, t -> t == 0 ? 20.0 : 10.0, t-> 0.0); + timeAdded = fixture.loader().addLoadMeasurements(100, t -> t == 0 ? 200.0 : 100.0, t-> 0.0); fixture.tester.clock().advance(timeAdded.negated()); fixture.loader().addCpuMeasurements(0.4, 200); fixture.tester().assertResources("Query only -> largest possible", @@ -636,7 +653,7 @@ public class AutoscalingTest { fixture.autoscale()); fixture.tester().clock().advance(Duration.ofDays(2)); - timeAdded = fixture.loader().addLoadMeasurements(100, t -> 0.0, t -> 10.0); + timeAdded = fixture.loader().addLoadMeasurements(100, t -> 0.0, t -> 100.0); fixture.tester.clock().advance(timeAdded.negated()); fixture.loader().addCpuMeasurements(0.4, 200); fixture.tester().assertResources("Write only -> smallest possible", diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java index b38dbfc55ae..ed00134af55 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java @@ -41,31 +41,41 @@ public class ClusterModelTest { public void test_traffic_headroom() { // No current traffic share: Ideal load is low but capped var model1 = clusterModel(new Status(0.0, 1.0), - t -> t == 0 ? 10000.0 : 0.0, t -> 0.0); - assertEquals(0.37067209775967414, model1.idealLoad().cpu(), delta); + t -> t == 0 ? 10000.0 : 100.0, t -> 0.0); + assertEquals(0.32653061224489793, model1.idealLoad().cpu(), delta); // Almost no current traffic share: Ideal load is low but capped var model2 = clusterModel(new Status(0.0001, 1.0), - t -> t == 0 ? 10000.0 : 0.0, t -> 0.0); - assertEquals(0.37067209775967414, model2.idealLoad().cpu(), delta); + t -> t == 0 ? 10000.0 : 100.0, t -> 0.0); + assertEquals(0.32653061224489793, model2.idealLoad().cpu(), delta); + + // Almost no traffic: Headroom impact is reduced due to uncertainty + var model3 = clusterModel(new Status(0.0001, 1.0), + t -> t == 0 ? 10000.0 : 1.0, t -> 0.0); + assertEquals(0.6465952717720751, model3.idealLoad().cpu(), delta); } @Test public void test_growth_headroom() { // No traffic data: Ideal load assumes 2 regions var model1 = clusterModel(new Status(0.0, 0.0), - t -> t == 0 ? 10000.0 : 0.0, t -> 0.0); - assertEquals(0.2240325865580448, model1.idealLoad().cpu(), delta); + t -> t == 0 ? 10000.0 : 100.0, t -> 0.0); + assertEquals(0.16326530612244897, model1.idealLoad().cpu(), delta); // No traffic: Ideal load is higher since we now know there is only one zone var model2 = clusterModel(new Status(0.0, 1.0), - t -> t == 0 ? 10000.0 : 0.0, t -> 0.0); - assertEquals(0.37067209775967414, model2.idealLoad().cpu(), delta); + t -> t == 0 ? 10000.0 : 100.0, t -> 0.0); + assertEquals(0.32653061224489793, model2.idealLoad().cpu(), delta); // Almost no current traffic: Similar number as above var model3 = clusterModel(new Status(0.0001, 1.0), - t -> t == 0 ? 10000.0 : 0.0001, t -> 0.0); + t -> t == 0 ? 10000.0 : 100.0, t -> 0.0); assertEquals(0.32653061224489793, model3.idealLoad().cpu(), delta); + + // Low query rate: Impact of growth headroom is reduced due to uncertainty + var model4 = clusterModel(new Status(0.0001, 1.0), + t -> t == 0 ? 100.0 : 1.0, t -> 0.0); + assertEquals(0.6465952717720751, model4.idealLoad().cpu(), delta); } private ClusterModel clusterModelWithNoData() { diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/Loader.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/Loader.java index 9158262b134..10c8c7434b1 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/Loader.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/Loader.java @@ -82,13 +82,13 @@ public class Loader { public void applyCpuLoad(double cpuLoad, int measurements) { addCpuMeasurements((float)cpuLoad, measurements); fixture.tester().clock().advance(samplingInterval.negated().multipliedBy(measurements)); - addQueryRateMeasurements(measurements, t -> t == 0 ? 20.0 : 10.0); // Query traffic only + addQueryRateMeasurements(measurements, t -> t == 0 ? 200.0 : 100.0); // Query traffic only } public void applyMemLoad(double memLoad, int measurements) { addMemMeasurements(memLoad, measurements); fixture.tester().clock().advance(samplingInterval.negated().multipliedBy(measurements)); - addQueryRateMeasurements(measurements, t -> t == 0 ? 20.0 : 10.0); // Query traffic only + addQueryRateMeasurements(measurements, t -> t == 0 ? 200.0 : 100.0); // Query traffic only } /** @@ -140,13 +140,13 @@ public class Loader { public void applyLoad(Load load, int measurements) { addMeasurements(load, measurements); fixture.tester().clock().advance(samplingInterval.negated().multipliedBy(measurements)); - addQueryRateMeasurements(measurements, t -> t == 0 ? 20.0 : 10.0); // Query traffic only + addQueryRateMeasurements(measurements, t -> t == 0 ? 200.0 : 100.0); // Query traffic only } public void applyLoad(Load load, int generation, boolean inService, boolean stable, int measurements) { addMeasurements(load, generation, inService, stable, measurements); fixture.tester().clock().advance(samplingInterval.negated().multipliedBy(measurements)); - addQueryRateMeasurements(measurements, t -> t == 0 ? 20.0 : 10.0); // Query traffic only + addQueryRateMeasurements(measurements, t -> t == 0 ? 200.0 : 100.0); // Query traffic only } public Duration addQueryRateMeasurements(int measurements, IntFunction<Double> queryRate) { diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java index 5ceb28d3fed..214d842e4bb 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java @@ -70,8 +70,8 @@ public class AutoscalingMaintainerTest { assertTrue(tester.deployer().lastDeployTime(app1).isEmpty()); assertTrue(tester.deployer().lastDeployTime(app2).isEmpty()); - tester.addMeasurements(0.9f, 0.9f, 0.9f, 0, 500, app1); - tester.addMeasurements(0.9f, 0.9f, 0.9f, 0, 500, app2); + tester.addMeasurements(0.9f, 0.9f, 0.9f, 0, 500, app1, cluster1.id()); + tester.addMeasurements(0.9f, 0.9f, 0.9f, 0, 500, app2, cluster2.id()); tester.clock().advance(Duration.ofMinutes(10)); tester.maintainer().maintain(); @@ -93,7 +93,7 @@ public class AutoscalingMaintainerTest { tester.deploy(app1, cluster1, app1Capacity); // Measure overload - tester.addMeasurements(0.9f, 0.9f, 0.9f, 0, 500, app1); + tester.addMeasurements(0.9f, 0.9f, 0.9f, 0, 500, app1, cluster1.id()); // Causes autoscaling tester.clock().advance(Duration.ofMinutes(10)); @@ -110,24 +110,24 @@ public class AutoscalingMaintainerTest { assertEquals(firstMaintenanceTime.toEpochMilli(), events.get(1).at().toEpochMilli()); // Measure overload still, since change is not applied, but metrics are discarded - tester.addMeasurements(0.9f, 0.9f, 0.9f, 0, 500, app1); + tester.addMeasurements(0.9f, 0.9f, 0.9f, 0, 500, app1, cluster1.id()); tester.maintainer().maintain(); assertEquals(firstMaintenanceTime.toEpochMilli(), tester.deployer().lastDeployTime(app1).get().toEpochMilli()); // Measure underload, but no autoscaling since we still haven't measured we're on the new config generation - tester.addMeasurements(0.1f, 0.1f, 0.1f, 0, 500, app1); + tester.addMeasurements(0.1f, 0.1f, 0.1f, 0, 500, app1, cluster1.id()); tester.maintainer().maintain(); assertEquals(firstMaintenanceTime.toEpochMilli(), tester.deployer().lastDeployTime(app1).get().toEpochMilli()); // Add measurement of the expected generation, leading to rescaling // - record scaling completion tester.clock().advance(Duration.ofMinutes(5)); - tester.addMeasurements(0.1f, 0.1f, 0.1f, 1, 1, app1); + tester.addMeasurements(0.1f, 0.1f, 0.1f, 1, 1, app1, cluster1.id()); tester.maintainer().maintain(); assertEquals(firstMaintenanceTime.toEpochMilli(), tester.deployer().lastDeployTime(app1).get().toEpochMilli()); // - measure underload tester.clock().advance(Duration.ofDays(4)); // Exit cooling period - tester.addMeasurements(0.1f, 0.1f, 0.1f, 1, 500, app1); + tester.addMeasurements(0.1f, 0.1f, 0.1f, 1, 500, app1, cluster1.id()); Instant lastMaintenanceTime = tester.clock().instant(); tester.maintainer().maintain(); assertEquals(lastMaintenanceTime.toEpochMilli(), tester.deployer().lastDeployTime(app1).get().toEpochMilli()); @@ -161,16 +161,16 @@ public class AutoscalingMaintainerTest { Duration samplePeriod = Duration.ofSeconds(150); for (int i = 0; i < 20; i++) { // Record completion to keep scaling window at minimum - tester.addMeasurements(0.1f, 0.1f, 0.1f, i, 1, app1); + tester.addMeasurements(0.1f, 0.1f, 0.1f, i, 1, app1, cluster1.id()); tester.maintainer().maintain(); tester.clock().advance(Duration.ofDays(1)); if (i % 2 == 0) { // high load - tester.addMeasurements(0.99f, 0.99f, 0.99f, i, measurements, app1); + tester.addMeasurements(0.99f, 0.99f, 0.99f, i, measurements, app1, cluster1.id()); } else { // low load - tester.addMeasurements(0.2f, 0.2f, 0.2f, i, measurements, app1); + tester.addMeasurements(0.2f, 0.2f, 0.2f, i, measurements, app1, cluster1.id()); } tester.clock().advance(samplePeriod.negated().multipliedBy(measurements)); tester.addQueryRateMeasurements(app1, cluster1.id(), measurements, t -> (t == 0 ? 20.0 : 10.0 )); @@ -180,7 +180,7 @@ public class AutoscalingMaintainerTest { assertEquals(Cluster.maxScalingEvents, tester.cluster(app1, cluster1).scalingEvents().size()); // Complete last event - tester.addMeasurements(0.1f, 0.1f, 0.1f, 20, 1, app1); + tester.addMeasurements(0.1f, 0.1f, 0.1f, 20, 1, app1, cluster1.id()); tester.maintainer().maintain(); assertEquals("Last event is completed", tester.clock().instant(), @@ -202,7 +202,6 @@ public class AutoscalingMaintainerTest { autoscale(false, Duration.ofMinutes( 1), Duration.ofMinutes( 5), clock, app1, cluster1, tester); autoscale( true, Duration.ofMinutes(19), Duration.ofMinutes(10), clock, app1, cluster1, tester); - autoscale( true, Duration.ofMinutes(40), Duration.ofMinutes(20), clock, app1, cluster1, tester); } @Test @@ -217,21 +216,21 @@ public class AutoscalingMaintainerTest { // Add a scaling event tester.deploy(app1, cluster1, capacity); - tester.addMeasurements(1.0f, 0.3f, 0.3f, 0, 4, app1); + tester.addMeasurements(1.0f, 0.3f, 0.3f, 0, 4, app1, cluster1.id()); tester.maintainer().maintain(); assertEquals("Scale up: " + tester.cluster(app1, cluster1).autoscalingStatus(), 1, tester.cluster(app1, cluster1).lastScalingEvent().get().generation()); // measurements with outdated generation are ignored -> no autoscaling - var duration = tester.addMeasurements(3.0f, 0.3f, 0.3f, 0, 2, app1); + var duration = tester.addMeasurements(3.0f, 0.3f, 0.3f, 0, 2, app1, cluster1.id()); tester.maintainer().maintain(); assertEquals("Measurements with outdated generation are ignored -> no autoscaling", 1, tester.cluster(app1, cluster1).lastScalingEvent().get().generation()); tester.clock().advance(duration.negated()); - duration = tester.addMeasurements(3.0f, 0.3f, 0.3f, 1, 2, app1); + duration = tester.addMeasurements(3.0f, 0.3f, 0.3f, 1, 2, app1, cluster1.id()); tester.maintainer().maintain(); assertEquals("Measurements right after generation change are ignored -> no autoscaling", 1, @@ -242,7 +241,7 @@ public class AutoscalingMaintainerTest { tester.clock().advance(ClusterModel.warmupDuration.plus(Duration.ofMinutes(1))); tester.nodeRepository().nodes().list().owner(app1).asList().forEach(node -> recordRestart(node, tester.nodeRepository())); - duration = tester.addMeasurements(3.0f, 0.3f, 0.3f, 1, 2, app1); + duration = tester.addMeasurements(3.0f, 0.3f, 0.3f, 1, 2, app1, cluster1.id()); tester.maintainer().maintain(); assertEquals("Measurements right after restart are ignored -> no autoscaling", 1, @@ -250,7 +249,7 @@ public class AutoscalingMaintainerTest { tester.clock().advance(duration.negated()); tester.clock().advance(ClusterModel.warmupDuration.plus(Duration.ofMinutes(1))); - tester.addMeasurements(3.0f, 0.3f, 0.3f, 1, 2, app1); + tester.addMeasurements(3.0f, 0.3f, 0.3f, 1, 2, app1, cluster1.id()); tester.maintainer().maintain(); assertEquals("We have valid measurements -> scale up", 2, @@ -310,7 +309,7 @@ public class AutoscalingMaintainerTest { clock.advance(completionTime); float load = down ? 0.1f : 1.0f; - tester.addMeasurements(load, load, load, generation, 1, application); + tester.addMeasurements(load, load, load, generation, 1, application, cluster.id()); tester.maintainer().maintain(); assertEvent("Measured completion of the last scaling event, but no new autoscaling yet", generation, Optional.of(clock.instant()), @@ -320,7 +319,7 @@ public class AutoscalingMaintainerTest { else clock.advance(expectedWindow.minus(completionTime)); - tester.addMeasurements(load, load, load, generation, 200, application); + tester.addMeasurements(load, load, load, generation, 200, application, cluster.id()); tester.maintainer().maintain(); assertEquals("We passed window duration so a new autoscaling is started: " + tester.cluster(application, cluster).autoscalingStatus(), diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTester.java index d921af9543e..95e36787219 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTester.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTester.java @@ -71,7 +71,8 @@ public class AutoscalingMaintainerTester { return provisioningTester.deploy(application, cluster, capacity); } - public Duration addMeasurements(float cpu, float mem, float disk, long generation, int count, ApplicationId applicationId) { + public Duration addMeasurements(float cpu, float mem, float disk, long generation, int count, + ApplicationId applicationId, ClusterSpec.Id clusterId) { NodeList nodes = nodeRepository().nodes().list(Node.State.active).owner(applicationId); Instant startTime = clock().instant(); for (int i = 0; i < count; i++) { @@ -85,7 +86,10 @@ public class AutoscalingMaintainerTester { 0.0)))); clock().advance(Duration.ofSeconds(150)); } - return Duration.between(startTime, clock().instant()); + var totalDuration = Duration.between(startTime, clock().instant()); + clock().advance(totalDuration.negated()); + addQueryRateMeasurements(applicationId, clusterId, count, t -> 100.0); + return totalDuration; } /** Creates the given number of measurements, spaced 5 minutes between, using the given function */ diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java index b43baf444c8..f5ab822721f 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainerTest.java @@ -70,9 +70,9 @@ public class ScalingSuggestionsMaintainerTest { new TestMetric()); maintainer.maintain(); - assertEquals("13 nodes with [vcpu: 5.5, memory: 4.5 Gb, disk 10.0 Gb, bandwidth: 0.1 Gbps, architecture: x86_64]", + assertEquals("8 nodes with [vcpu: 3.2, memory: 4.5 Gb, disk 10.0 Gb, bandwidth: 0.1 Gbps, architecture: x86_64]", suggestionOf(app1, cluster1, tester).get().resources().toString()); - assertEquals("8 nodes with [vcpu: 11.0, memory: 4.4 Gb, disk 11.8 Gb, bandwidth: 0.1 Gbps, architecture: x86_64]", + assertEquals("8 nodes with [vcpu: 3.6, memory: 4.4 Gb, disk 11.8 Gb, bandwidth: 0.1 Gbps, architecture: x86_64]", suggestionOf(app2, cluster2, tester).get().resources().toString()); // Utilization goes way down @@ -80,14 +80,14 @@ public class ScalingSuggestionsMaintainerTest { addMeasurements(0.10f, 0.10f, 0.10f, 0, 500, app1, tester.nodeRepository()); maintainer.maintain(); assertEquals("Suggestion stays at the peak value observed", - "13 nodes with [vcpu: 5.5, memory: 4.5 Gb, disk 10.0 Gb, bandwidth: 0.1 Gbps, architecture: x86_64]", + "8 nodes with [vcpu: 3.2, memory: 4.5 Gb, disk 10.0 Gb, bandwidth: 0.1 Gbps, architecture: x86_64]", suggestionOf(app1, cluster1, tester).get().resources().toString()); // Utilization is still way down and a week has passed tester.clock().advance(Duration.ofDays(7)); addMeasurements(0.10f, 0.10f, 0.10f, 0, 500, app1, tester.nodeRepository()); maintainer.maintain(); assertEquals("Peak suggestion has been outdated", - "5 nodes with [vcpu: 1.8, memory: 4.0 Gb, disk 10.0 Gb, bandwidth: 0.1 Gbps, architecture: x86_64]", + "3 nodes with [vcpu: 1.2, memory: 4.0 Gb, disk 10.0 Gb, bandwidth: 0.1 Gbps, architecture: x86_64]", suggestionOf(app1, cluster1, tester).get().resources().toString()); assertTrue(shouldSuggest(app1, cluster1, tester)); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application1.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application1.json index 6adcb1199eb..0d640f7e3b2 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application1.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application1.json @@ -71,7 +71,7 @@ }, "utilization" : { "cpu" : 0.0, - "idealCpu": 0.1375, + "idealCpu": 0.40750000000000003, "currentCpu": 0.0, "peakCpu": 0.0, "memory" : 0.0, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application2.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application2.json index 5babf5fc843..80da118f620 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application2.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/application2.json @@ -45,7 +45,7 @@ }, "utilization" : { "cpu" : 0.0, - "idealCpu": 0.1394913986537023, + "idealCpu": 0.42670157068062825, "currentCpu": 0.0, "peakCpu": 0.0, "memory" : 0.0, diff --git a/searchcore/src/vespa/searchcore/proton/common/scheduled_forward_executor.cpp b/searchcore/src/vespa/searchcore/proton/common/scheduled_forward_executor.cpp index 3f94247fa7e..40f8cd19a17 100644 --- a/searchcore/src/vespa/searchcore/proton/common/scheduled_forward_executor.cpp +++ b/searchcore/src/vespa/searchcore/proton/common/scheduled_forward_executor.cpp @@ -24,8 +24,9 @@ IScheduledExecutor::Handle ScheduledForwardExecutor::scheduleAtFixedRate(Executor::Task::UP task, duration delay, duration interval) { - return _scheduler.scheduleAtFixedRate(makeLambdaTask([&, my_task = std::move(task)]() { - _executor.execute(makeLambdaTask([&]() { + std::shared_ptr<Executor::Task> my_task = std::move(task); + return _scheduler.scheduleAtFixedRate(makeLambdaTask([&, my_task = std::move(my_task)]() { + _executor.execute(makeLambdaTask([&, my_task]() { my_task->run(); })); }), delay, interval); diff --git a/searchcore/src/vespa/searchcore/proton/common/scheduledexecutor.cpp b/searchcore/src/vespa/searchcore/proton/common/scheduledexecutor.cpp index 94c81ee4b6b..1619388ce52 100644 --- a/searchcore/src/vespa/searchcore/proton/common/scheduledexecutor.cpp +++ b/searchcore/src/vespa/searchcore/proton/common/scheduledexecutor.cpp @@ -67,7 +67,7 @@ ScheduledExecutor::scheduleAtFixedRate(Executor::Task::UP task, duration delay, uint64_t key = _nextKey++; auto tTask = std::make_unique<TimerTask>(_transport.GetScheduler(), std::move(task), interval); auto & taskRef = *tTask; - _taskList[key] = std::move(tTask); + _taskList[key] = std::move(tTask); taskRef.Schedule(vespalib::to_s(delay)); return std::make_unique<Registration>(*this, key); } @@ -80,6 +80,7 @@ ScheduledExecutor::cancel(uint64_t key) if (found == _taskList.end()) return false; found->second->Unschedule(); + _taskList.erase(found); return true; } diff --git a/searchlib/src/vespa/searchlib/expression/resultnodes.cpp b/searchlib/src/vespa/searchlib/expression/resultnodes.cpp index 5222ac30698..7fb3ab1b6cf 100644 --- a/searchlib/src/vespa/searchlib/expression/resultnodes.cpp +++ b/searchlib/src/vespa/searchlib/expression/resultnodes.cpp @@ -239,43 +239,18 @@ RawResultNode::add(const ResultNode & b) void RawResultNode::min(const ResultNode & b) { - char buf[32]; - ConstBufferRef s(b.getString(BufferRef(buf, sizeof(buf)))); - - size_t min_sz = std::min(s.size(), _value.size()); - if (min_sz == 0) { - if ( ! _value.empty()) { - setBuffer("", 0); - } - } else { - int cmp = memcmp(_value.data(), s.data(), min_sz); - if (cmp > 0) { - setBuffer(s.data(), s.size()); - } else if (cmp == 0 && cmpNum(_value.size(), s.size()) > 0) { - setBuffer(s.data(), s.size()); - } + int res = cmp(b); + if (res > 0) { + set(b); } } void RawResultNode::max(const ResultNode & b) { - char buf[32]; - ConstBufferRef s(b.getString(BufferRef(buf, sizeof(buf)))); - - size_t min_sz = std::min(s.size(), _value.size()); - if (min_sz == 0) { - if (s.size() > _value.size()) { - setBuffer(s.data(), s.size()); - } - - } else { - int cmp = memcmp(_value.data(), s.data(), min_sz); - if (cmp < 0) { - setBuffer(s.data(), s.size()); - } else if (cmp == 0 && cmpNum(_value.size(), s.size()) < 0) { - setBuffer(s.data(), s.size()); - } + int res = cmp(b); + if (res < 0) { + set(b); } } diff --git a/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/FeedClientBuilderImpl.java b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/FeedClientBuilderImpl.java index bc801dabf2d..1960991792f 100644 --- a/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/FeedClientBuilderImpl.java +++ b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/FeedClientBuilderImpl.java @@ -37,8 +37,8 @@ public class FeedClientBuilderImpl implements FeedClientBuilder { final Map<String, Supplier<String>> requestHeaders = new HashMap<>(); SSLContext sslContext; HostnameVerifier hostnameVerifier; - int connectionsPerEndpoint = 4; - int maxStreamsPerConnection = 4096; + int connectionsPerEndpoint = 8; + int maxStreamsPerConnection = 32; FeedClient.RetryStrategy retryStrategy = defaultRetryStrategy; FeedClient.CircuitBreaker circuitBreaker = new GracePeriodCircuitBreaker(Duration.ofSeconds(10)); Path certificateFile; |