diff options
52 files changed, 675 insertions, 153 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 4f7cf8575c4..afa4000f891 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 @@ -88,6 +88,10 @@ public interface ModelContext { // TODO Revisit in May or June 2020 double defaultTopKProbability(); + boolean useDistributorBtreeDb(); + + boolean useThreePhaseUpdates(); + // TODO: Remove once there are no Vespa versions below 7.170 boolean useBucketSpaceMetric(); diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java index d799af36c3b..24cf892c49a 100644 --- a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java +++ b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java @@ -41,6 +41,8 @@ public class TestProperties implements ModelContext.Properties { private boolean useDedicatedNodeForLogserver = false; private boolean useAdaptiveDispatch = false; private double topKProbability = 1.0; + private boolean useDistributorBtreeDb = false; + private boolean useThreePhaseUpdates = false; private double defaultTermwiseLimit = 1.0; private double softStartSeconds = 0.0; private double threadPoolSizeFactor = 0.0; @@ -82,6 +84,8 @@ public class TestProperties implements ModelContext.Properties { } @Override public double defaultTopKProbability() { return topKProbability; } + @Override public boolean useDistributorBtreeDb() { return useDistributorBtreeDb; } + @Override public boolean useThreePhaseUpdates() { return useThreePhaseUpdates; } @Override public boolean useBucketSpaceMetric() { return true; } @Override public Optional<AthenzDomain> athenzDomain() { return Optional.ofNullable(athenzDomain); } @@ -94,6 +98,17 @@ public class TestProperties implements ModelContext.Properties { topKProbability = probability; return this; } + + public TestProperties setUseDistributorBtreeDB(boolean useBtreeDb) { + useDistributorBtreeDb = useBtreeDb; + return this; + } + + public TestProperties setUseThreePhaseUpdates(boolean useThreePhaseUpdates) { + this.useThreePhaseUpdates = useThreePhaseUpdates; + return this; + } + public TestProperties setSoftStartSeconds(double softStartSeconds) { this.softStartSeconds = softStartSeconds; return this; diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java index 045646cbc5c..0b6b7154a62 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java @@ -41,6 +41,8 @@ public class DistributorCluster extends AbstractConfigProducer<Distributor> impl private final BucketSplitting bucketSplitting; private final GcOptions gc; private final boolean hasIndexedDocumentType; + private final boolean useBtreeDatabase; + private final boolean useThreePhaseUpdates; public static class Builder extends VespaDomBuilder.DomConfigProducerBuilder<DistributorCluster> { @@ -101,20 +103,26 @@ public class DistributorCluster extends AbstractConfigProducer<Distributor> impl final ModelElement documentsNode = clusterElement.child("documents"); final GcOptions gc = parseGcOptions(documentsNode); final boolean hasIndexedDocumentType = clusterContainsIndexedDocumentType(documentsNode); + boolean useBtreeDb = deployState.getProperties().useDistributorBtreeDb(); + boolean useThreePhaseUpdates = deployState.getProperties().useThreePhaseUpdates(); return new DistributorCluster(parent, - new BucketSplitting.Builder().build(new ModelElement(producerSpec)), gc, hasIndexedDocumentType); + new BucketSplitting.Builder().build(new ModelElement(producerSpec)), gc, + hasIndexedDocumentType, useBtreeDb, useThreePhaseUpdates); } } private DistributorCluster(ContentCluster parent, BucketSplitting bucketSplitting, - GcOptions gc, boolean hasIndexedDocumentType) + GcOptions gc, boolean hasIndexedDocumentType, + boolean useBtreeDatabase, boolean useThreePhaseUpdates) { super(parent, "distributor"); this.parent = parent; this.bucketSplitting = bucketSplitting; this.gc = gc; this.hasIndexedDocumentType = hasIndexedDocumentType; + this.useBtreeDatabase = useBtreeDatabase; + this.useThreePhaseUpdates = useThreePhaseUpdates; } @Override @@ -126,6 +134,8 @@ public class DistributorCluster extends AbstractConfigProducer<Distributor> impl } builder.enable_revert(parent.getPersistence().supportRevert()); builder.disable_bucket_activation(hasIndexedDocumentType == false); + builder.use_btree_database(useBtreeDatabase); + builder.enable_metadata_only_fetch_phase_for_inconsistent_updates(useThreePhaseUpdates); bucketSplitting.getConfig(builder); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/ml/ConvertedModel.java b/config-model/src/main/java/com/yahoo/vespa/model/ml/ConvertedModel.java index 33b1e9c654c..c3d6f457ce8 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/ml/ConvertedModel.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/ml/ConvertedModel.java @@ -209,8 +209,8 @@ public class ConvertedModel { ModelStore store) { // Add constants Set<String> constantsReplacedByFunctions = new HashSet<>(); - model.smallConstantValues().forEach((k, v) -> transformSmallConstant(store, profile, k, v)); - model.largeConstantValues().forEach((k, v) -> transformLargeConstant(store, profile, queryProfiles, + model.smallConstants().forEach((k, v) -> transformSmallConstant(store, profile, k, v)); + model.largeConstants().forEach((k, v) -> transformLargeConstant(store, profile, queryProfiles, constantsReplacedByFunctions, k, v)); // Add functions @@ -283,7 +283,8 @@ public class ConvertedModel { } private static void transformSmallConstant(ModelStore store, RankProfile profile, String constantName, - Tensor constantValue) { + String constantValueString) { + Tensor constantValue = Tensor.from(constantValueString); store.writeSmallConstant(constantName, constantValue); profile.addConstant(constantName, asValue(constantValue)); } @@ -293,7 +294,8 @@ public class ConvertedModel { QueryProfileRegistry queryProfiles, Set<String> constantsReplacedByFunctions, String constantName, - Tensor constantValue) { + String constantValueString) { + Tensor constantValue = Tensor.from(constantValueString); RankProfile.RankingExpressionFunction rankingExpressionFunctionOverridingConstant = profile.getFunctions().get(constantName); if (rankingExpressionFunctionOverridingConstant != null) { TensorType functionType = rankingExpressionFunctionOverridingConstant.function().getBody().type(profile.typeContext(queryProfiles)); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java index 4d5df7c1965..802082cc2ff 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java @@ -972,5 +972,36 @@ public class ContentClusterTest extends ContentBaseTest { verifyTopKProbabilityPropertiesControl(0.77); } + private boolean resolveDistributorBtreeDbConfigWithFeatureFlag(boolean flagEnabledBtreeDb) { + VespaModel model = createEnd2EndOneNode(new TestProperties().setUseDistributorBtreeDB(flagEnabledBtreeDb)); + + ContentCluster cc = model.getContentClusters().get("storage"); + var builder = new StorDistributormanagerConfig.Builder(); + cc.getDistributorNodes().getConfig(builder); + + return (new StorDistributormanagerConfig(builder)).use_btree_database(); + } + + @Test + public void default_distributor_btree_usage_controlled_by_properties() { + assertFalse(resolveDistributorBtreeDbConfigWithFeatureFlag(false)); + assertTrue(resolveDistributorBtreeDbConfigWithFeatureFlag(true)); + } + + private boolean resolveThreePhaseUpdateConfigWithFeatureFlag(boolean flagEnableThreePhase) { + VespaModel model = createEnd2EndOneNode(new TestProperties().setUseThreePhaseUpdates(flagEnableThreePhase)); + + ContentCluster cc = model.getContentClusters().get("storage"); + var builder = new StorDistributormanagerConfig.Builder(); + cc.getDistributorNodes().getConfig(builder); + + return (new StorDistributormanagerConfig(builder)).enable_metadata_only_fetch_phase_for_inconsistent_updates(); + } + + @Test + public void default_distributor_three_phase_update_config_controlled_by_properties() { + assertFalse(resolveThreePhaseUpdateConfigWithFeatureFlag(false)); + assertTrue(resolveThreePhaseUpdateConfigWithFeatureFlag(true)); + } } 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 cd06f7208e4..61ecfa16f66 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 @@ -151,6 +151,8 @@ public class ModelContextImpl implements ModelContext { private final boolean isFirstTimeDeployment; private final boolean useAdaptiveDispatch; private final double defaultTopKprobability; + private final boolean useDistributorBtreeDb; + private final boolean useThreePhaseUpdates; private final Optional<EndpointCertificateSecrets> endpointCertificateSecrets; private final double defaultTermwiseLimit; private final double defaultSoftStartSeconds; @@ -192,6 +194,10 @@ public class ModelContextImpl implements ModelContext { .with(FetchVector.Dimension.APPLICATION_ID, applicationId.serializedForm()).value(); defaultTopKprobability = Flags.DEFAULT_TOP_K_PROBABILITY.bindTo(flagSource) .with(FetchVector.Dimension.APPLICATION_ID, applicationId.serializedForm()).value(); + useDistributorBtreeDb = Flags.USE_DISTRIBUTOR_BTREE_DB.bindTo(flagSource) + .with(FetchVector.Dimension.APPLICATION_ID, applicationId.serializedForm()).value(); + useThreePhaseUpdates = Flags.USE_THREE_PHASE_UPDATES.bindTo(flagSource) + .with(FetchVector.Dimension.APPLICATION_ID, applicationId.serializedForm()).value(); threadPoolSizeFactor = Flags.DEFAULT_THREADPOOL_SIZE_FACTOR.bindTo(flagSource) .with(FetchVector.Dimension.APPLICATION_ID, applicationId.serializedForm()).value(); queueSizefactor = Flags.DEFAULT_QUEUE_SIZE_FACTOR.bindTo(flagSource) @@ -267,6 +273,16 @@ public class ModelContextImpl implements ModelContext { return defaultTopKprobability; } + @Override + public boolean useDistributorBtreeDb() { + return useDistributorBtreeDb; + } + + @Override + public boolean useThreePhaseUpdates() { + return useThreePhaseUpdates; + } + // TODO: Remove @Override public boolean useBucketSpaceMetric() { return true; } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ArtifactRepository.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ArtifactRepository.java index 2beb19536b6..b40f4c69552 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ArtifactRepository.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ArtifactRepository.java @@ -14,7 +14,7 @@ public interface ArtifactRepository { // TODO unused, remove /** Returns the tenant application package of the given version. */ - byte[] getApplicationPackage(ApplicationId application, String applicationVersion); + default byte[] getApplicationPackage(ApplicationId application, String applicationVersion) { return new byte[0]; } /** Returns the system application package of the given version. */ byte[] getSystemApplicationPackage(ApplicationId application, ZoneId zone, Version version); diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java index ced3d201f6f..80de7f802b6 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java @@ -217,7 +217,10 @@ enum PathGroup { systemFlagsDryrun(PathPrefix.none, "/system-flags/v1/dryrun"), /** Paths used for receiving payment callbacks */ - paymentProcessor(PathPrefix.none, "/payment/notification"); + paymentProcessor(PathPrefix.none, "/payment/notification"), + + /** Invoice management */ + invoiceManagement(PathPrefix.none, "/billing/v1/invoice"); final List<String> pathSpecs; diff --git a/dist/vespa.spec b/dist/vespa.spec index aa0232fa421..89348dabb88 100644 --- a/dist/vespa.spec +++ b/dist/vespa.spec @@ -3,6 +3,9 @@ # Hack to speed up jar packing for now %define __jar_repack %{nil} +# Only strip debug info +%global _find_debuginfo_opts -g + # Force special prefix for Vespa %define _prefix /opt/vespa %define _vespa_deps_prefix /opt/vespa-deps 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 22b5ce419d6..155d2644095 100644 --- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java +++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java @@ -166,6 +166,18 @@ public class Flags { "Takes effect at redeployment", ZONE_ID, APPLICATION_ID); + public static final UnboundBooleanFlag USE_DISTRIBUTOR_BTREE_DB = defineFeatureFlag( + "use-distributor-btree-db", false, + "Whether to use the new B-tree bucket database in the distributors.", + "Takes effect at restart of distributor process", + ZONE_ID, APPLICATION_ID); + + public static final UnboundBooleanFlag USE_THREE_PHASE_UPDATES = defineFeatureFlag( + "use-three-phase-updates", false, + "Whether to enable the use of three-phase updates when bucket replicas are out of sync.", + "Takes effect at redeployment", + ZONE_ID, APPLICATION_ID); + public static final UnboundBooleanFlag HOST_HARDENING = defineFeatureFlag( "host-hardening", false, "Whether to enable host hardening Linux baseline.", diff --git a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/ImportedModel.java b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/ImportedModel.java index f58357cb874..47fe66dd424 100644 --- a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/ImportedModel.java +++ b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/ImportedModel.java @@ -82,9 +82,6 @@ public class ImportedModel implements ImportedMlModel { @Override public Map<String, String> smallConstants() { return asStrings(smallConstants); } - @Override - public Map<String, Tensor> smallConstantValues() { return ImmutableMap.copyOf(smallConstants); } - boolean hasSmallConstant(String name) { return smallConstants.containsKey(name); } /** @@ -95,9 +92,6 @@ public class ImportedModel implements ImportedMlModel { @Override public Map<String, String> largeConstants() { return asStrings(largeConstants); } - @Override - public Map<String, Tensor> largeConstantValues() { return ImmutableMap.copyOf(largeConstants); } - boolean hasLargeConstant(String name) { return largeConstants.containsKey(name); } /** diff --git a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/configmodelview/ImportedMlModel.java b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/configmodelview/ImportedMlModel.java index 8e53d477050..e40a06af042 100644 --- a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/configmodelview/ImportedMlModel.java +++ b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/configmodelview/ImportedMlModel.java @@ -1,7 +1,6 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package ai.vespa.rankingexpression.importer.configmodelview; -import com.yahoo.tensor.Tensor; import java.util.List; import java.util.Map; import java.util.Optional; @@ -17,9 +16,7 @@ public interface ImportedMlModel { String source(); Optional<String> inputTypeSpec(String input); Map<String, String> smallConstants(); - Map<String, Tensor> smallConstantValues(); Map<String, String> largeConstants(); - Map<String, Tensor> largeConstantValues(); Map<String, String> functions(); List<ImportedMlFunction> outputExpressions(); diff --git a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/onnx/TypeConverter.java b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/onnx/TypeConverter.java index 9354a346aaf..ef7038b1793 100644 --- a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/onnx/TypeConverter.java +++ b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/onnx/TypeConverter.java @@ -64,12 +64,12 @@ class TypeConverter { case BOOL: return TensorType.Value.FLOAT; case INT8: return TensorType.Value.FLOAT; case INT16: return TensorType.Value.FLOAT; - case INT32: return TensorType.Value.DOUBLE; - case INT64: return TensorType.Value.DOUBLE; + case INT32: return TensorType.Value.FLOAT; + case INT64: return TensorType.Value.FLOAT; case UINT8: return TensorType.Value.FLOAT; case UINT16: return TensorType.Value.FLOAT; - case UINT32: return TensorType.Value.DOUBLE; - case UINT64: return TensorType.Value.DOUBLE; + case UINT32: return TensorType.Value.FLOAT; + case UINT64: return TensorType.Value.FLOAT; default: throw new IllegalArgumentException("A ONNX tensor with data type " + dataType + " cannot be converted to a Vespa tensor type"); } diff --git a/model-integration/src/test/java/ai/vespa/rankingexpression/importer/onnx/OnnxMnistSoftmaxImportTestCase.java b/model-integration/src/test/java/ai/vespa/rankingexpression/importer/onnx/OnnxMnistSoftmaxImportTestCase.java index 81851f5658c..35c853bd746 100644 --- a/model-integration/src/test/java/ai/vespa/rankingexpression/importer/onnx/OnnxMnistSoftmaxImportTestCase.java +++ b/model-integration/src/test/java/ai/vespa/rankingexpression/importer/onnx/OnnxMnistSoftmaxImportTestCase.java @@ -29,13 +29,13 @@ public class OnnxMnistSoftmaxImportTestCase { // Check constants assertEquals(2, model.largeConstants().size()); - Tensor constant0 = model.largeConstantValues().get("test_Variable"); + Tensor constant0 = Tensor.from(model.largeConstants().get("test_Variable")); assertNotNull(constant0); assertEquals(new TensorType.Builder(TensorType.Value.FLOAT).indexed("d2", 784).indexed("d1", 10).build(), constant0.type()); assertEquals(7840, constant0.size()); - Tensor constant1 = model.largeConstantValues().get("test_Variable_1"); + Tensor constant1 = Tensor.from(model.largeConstants().get("test_Variable_1")); assertNotNull(constant1); assertEquals(new TensorType.Builder(TensorType.Value.FLOAT).indexed("d1", 10).build(), constant1.type()); assertEquals(10, constant1.size()); @@ -84,8 +84,8 @@ public class OnnxMnistSoftmaxImportTestCase { private Context contextFrom(ImportedModel result) { MapContext context = new MapContext(); - result.largeConstantValues().forEach((name, tensor) -> context.put("constant(" + name + ")", new TensorValue(tensor))); - result.smallConstantValues().forEach((name, tensor) -> context.put("constant(" + name + ")", new TensorValue(tensor))); + result.largeConstants().forEach((name, tensor) -> context.put("constant(" + name + ")", new TensorValue(Tensor.from(tensor)))); + result.smallConstants().forEach((name, tensor) -> context.put("constant(" + name + ")", new TensorValue(Tensor.from(tensor)))); return context; } diff --git a/model-integration/src/test/java/ai/vespa/rankingexpression/importer/onnx/TestableModel.java b/model-integration/src/test/java/ai/vespa/rankingexpression/importer/onnx/TestableModel.java index 0283244664f..c5355ebdf6f 100644 --- a/model-integration/src/test/java/ai/vespa/rankingexpression/importer/onnx/TestableModel.java +++ b/model-integration/src/test/java/ai/vespa/rankingexpression/importer/onnx/TestableModel.java @@ -103,8 +103,8 @@ public class TestableModel { static Context contextFrom(ImportedModel result) { TestableModelContext context = new TestableModelContext(); - result.largeConstantValues().forEach((name, tensor) -> context.put("constant(" + name + ")", new TensorValue(tensor))); - result.smallConstantValues().forEach((name, tensor) -> context.put("constant(" + name + ")", new TensorValue(tensor))); + result.largeConstants().forEach((name, tensor) -> context.put("constant(" + name + ")", new TensorValue(Tensor.from(tensor)))); + result.smallConstants().forEach((name, tensor) -> context.put("constant(" + name + ")", new TensorValue(Tensor.from(tensor)))); return context; } diff --git a/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/RegressionTestCase.java b/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/RegressionTestCase.java index 02feba9614b..46ced6f42ad 100644 --- a/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/RegressionTestCase.java +++ b/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/RegressionTestCase.java @@ -25,13 +25,13 @@ public class RegressionTestCase { // Check constants Assert.assertEquals(2, model.get().largeConstants().size()); - Tensor constant0 = model.get().largeConstantValues().get("test_Variable_read"); + Tensor constant0 = Tensor.from(model.get().largeConstants().get("test_Variable_read")); assertNotNull(constant0); assertEquals(new TensorType.Builder().indexed("d2", 1536).indexed("d1", 14).build(), constant0.type()); assertEquals(21504, constant0.size()); - Tensor constant1 = model.get().largeConstantValues().get("test_Variable_1_read"); + Tensor constant1 = Tensor.from(model.get().largeConstants().get("test_Variable_1_read")); assertNotNull(constant1); assertEquals(new TensorType.Builder().indexed("d1", 14).build(), constant1.type()); assertEquals(14, constant1.size()); diff --git a/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/TensorFlowMnistSoftmaxImportTestCase.java b/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/TensorFlowMnistSoftmaxImportTestCase.java index d7572bd7b6b..50e24f20972 100644 --- a/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/TensorFlowMnistSoftmaxImportTestCase.java +++ b/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/TensorFlowMnistSoftmaxImportTestCase.java @@ -25,13 +25,13 @@ public class TensorFlowMnistSoftmaxImportTestCase { // Check constants Assert.assertEquals(2, model.get().largeConstants().size()); - Tensor constant0 = model.get().largeConstantValues().get("test_Variable_read"); + Tensor constant0 = Tensor.from(model.get().largeConstants().get("test_Variable_read")); assertNotNull(constant0); assertEquals(new TensorType.Builder().indexed("d2", 784).indexed("d1", 10).build(), constant0.type()); assertEquals(7840, constant0.size()); - Tensor constant1 = model.get().largeConstantValues().get("test_Variable_1_read"); + Tensor constant1 = Tensor.from(model.get().largeConstants().get("test_Variable_1_read")); assertNotNull(constant1); assertEquals(new TensorType.Builder().indexed("d1", 10).build(), constant1.type()); diff --git a/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/TestableTensorFlowModel.java b/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/TestableTensorFlowModel.java index 6579101fc6b..41f343dbdaa 100644 --- a/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/TestableTensorFlowModel.java +++ b/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/TestableTensorFlowModel.java @@ -103,8 +103,8 @@ public class TestableTensorFlowModel { static Context contextFrom(ImportedModel result) { TestableModelContext context = new TestableModelContext(); - result.largeConstantValues().forEach((name, tensor) -> context.put("constant(" + name + ")", new TensorValue(tensor))); - result.smallConstantValues().forEach((name, tensor) -> context.put("constant(" + name + ")", new TensorValue(tensor))); + result.largeConstants().forEach((name, tensor) -> context.put("constant(" + name + ")", new TensorValue(Tensor.from(tensor)))); + result.smallConstants().forEach((name, tensor) -> context.put("constant(" + name + ")", new TensorValue(Tensor.from(tensor)))); return context; } diff --git a/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/Tf2OnnxImportTestCase.java b/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/Tf2OnnxImportTestCase.java index aed3f349240..0510a433dd9 100644 --- a/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/Tf2OnnxImportTestCase.java +++ b/model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/Tf2OnnxImportTestCase.java @@ -44,8 +44,8 @@ public class Tf2OnnxImportTestCase { private Context contextFrom(ImportedModel result) { MapContext context = new MapContext(); - result.largeConstantValues().forEach((name, tensor) -> context.put("constant(" + name + ")", new TensorValue(tensor))); - result.smallConstantValues().forEach((name, tensor) -> context.put("constant(" + name + ")", new TensorValue(tensor))); + result.largeConstants().forEach((name, tensor) -> context.put("constant(" + name + ")", new TensorValue(Tensor.from(tensor)))); + result.smallConstants().forEach((name, tensor) -> context.put("constant(" + name + ")", new TensorValue(Tensor.from(tensor)))); return context; } diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java index ef515022ae6..f1645727b7c 100644 --- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java +++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java @@ -106,9 +106,9 @@ public class StorageMaintainer { public boolean cleanDiskIfFull(NodeAgentContext context) { double totalBytes = context.node().diskSize().bytes(); - // Delete enough bytes to get below 80% disk usage, but only if we are already using more than 90% disk + // Delete enough bytes to get below 70% disk usage, but only if we are already using more than 80% disk long bytesToRemove = diskUsageFor(context) - .map(diskUsage -> (long) (diskUsage.bytes() - 0.8 * totalBytes)) + .map(diskUsage -> (long) (diskUsage.bytes() - 0.7 * totalBytes)) .filter(bytes -> bytes > totalBytes * 0.1) .orElse(0L); diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainerTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainerTest.java index c17d0017269..3a169795df2 100644 --- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainerTest.java +++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainerTest.java @@ -176,8 +176,8 @@ public class StorageMaintainerTest { mockDiskUsage(950_000L); storageMaintainer.cleanDiskIfFull(context); - // Allocated size: 1 GB, usage: 950_000 kiB (972.8 MB). Wanted usage: 80% => 800 MB - verify(diskCleanup).cleanup(eq(context), any(), eq(172_800_000L)); + // Allocated size: 1 GB, usage: 950_000 kiB (972.8 MB). Wanted usage: 70% => 700 MB + verify(diskCleanup).cleanup(eq(context), any(), eq(272_800_000L)); } private void mockDiskUsage(long kBytes) { diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java index 4475014172e..4defbb55485 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java @@ -12,8 +12,8 @@ import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.NodeFlavors; import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.Zone; +import java.util.logging.Level; import com.yahoo.path.Path; -import com.yahoo.transaction.Mutex; import com.yahoo.transaction.NestedTransaction; import com.yahoo.transaction.Transaction; import com.yahoo.vespa.curator.Curator; @@ -366,36 +366,19 @@ public class CuratorDatabaseClient implements JobControl.Db { /** Acquires the single cluster-global, reentrant lock for active nodes of this application */ // TODO(mpolden): Remove when all config servers take the new lock - public Mutex legacyLock(ApplicationId application) { + public Lock legacyLock(ApplicationId application) { return legacyLock(application, defaultLockTimeout); } /** Acquires the single cluster-global, reentrant lock with the specified timeout for active nodes of this application */ // TODO(mpolden): Remove when all config servers take the new lock - public Mutex legacyLock(ApplicationId application, Duration timeout) { - Mutex legacyLock; - Mutex lock; - // Take the legacy node-repository lock + public Lock legacyLock(ApplicationId application, Duration timeout) { try { - legacyLock = db.lock(legacyLockPath(application), timeout); - } catch (UncheckedTimeoutException e) { - throw new ApplicationLockException(e); + return db.lock(legacyLockPath(application), timeout); } - // Take the application lock (same as config server). This is likey already held at this point, but is - // re-entrant. - try { - lock = db.lock(lockPath(application), timeout); - } catch (UncheckedTimeoutException e) { - legacyLock.close(); + catch (UncheckedTimeoutException e) { throw new ApplicationLockException(e); } - return () -> { - try { - lock.close(); - } finally { - legacyLock.close(); - } - }; } /** diff --git a/searchcore/CMakeLists.txt b/searchcore/CMakeLists.txt index 9c913a4c012..412652821d1 100644 --- a/searchcore/CMakeLists.txt +++ b/searchcore/CMakeLists.txt @@ -70,6 +70,7 @@ vespa_define_module( src/tests/proton/common/attribute_updater src/tests/proton/common/document_type_inspector src/tests/proton/common/hw_info_sampler + src/tests/proton/common/operation_rate_tracker src/tests/proton/common/state_reporter_utils src/tests/proton/docsummary src/tests/proton/document_iterator diff --git a/searchcore/src/tests/proton/common/operation_rate_tracker/CMakeLists.txt b/searchcore/src/tests/proton/common/operation_rate_tracker/CMakeLists.txt new file mode 100644 index 00000000000..f5e6a791124 --- /dev/null +++ b/searchcore/src/tests/proton/common/operation_rate_tracker/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(searchcore_operation_rate_tracker_test_app TEST + SOURCES + operation_rate_tracker_test.cpp + DEPENDS + searchcore_pcommon + gtest +) +vespa_add_test(NAME searchcore_operation_rate_tracker_test_app COMMAND searchcore_operation_rate_tracker_test_app) diff --git a/searchcore/src/tests/proton/common/operation_rate_tracker/operation_rate_tracker_test.cpp b/searchcore/src/tests/proton/common/operation_rate_tracker/operation_rate_tracker_test.cpp new file mode 100644 index 00000000000..ce19867e286 --- /dev/null +++ b/searchcore/src/tests/proton/common/operation_rate_tracker/operation_rate_tracker_test.cpp @@ -0,0 +1,90 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include <vespa/searchcore/proton/common/operation_rate_tracker.h> +#include <vespa/vespalib/gtest/gtest.h> +#include <vespa/vespalib/util/time.h> + +#include <vespa/log/log.h> +LOG_SETUP("operation_rate_tracker_test"); + +using namespace proton; + +TEST(OperationRateTrackerTest, time_budget_per_op_is_inverse_of_rate_threshold) +{ + EXPECT_EQ(vespalib::from_s(0.25), OperationRateTracker(4).get_time_budget_per_op()); + EXPECT_EQ(vespalib::from_s(2.0), OperationRateTracker(0.5).get_time_budget_per_op()); +} + +TEST(OperationRateTrackerTest, time_budget_window_is_minimum_1_sec) +{ + EXPECT_EQ(vespalib::from_s(1.0), OperationRateTracker(4).get_time_budget_window()); + EXPECT_EQ(vespalib::from_s(2.0), OperationRateTracker(0.5).get_time_budget_window()); +} + +class Simulator { +public: + vespalib::steady_time now; + OperationRateTracker ort; + Simulator(double rate_threshold) + : now(vespalib::steady_clock::now()), + ort(rate_threshold) + { + } + void tick(double real_rate) { + now = now + vespalib::from_s(1.0 / real_rate); + ort.observe(now); + } + bool above_threshold(double now_delta = 0) { + return ort.above_threshold(now + vespalib::from_s(now_delta)); + } +}; + +TEST(OperationRateTrackerTest, tracks_whether_operation_rate_is_below_or_above_threshold) +{ + Simulator sim(2); + + // Simulate an actual rate of 4 ops / sec + sim.tick(4); // Threshold time is 1.0s in the past (at time budget window start) + EXPECT_FALSE(sim.above_threshold(-1.0)); + EXPECT_TRUE(sim.above_threshold(-1.01)); + + // Catch up with now + sim.tick(4); + sim.tick(4); + sim.tick(4); + sim.tick(4); // Threshold time is now. + EXPECT_FALSE(sim.above_threshold()); + EXPECT_TRUE(sim.above_threshold(-0.01)); + + // Move into the future + sim.tick(4); // Threshold time is 0.25s into the future. + EXPECT_TRUE(sim.above_threshold(0.24)); + EXPECT_FALSE(sim.above_threshold(0.25)); + + // Move to time budget window end + sim.tick(4); + sim.tick(4); + sim.tick(4); // Threshold time is 1.0s into the future (at time budget window end) + EXPECT_TRUE(sim.above_threshold(0.99)); + EXPECT_FALSE(sim.above_threshold(1.0)); + + sim.tick(4); // Threshold time is still 1.0s into the future (at time budget window end) + EXPECT_TRUE(sim.above_threshold(0.99)); + EXPECT_FALSE(sim.above_threshold(1.0)); + + // Reduce actual rate to 1 ops / sec + sim.tick(1); // Threshold time is 0.5s into the future. + EXPECT_TRUE(sim.above_threshold(0.49)); + EXPECT_FALSE(sim.above_threshold(0.5)); + + sim.tick(1); // Threshold time is now. + EXPECT_FALSE(sim.above_threshold()); + EXPECT_TRUE(sim.above_threshold(-0.01)); + + sim.tick(1); + sim.tick(1); // Threshold time is back at time budget window start + EXPECT_FALSE(sim.above_threshold(-1.0)); + EXPECT_TRUE(sim.above_threshold(-1.01)); +} + +GTEST_MAIN_RUN_ALL_TESTS() diff --git a/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_compaction_test.cpp b/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_compaction_test.cpp index 64299c70588..e679c028d1b 100644 --- a/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_compaction_test.cpp +++ b/searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_compaction_test.cpp @@ -30,7 +30,8 @@ constexpr uint32_t SUBDB_ID = 2; constexpr vespalib::duration JOB_DELAY = 1s; constexpr uint32_t ALLOWED_LID_BLOAT = 1; constexpr double ALLOWED_LID_BLOAT_FACTOR = 0.3; -constexpr vespalib::duration REMOVE_BATCH_BLOCK_DELAY = 20s; +constexpr double REMOVE_BATCH_BLOCK_RATE = 1.0 / 21.0; +constexpr double REMOVE_BLOCK_RATE = 1.0 / 20.0; constexpr uint32_t MAX_DOCS_TO_SCAN = 100; constexpr double RESOURCE_LIMIT_FACTOR = 1.0; constexpr uint32_t MAX_OUTSTANDING_MOVE_OPS = 10; @@ -82,19 +83,40 @@ struct MyHandler : public ILidSpaceCompactionHandler { mutable uint32_t _iteratorCnt; bool _storeMoveDoneContexts; std::vector<IDestructorCallback::SP> _moveDoneContexts; + documentmetastore::OperationListener::SP _op_listener; + RemoveOperationsRateTracker* _rm_listener; MyHandler(bool storeMoveDoneContexts = false); ~MyHandler(); void clearMoveDoneContexts() { _moveDoneContexts.clear(); } - void set_last_remove_batch(TimePoint last_remove_batch) { - for (auto& s : _stats) { - s = LidUsageStats(s.getLidLimit(), s.getUsedLids(), - s.getLowestFreeLid(), s.getHighestUsedLid(), last_remove_batch); + void run_remove_ops(bool remove_batch) { + // This ensures to max out the threshold time in the operation rate tracker. + if (remove_batch) { + _op_listener->notify_remove_batch(); + _op_listener->notify_remove_batch(); + _op_listener->notify_remove_batch(); + } else { + _op_listener->notify_remove(); + _op_listener->notify_remove(); + _op_listener->notify_remove(); + } + } + void stop_remove_ops(bool remove_batch) { + if (remove_batch) { + _rm_listener->get_remove_batch_tracker().reset(vespalib::steady_clock::now()); + } else { + _rm_listener->get_remove_tracker().reset(vespalib::steady_clock::now()); } } virtual vespalib::string getName() const override { return "myhandler"; } + virtual void set_operation_listener(documentmetastore::OperationListener::SP op_listener) override { + auto* rm_listener = dynamic_cast<RemoveOperationsRateTracker*>(op_listener.get()); + assert(rm_listener != nullptr); + _op_listener = std::move(op_listener); + _rm_listener = rm_listener; + } virtual uint32_t getSubDbId() const override { return 2; } virtual LidUsageStats getLidStatus() const override { assert(_handleMoveCnt < _stats.size()); @@ -132,7 +154,9 @@ MyHandler::MyHandler(bool storeMoveDoneContexts) _wantedLidLimit(0), _iteratorCnt(0), _storeMoveDoneContexts(storeMoveDoneContexts), - _moveDoneContexts() + _moveDoneContexts(), + _op_listener(), + _rm_listener() {} MyHandler::~MyHandler() {} @@ -265,7 +289,8 @@ struct JobTestBase : public ::testing::Test { _handler = std::make_unique<MyHandler>(maxOutstandingMoveOps != MAX_OUTSTANDING_MOVE_OPS); _job = std::make_unique<LidSpaceCompactionJob>(DocumentDBLidSpaceCompactionConfig(interval, allowedLidBloat, allowedLidBloatFactor, - REMOVE_BATCH_BLOCK_DELAY, + REMOVE_BATCH_BLOCK_RATE, + REMOVE_BLOCK_RATE, false, maxDocsToScan), *_handler, _storer, _frozenHandler, _diskMemUsageNotifier, BlockableMaintenanceJobConfig(resourceLimitFactor, maxOutstandingMoveOps), @@ -274,20 +299,18 @@ struct JobTestBase : public ::testing::Test { ~JobTestBase(); JobTestBase &addStats(uint32_t docIdLimit, const LidVector &usedLids, - const LidPairVector &usedFreePairs, - TimePoint last_remove_batch = TimePoint()) { - return addMultiStats(docIdLimit, {usedLids}, usedFreePairs, last_remove_batch); + const LidPairVector &usedFreePairs) { + return addMultiStats(docIdLimit, {usedLids}, usedFreePairs); } JobTestBase &addMultiStats(uint32_t docIdLimit, const std::vector<LidVector> &usedLidsVector, - const LidPairVector &usedFreePairs, - TimePoint last_remove_batch = TimePoint()) { + const LidPairVector &usedFreePairs) { uint32_t usedLids = usedLidsVector[0].size(); for (auto pair : usedFreePairs) { uint32_t highestUsedLid = pair.first; uint32_t lowestFreeLid = pair.second; _handler->_stats.push_back(LidUsageStats - (docIdLimit, usedLids, lowestFreeLid, highestUsedLid, last_remove_batch)); + (docIdLimit, usedLids, lowestFreeLid, highestUsedLid)); } _handler->_lids = usedLidsVector; return *this; @@ -297,7 +320,7 @@ struct JobTestBase : public ::testing::Test { uint32_t lowestFreeLid, uint32_t highestUsedLid) { _handler->_stats.push_back(LidUsageStats - (docIdLimit, numDocs, lowestFreeLid, highestUsedLid, TimePoint())); + (docIdLimit, numDocs, lowestFreeLid, highestUsedLid)); return *this; } bool run() { @@ -332,11 +355,10 @@ struct JobTestBase : public ::testing::Test { void assertNoWorkDone() { assertJobContext(0, 0, 0, 0, 0); } - JobTestBase &setupOneDocumentToCompact(TimePoint last_remove_batch = TimePoint()) { + JobTestBase &setupOneDocumentToCompact() { addStats(10, {1,3,4,5,6,9}, {{9,2}, // 30% bloat: move 9 -> 2 - {6,7}}, // no documents to move - last_remove_batch); + {6,7}}); // no documents to move return *this; } void assertOneDocumentCompacted() { @@ -620,41 +642,78 @@ TEST_F(JobTest, job_is_re_enabled_when_node_is_no_longer_retired) assertOneDocumentCompacted(); } -TEST_F(JobTest, job_is_disabled_while_remove_batch_is_ongoing) +class JobDisabledByRemoveOpsTest : public JobTest { +public: + JobDisabledByRemoveOpsTest() : JobTest() {} + + void job_is_disabled_while_remove_ops_are_ongoing(bool remove_batch) { + setupOneDocumentToCompact(); + _handler->run_remove_ops(remove_batch); + EXPECT_TRUE(run()); // job is disabled + assertNoWorkDone(); + } + + void job_becomes_disabled_if_remove_ops_starts(bool remove_batch) { + setupThreeDocumentsToCompact(); + EXPECT_FALSE(run()); // job executed as normal (with more work to do) + assertJobContext(2, 9, 1, 0, 0); + + _handler->run_remove_ops(remove_batch); + EXPECT_TRUE(run()); // job is disabled + assertJobContext(2, 9, 1, 0, 0); + } + + void job_is_re_enabled_when_remove_ops_are_no_longer_ongoing(bool remove_batch) { + job_becomes_disabled_if_remove_ops_starts(remove_batch); + + _handler->stop_remove_ops(remove_batch); + EXPECT_FALSE(run()); // job executed as normal (with more work to do) + assertJobContext(3, 8, 2, 0, 0); + } +}; + +TEST_F(JobDisabledByRemoveOpsTest, config_is_propagated_to_remove_operations_rate_tracker) { - TimePoint last_remove_batch = std::chrono::steady_clock::now(); - setupOneDocumentToCompact(last_remove_batch); - EXPECT_TRUE(run()); // job is disabled - assertNoWorkDone(); + auto& remove_batch_tracker = _handler->_rm_listener->get_remove_batch_tracker(); + EXPECT_EQ(vespalib::from_s(21.0), remove_batch_tracker.get_time_budget_per_op()); + EXPECT_EQ(vespalib::from_s(21.0), remove_batch_tracker.get_time_budget_window()); + + auto& remove_tracker = _handler->_rm_listener->get_remove_tracker(); + EXPECT_EQ(vespalib::from_s(20.0), remove_tracker.get_time_budget_per_op()); + EXPECT_EQ(vespalib::from_s(20.0), remove_tracker.get_time_budget_window()); } -TEST_F(JobTest, job_becomes_disabled_if_remove_batch_starts) +TEST_F(JobDisabledByRemoveOpsTest, job_is_disabled_while_remove_batch_is_ongoing) { - setupThreeDocumentsToCompact(); - EXPECT_FALSE(run()); // job executed as normal (with more work to do) - assertJobContext(2, 9, 1, 0, 0); + job_is_disabled_while_remove_ops_are_ongoing(true); +} - _handler->set_last_remove_batch(std::chrono::steady_clock::now()); - EXPECT_TRUE(run()); // job is disabled - assertJobContext(2, 9, 1, 0, 0); +TEST_F(JobDisabledByRemoveOpsTest, job_becomes_disabled_if_remove_batch_starts) +{ + job_becomes_disabled_if_remove_ops_starts(true); } -TEST_F(JobTest, job_is_re_enabled_when_remove_batch_is_no_longer_ongoing) +TEST_F(JobDisabledByRemoveOpsTest, job_is_re_enabled_when_remove_batch_is_no_longer_ongoing) { - setupThreeDocumentsToCompact(); - EXPECT_FALSE(run()); // job executed as normal (with more work to do) - assertJobContext(2, 9, 1, 0, 0); + job_is_re_enabled_when_remove_ops_are_no_longer_ongoing(true); +} - TimePoint last_remove_batch = std::chrono::steady_clock::now(); - _handler->set_last_remove_batch(last_remove_batch); - EXPECT_TRUE(run()); // job is disabled - assertJobContext(2, 9, 1, 0, 0); +TEST_F(JobDisabledByRemoveOpsTest, job_is_disabled_while_removes_are_ongoing) +{ + job_is_disabled_while_remove_ops_are_ongoing(false); +} - _handler->set_last_remove_batch(last_remove_batch - REMOVE_BATCH_BLOCK_DELAY); - EXPECT_FALSE(run()); // job executed as normal (with more work to do) - assertJobContext(3, 8, 2, 0, 0); +TEST_F(JobDisabledByRemoveOpsTest, job_becomes_disabled_if_removes_start) +{ + job_becomes_disabled_if_remove_ops_starts(false); } +TEST_F(JobDisabledByRemoveOpsTest, job_is_re_enabled_when_removes_are_no_longer_ongoing) +{ + job_is_re_enabled_when_remove_ops_are_no_longer_ongoing(false); +} + + struct MaxOutstandingJobTest : public JobTest { std::unique_ptr<MyCountJobRunner> runner; MaxOutstandingJobTest() diff --git a/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp b/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp index 7e2e258476f..1f0f3566b1d 100644 --- a/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp +++ b/searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp @@ -2,11 +2,13 @@ #include <vespa/document/repo/documenttyperepo.h> #include <vespa/document/test/make_bucket_space.h> +#include <vespa/fastos/thread.h> #include <vespa/searchcore/proton/attribute/attribute_usage_filter.h> #include <vespa/searchcore/proton/attribute/i_attribute_manager.h> #include <vespa/searchcore/proton/bucketdb/bucket_create_notifier.h> #include <vespa/searchcore/proton/common/doctypename.h> #include <vespa/searchcore/proton/common/feedtoken.h> +#include <vespa/searchcore/proton/documentmetastore/operation_listener.h> #include <vespa/searchcore/proton/feedoperation/moveoperation.h> #include <vespa/searchcore/proton/feedoperation/pruneremoveddocumentsoperation.h> #include <vespa/searchcore/proton/feedoperation/putoperation.h> @@ -34,7 +36,6 @@ #include <vespa/vespalib/util/closuretask.h> #include <vespa/vespalib/util/gate.h> #include <vespa/vespalib/util/threadstackexecutor.h> -#include <vespa/fastos/thread.h> #include <unistd.h> #include <vespa/log/log.h> @@ -339,6 +340,7 @@ struct MockLidSpaceCompactionHandler : public ILidSpaceCompactionHandler MockLidSpaceCompactionHandler(const vespalib::string &name_) : name(name_) {} virtual vespalib::string getName() const override { return name; } + virtual void set_operation_listener(documentmetastore::OperationListener::SP) override {} virtual uint32_t getSubDbId() const override { return 0; } virtual search::LidUsageStats getLidStatus() const override { return search::LidUsageStats(); } virtual IDocumentScanIterator::UP getIterator() const override { return IDocumentScanIterator::UP(); } diff --git a/searchcore/src/tests/proton/documentmetastore/documentmetastore_test.cpp b/searchcore/src/tests/proton/documentmetastore/documentmetastore_test.cpp index f8069dcf494..49b84c09040 100644 --- a/searchcore/src/tests/proton/documentmetastore/documentmetastore_test.cpp +++ b/searchcore/src/tests/proton/documentmetastore/documentmetastore_test.cpp @@ -6,6 +6,7 @@ #include <vespa/searchcore/proton/bucketdb/i_bucket_create_listener.h> #include <vespa/searchcore/proton/common/hw_info.h> #include <vespa/searchcore/proton/documentmetastore/documentmetastore.h> +#include <vespa/searchcore/proton/documentmetastore/operation_listener.h> #include <vespa/searchcore/proton/flushengine/shrink_lid_space_flush_target.h> #include <vespa/searchcore/proton/server/itlssyncer.h> #include <vespa/searchlib/attribute/attributefilesavetarget.h> @@ -1782,7 +1783,7 @@ TEST(DocumentMetaStoreTest, get_lid_usage_stats_works) void assertLidBloat(uint32_t expBloat, uint32_t lidLimit, uint32_t usedLids) { - LidUsageStats stats(lidLimit, usedLids, 0, 0, LidUsageStats::TimePoint()); + LidUsageStats stats(lidLimit, usedLids, 0, 0); EXPECT_EQ(expBloat, stats.getLidBloat()); } @@ -2084,21 +2085,42 @@ TEST(DocumentMetaStoreTest, multiple_lids_can_be_removed_with_removeBatch) assertLidGidFound(4, dms); } -TEST(DocumentMetaStoreTest, tracks_time_of_last_call_to_remove_batch) +class MockOperationListener : public documentmetastore::OperationListener { +public: + size_t remove_batch_cnt; + size_t remove_cnt; + + MockOperationListener() + : remove_batch_cnt(0), + remove_cnt(0) + { + } + void notify_remove_batch() override { ++remove_batch_cnt; } + void notify_remove() override { ++remove_cnt; } +}; + +TEST(DocumentMetaStoreTest, call_to_remove_batch_is_notified) { DocumentMetaStore dms(createBucketDB()); + auto listener = std::make_shared<MockOperationListener>(); + dms.set_operation_listener(listener); dms.constructFreeList(); addLid(dms, 1); - LidUsageStats::TimePoint before = std::chrono::steady_clock::now(); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); dms.removeBatch({1}, 5); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - LidUsageStats::TimePoint after = std::chrono::steady_clock::now(); + EXPECT_EQ(1, listener->remove_batch_cnt); +} - auto stats = dms.getLidUsageStats(); - EXPECT_LT(before, stats.get_last_remove_batch()); - EXPECT_GT(after, stats.get_last_remove_batch()); +TEST(DocumentMetaStoreTest, call_to_remove_is_notified) +{ + DocumentMetaStore dms(createBucketDB()); + auto listener = std::make_shared<MockOperationListener>(); + dms.set_operation_listener(listener); + dms.constructFreeList(); + addLid(dms, 1); + + dms.remove(1); + EXPECT_EQ(1, listener->remove_cnt); } } diff --git a/searchcore/src/vespa/searchcore/config/proton.def b/searchcore/src/vespa/searchcore/config/proton.def index 0501ab6ed7c..5d84516d100 100644 --- a/searchcore/src/vespa/searchcore/config/proton.def +++ b/searchcore/src/vespa/searchcore/config/proton.def @@ -369,6 +369,7 @@ lidspacecompaction.allowedlidbloat int default=1000 ## The lid bloat factor must be >= allowedlidbloatfactor before considering compaction. lidspacecompaction.allowedlidbloatfactor double default=0.01 +## DEPRECATED (no longer used): Remove on Vespa 8 ## The delay (in seconds) for when the last remove batch operation would be considered to block lid space compaction. ## ## When considering compaction, if the document meta store has received a remove batch operation in the last delay seconds, @@ -379,6 +380,24 @@ lidspacecompaction.allowedlidbloatfactor double default=0.01 ## lid space compaction do not interfere, but instead is applied after deleting of buckets is complete. lidspacecompaction.removebatchblockdelay double default=2.0 +## The rate (ops / second) of remove batch operations for when to block lid space compaction. +## +## When considering compaction, if the current observed rate of remove batch operations +## is higher than the given block rate, the lid space compaction job is blocked. +## It is considered again at the next regular interval (see above). +## +## Remove batch operations are used when deleting buckets on a content node. +## This functionality ensures that during massive deleting of buckets (e.g. as part of redistribution of data to a new node), +## lid space compaction do not interfere, but instead is applied after deleting of buckets is complete. +lidspacecompaction.removebatchblockrate double default=0.5 + +## The rate (ops / second) of remove operations for when to block lid space compaction. +## +## When considering compaction, if the current observed rate of remove operations +## is higher than the given block rate, the lid space compaction job is blocked. +## It is considered again at the next regular interval (see above). +lidspacecompaction.removeblockrate double default=100000.0 + ## This is the maximum value visibilitydelay you can have. ## A to higher value here will cost more memory while not improving too much. maxvisibilitydelay double default=1.0 diff --git a/searchcore/src/vespa/searchcore/proton/common/CMakeLists.txt b/searchcore/src/vespa/searchcore/proton/common/CMakeLists.txt index 4db36039b3c..f3303f36199 100644 --- a/searchcore/src/vespa/searchcore/proton/common/CMakeLists.txt +++ b/searchcore/src/vespa/searchcore/proton/common/CMakeLists.txt @@ -14,9 +14,10 @@ vespa_add_library(searchcore_pcommon STATIC hw_info_sampler.cpp indexschema_inspector.cpp monitored_refcount.cpp + operation_rate_tracker.cpp select_utils.cpp - selectpruner.cpp selectcontext.cpp + selectpruner.cpp state_reporter_utils.cpp statusreport.cpp DEPENDS diff --git a/searchcore/src/vespa/searchcore/proton/common/operation_rate_tracker.cpp b/searchcore/src/vespa/searchcore/proton/common/operation_rate_tracker.cpp new file mode 100644 index 00000000000..487102cebf6 --- /dev/null +++ b/searchcore/src/vespa/searchcore/proton/common/operation_rate_tracker.cpp @@ -0,0 +1,33 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "operation_rate_tracker.h" + +namespace proton { + +OperationRateTracker::OperationRateTracker(double rate_threshold) + : _time_budget_per_op(vespalib::from_s(1.0 / rate_threshold)), + _time_budget_window(std::max(vespalib::from_s(1.0), _time_budget_per_op)), + _threshold_time() +{ +} + +void +OperationRateTracker::observe(vespalib::steady_time now) +{ + vespalib::steady_time cand_time = std::max(now - _time_budget_window, _threshold_time + _time_budget_per_op); + _threshold_time = std::min(cand_time, now + _time_budget_window); +} + +bool +OperationRateTracker::above_threshold(vespalib::steady_time now) const +{ + return _threshold_time > now; +} + +void +OperationRateTracker::reset(vespalib::steady_time now) +{ + _threshold_time = now - _time_budget_window; +} + +} diff --git a/searchcore/src/vespa/searchcore/proton/common/operation_rate_tracker.h b/searchcore/src/vespa/searchcore/proton/common/operation_rate_tracker.h new file mode 100644 index 00000000000..7f7526faa5d --- /dev/null +++ b/searchcore/src/vespa/searchcore/proton/common/operation_rate_tracker.h @@ -0,0 +1,38 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <vespa/vespalib/util/time.h> + +namespace proton { + +/** + * Tracks whether the rate (ops/sec) of an operation is above or below a given threshold. + * + * An operation is given a time budget which is the inverse of the rate threshold. + * When we observe an operation that much time is "spent", and we adjust a threshold time accordingly. + * If this time is into the future, the current observed rate is above the rate threshold. + * + * To avoid the threshold time racing into the future or lagging behind, + * it is capped in both directions by a time budget window. + */ +class OperationRateTracker { +private: + vespalib::duration _time_budget_per_op; + vespalib::duration _time_budget_window; + vespalib::steady_time _threshold_time; + +public: + OperationRateTracker(double rate_threshold); + + vespalib::duration get_time_budget_per_op() const { return _time_budget_per_op; } + vespalib::duration get_time_budget_window() const { return _time_budget_window; } + + void observe(vespalib::steady_time now); + bool above_threshold(vespalib::steady_time now) const; + + // Should only be used for testing + void reset(vespalib::steady_time now); +}; + +} diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp index c36ee8e474a..b43efa41d0b 100644 --- a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp +++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp @@ -2,6 +2,7 @@ #include "documentmetastore.h" #include "documentmetastoresaver.h" +#include "operation_listener.h" #include "search_context.h" #include <vespa/fastos/file.h> #include <vespa/searchcore/proton/bucketdb/bucketsessionbase.h> @@ -453,7 +454,7 @@ DocumentMetaStore::DocumentMetaStore(BucketDBOwner::SP bucketDB, _shrinkLidSpaceBlockers(0), _subDbType(subDbType), _trackDocumentSizes(true), - _last_remove_batch() + _op_listener() { ensureSpace(0); // lid 0 is reserved setCommittedDocIdLimit(1u); // lid 0 is reserved @@ -621,6 +622,9 @@ DocumentMetaStore::remove(DocId lid) BucketDBOwner::Guard bucketGuard = _bucketDB->takeGuard(); bool result = remove(lid, bucketGuard); incGeneration(); + if (result && _op_listener) { + _op_listener->notify_remove(); + } return result; } @@ -668,7 +672,9 @@ DocumentMetaStore::removeBatch(const std::vector<DocId> &lidsToRemove, const uin (void) removed; } incGeneration(); - _last_remove_batch = std::chrono::steady_clock::now(); + if (_op_listener) { + _op_listener->notify_remove_batch(); + } } void @@ -776,8 +782,7 @@ DocumentMetaStore::getLidUsageStats() const return LidUsageStats(docIdLimit, numDocs, lowestFreeLid, - highestUsedLid, - _last_remove_batch); + highestUsedLid); } Blueprint::UP @@ -1021,6 +1026,12 @@ DocumentMetaStore::canShrinkLidSpace() const } void +DocumentMetaStore::set_operation_listener(documentmetastore::OperationListener::SP op_listener) +{ + _op_listener = std::move(op_listener); +} + +void DocumentMetaStore::onShrinkLidSpace() { uint32_t committedDocIdLimit = this->getCommittedDocIdLimit(); diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.h b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.h index 3bd9795cfd5..57f3458157e 100644 --- a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.h +++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.h @@ -17,11 +17,14 @@ #include <vespa/vespalib/util/rcuvector.h> namespace proton::bucketdb { - class SplitBucketSession; - class JoinBucketsSession; +class SplitBucketSession; +class JoinBucketsSession; } -namespace proton::documentmetastore { class Reader; } +namespace proton::documentmetastore { +class OperationListener; +class Reader; +} namespace proton { @@ -71,7 +74,7 @@ private: uint32_t _shrinkLidSpaceBlockers; const SubDbType _subDbType; bool _trackDocumentSizes; - search::LidUsageStats::TimePoint _last_remove_batch; + std::shared_ptr<documentmetastore::OperationListener> _op_listener; DocId getFreeLid(); DocId peekFreeLid(); @@ -225,6 +228,7 @@ public: void compactLidSpace(DocId wantedLidLimit) override; void holdUnblockShrinkLidSpace() override; bool canShrinkLidSpace() const override; + void set_operation_listener(std::shared_ptr<documentmetastore::OperationListener> op_listener) override; SerialNum getLastSerialNum() const override { return getStatus().getLastSyncToken(); diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/i_document_meta_store.h b/searchcore/src/vespa/searchcore/proton/documentmetastore/i_document_meta_store.h index 37666879205..b4e849bd53f 100644 --- a/searchcore/src/vespa/searchcore/proton/documentmetastore/i_document_meta_store.h +++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/i_document_meta_store.h @@ -10,6 +10,8 @@ #include <vespa/vespalib/btree/btree.h> #include <vespa/vespalib/btree/btreenodeallocator.h> +namespace proton::documentmetastore { class OperationListener; } + namespace proton { /** @@ -82,6 +84,8 @@ struct IDocumentMetaStore : public search::IDocumentMetaStore, */ virtual void compactLidSpace(DocId wantedLidLimit) = 0; + virtual void set_operation_listener(std::shared_ptr<documentmetastore::OperationListener> op_listener) = 0; + }; } // namespace proton diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/operation_listener.h b/searchcore/src/vespa/searchcore/proton/documentmetastore/operation_listener.h new file mode 100644 index 00000000000..259f1a40c9d --- /dev/null +++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/operation_listener.h @@ -0,0 +1,20 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <memory> + +namespace proton::documentmetastore { + +/** + * Interface used to listen to operations handled by the document meta store. + */ +class OperationListener { +public: + using SP = std::shared_ptr<OperationListener>; + virtual ~OperationListener() {} + virtual void notify_remove_batch() = 0; + virtual void notify_remove() = 0; +}; + +} diff --git a/searchcore/src/vespa/searchcore/proton/server/CMakeLists.txt b/searchcore/src/vespa/searchcore/proton/server/CMakeLists.txt index 92cf186f697..5f9f5528776 100644 --- a/searchcore/src/vespa/searchcore/proton/server/CMakeLists.txt +++ b/searchcore/src/vespa/searchcore/proton/server/CMakeLists.txt @@ -10,8 +10,8 @@ vespa_add_library(searchcore_server STATIC combiningfeedview.cpp ddbstate.cpp disk_mem_usage_filter.cpp - disk_mem_usage_sampler.cpp disk_mem_usage_forwarder.cpp + disk_mem_usage_sampler.cpp docstorevalidator.cpp document_db_config_owner.cpp document_db_directory_holder.cpp @@ -30,8 +30,8 @@ vespa_add_library(searchcore_server STATIC documentdb_commit_job.cpp documentdb_metrics_updater.cpp documentdbconfig.cpp - documentdbconfigscout.cpp documentdbconfigmanager.cpp + documentdbconfigscout.cpp documentretriever.cpp documentretrieverbase.cpp documentsubdbcollection.cpp @@ -63,8 +63,8 @@ vespa_add_library(searchcore_server STATIC maintenancejobrunner.cpp matchers.cpp matchview.cpp - memoryconfigstore.cpp memory_flush_config_updater.cpp + memoryconfigstore.cpp memoryflush.cpp minimal_document_retriever.cpp move_operation_limiter.cpp @@ -82,6 +82,7 @@ vespa_add_library(searchcore_server STATIC putdonecontext.cpp reconfig_params.cpp remove_batch_done_context.cpp + remove_operations_rate_tracker.cpp removedonecontext.cpp removedonetask.cpp replaypacketdispatcher.cpp diff --git a/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.cpp b/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.cpp index 57ef2d1a45c..a4e8b6752b0 100644 --- a/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.cpp @@ -53,7 +53,8 @@ DocumentDBLidSpaceCompactionConfig::DocumentDBLidSpaceCompactionConfig() _interval(3600s), _allowedLidBloat(1000000000), _allowedLidBloatFactor(1.0), - _remove_batch_block_delay(5s), + _remove_batch_block_rate(0.5), + _remove_block_rate(100000), _disabled(false), _maxDocsToScan(10000) { @@ -62,14 +63,16 @@ DocumentDBLidSpaceCompactionConfig::DocumentDBLidSpaceCompactionConfig() DocumentDBLidSpaceCompactionConfig::DocumentDBLidSpaceCompactionConfig(vespalib::duration interval, uint32_t allowedLidBloat, double allowedLidBloatFactor, - vespalib::duration remove_batch_block_delay, + double remove_batch_block_rate, + double remove_block_rate, bool disabled, uint32_t maxDocsToScan) : _delay(std::min(MAX_DELAY_SEC, interval)), _interval(interval), _allowedLidBloat(allowedLidBloat), _allowedLidBloatFactor(allowedLidBloatFactor), - _remove_batch_block_delay(remove_batch_block_delay), + _remove_batch_block_rate(remove_batch_block_rate), + _remove_block_rate(remove_block_rate), _disabled(disabled), _maxDocsToScan(maxDocsToScan) { diff --git a/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.h b/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.h index 604977aa04f..4c0485baec6 100644 --- a/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.h +++ b/searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.h @@ -47,7 +47,8 @@ private: vespalib::duration _interval; uint32_t _allowedLidBloat; double _allowedLidBloatFactor; - vespalib::duration _remove_batch_block_delay; + double _remove_batch_block_rate; + double _remove_block_rate; bool _disabled; uint32_t _maxDocsToScan; @@ -56,7 +57,8 @@ public: DocumentDBLidSpaceCompactionConfig(vespalib::duration interval, uint32_t allowedLidBloat, double allowwedLidBloatFactor, - vespalib::duration remove_batch_block_delay, + double remove_batch_block_rate, + double remove_block_rate, bool disabled, uint32_t maxDocsToScan = 10000); @@ -66,7 +68,8 @@ public: vespalib::duration getInterval() const { return _interval; } uint32_t getAllowedLidBloat() const { return _allowedLidBloat; } double getAllowedLidBloatFactor() const { return _allowedLidBloatFactor; } - vespalib::duration get_remove_batch_block_delay() const { return _remove_batch_block_delay; } + double get_remove_batch_block_rate() const { return _remove_batch_block_rate; } + double get_remove_block_rate() const { return _remove_block_rate; } bool isDisabled() const { return _disabled; } uint32_t getMaxDocsToScan() const { return _maxDocsToScan; } }; diff --git a/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp b/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp index 7a1989c8d7b..d1d88434bd6 100644 --- a/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp @@ -134,7 +134,8 @@ buildMaintenanceConfig(const BootstrapConfig::SP &bootstrapConfig, vespalib::from_s(proton.lidspacecompaction.interval), proton.lidspacecompaction.allowedlidbloat, proton.lidspacecompaction.allowedlidbloatfactor, - vespalib::from_s(proton.lidspacecompaction.removebatchblockdelay), + proton.lidspacecompaction.removebatchblockrate, + proton.lidspacecompaction.removeblockrate, isDocumentTypeGlobal), AttributeUsageFilterConfig( proton.writefilter.attribute.enumstorelimit, diff --git a/searchcore/src/vespa/searchcore/proton/server/i_lid_space_compaction_handler.h b/searchcore/src/vespa/searchcore/proton/server/i_lid_space_compaction_handler.h index 4e996f3596d..c601f516d95 100644 --- a/searchcore/src/vespa/searchcore/proton/server/i_lid_space_compaction_handler.h +++ b/searchcore/src/vespa/searchcore/proton/server/i_lid_space_compaction_handler.h @@ -10,6 +10,8 @@ namespace search { class IDestructorCallback; } +namespace proton::documentmetastore { class OperationListener; } + namespace proton { /** @@ -30,6 +32,13 @@ struct ILidSpaceCompactionHandler virtual vespalib::string getName() const = 0; /** + * Sets the listener used to get notifications on the operations handled by the document meta store. + * + * A call to this function should replace the previous listener if set. + */ + virtual void set_operation_listener(std::shared_ptr<documentmetastore::OperationListener> op_listener) = 0; + + /** * Returns the id of the sub database this handler is operating over. */ virtual uint32_t getSubDbId() const = 0; diff --git a/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_handler.cpp b/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_handler.cpp index c4dc26a0875..98de2902f46 100644 --- a/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_handler.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_handler.cpp @@ -3,10 +3,11 @@ #include "document_scan_iterator.h" #include "ifeedview.h" #include "lid_space_compaction_handler.h" +#include <vespa/document/fieldvalue/document.h> #include <vespa/searchcore/proton/docsummary/isummarymanager.h> #include <vespa/searchcore/proton/documentmetastore/i_document_meta_store_context.h> +#include <vespa/searchcore/proton/documentmetastore/operation_listener.h> #include <vespa/searchlib/common/idestructorcallback.h> -#include <vespa/document/fieldvalue/document.h> using document::BucketId; using document::Document; @@ -23,6 +24,12 @@ LidSpaceCompactionHandler::LidSpaceCompactionHandler(const MaintenanceDocumentSu { } +void +LidSpaceCompactionHandler::set_operation_listener(documentmetastore::OperationListener::SP op_listener) +{ + return _subDb.meta_store()->set_operation_listener(std::move(op_listener)); +} + LidUsageStats LidSpaceCompactionHandler::getLidStatus() const { diff --git a/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_handler.h b/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_handler.h index 21d20001923..dea573122df 100644 --- a/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_handler.h +++ b/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_handler.h @@ -23,6 +23,7 @@ public: virtual vespalib::string getName() const override { return _docTypeName + "." + _subDb.name(); } + virtual void set_operation_listener(std::shared_ptr<documentmetastore::OperationListener> op_listener) override; virtual uint32_t getSubDbId() const override { return _subDb.sub_db_id(); } virtual search::LidUsageStats getLidStatus() const override; virtual IDocumentScanIterator::UP getIterator() const override; diff --git a/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_job.cpp b/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_job.cpp index c2d655538f5..bd53810e14b 100644 --- a/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_job.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_job.cpp @@ -90,10 +90,15 @@ LidSpaceCompactionJob::compactLidSpace(const LidUsageStats &stats) } bool -LidSpaceCompactionJob::remove_batch_is_ongoing(const LidUsageStats& stats) const +LidSpaceCompactionJob::remove_batch_is_ongoing() const { - LidUsageStats::TimePoint now = std::chrono::steady_clock::now(); - return (now - stats.get_last_remove_batch()) < std::chrono::duration<double>(_cfg.get_remove_batch_block_delay()); + return _ops_rate_tracker->remove_batch_above_threshold(); +} + +bool +LidSpaceCompactionJob::remove_is_ongoing() const +{ + return _ops_rate_tracker->remove_above_threshold(); } LidSpaceCompactionJob::LidSpaceCompactionJob(const DocumentDBLidSpaceCompactionConfig &config, @@ -114,13 +119,16 @@ LidSpaceCompactionJob::LidSpaceCompactionJob(const DocumentDBLidSpaceCompactionC _retryFrozenDocument(false), _shouldCompactLidSpace(false), _diskMemUsageNotifier(diskMemUsageNotifier), - _clusterStateChangedNotifier(clusterStateChangedNotifier) + _clusterStateChangedNotifier(clusterStateChangedNotifier), + _ops_rate_tracker(std::make_shared<RemoveOperationsRateTracker>(config.get_remove_batch_block_rate(), + config.get_remove_block_rate())) { _diskMemUsageNotifier.addDiskMemUsageListener(this); _clusterStateChangedNotifier.addClusterStateChangedHandler(this); if (nodeRetired) { setBlocked(BlockedReason::CLUSTER_STATE); } + handler.set_operation_listener(_ops_rate_tracker); } LidSpaceCompactionJob::~LidSpaceCompactionJob() @@ -136,11 +144,16 @@ LidSpaceCompactionJob::run() return true; // indicate work is done since no work can be done } LidUsageStats stats = _handler.getLidStatus(); - if (remove_batch_is_ongoing(stats)) { + if (remove_batch_is_ongoing()) { // Note that we don't set the job as blocked as the decision to un-block it is not driven externally. LOG(info, "run(): Lid space compaction is disabled while remove batch (delete buckets) is ongoing"); return true; } + if (remove_is_ongoing()) { + // Note that we don't set the job as blocked as the decision to un-block it is not driven externally. + LOG(info, "run(): Lid space compaction is disabled while remove operations are ongoing"); + return true; + } if (_scanItr) { return scanDocuments(stats); } else if (_shouldCompactLidSpace) { diff --git a/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_job.h b/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_job.h index 2f242e5a33a..9171fe7472e 100644 --- a/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_job.h +++ b/searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_job.h @@ -9,6 +9,7 @@ #include "ibucketstatecalculator.h" #include "iclusterstatechangedhandler.h" #include "iclusterstatechangednotifier.h" +#include "remove_operations_rate_tracker.h" namespace proton { @@ -37,6 +38,7 @@ private: bool _shouldCompactLidSpace; IDiskMemUsageNotifier &_diskMemUsageNotifier; IClusterStateChangedNotifier &_clusterStateChangedNotifier; + std::shared_ptr<RemoveOperationsRateTracker> _ops_rate_tracker; bool hasTooMuchLidBloat(const search::LidUsageStats &stats) const; bool shouldRestartScanDocuments(const search::LidUsageStats &stats) const; @@ -45,7 +47,8 @@ private: void compactLidSpace(const search::LidUsageStats &stats); void refreshRunnable(); void refreshAndConsiderRunnable(); - bool remove_batch_is_ongoing(const search::LidUsageStats& stats) const; + bool remove_batch_is_ongoing() const; + bool remove_is_ongoing() const; public: LidSpaceCompactionJob(const DocumentDBLidSpaceCompactionConfig &config, diff --git a/searchcore/src/vespa/searchcore/proton/server/remove_operations_rate_tracker.cpp b/searchcore/src/vespa/searchcore/proton/server/remove_operations_rate_tracker.cpp new file mode 100644 index 00000000000..22294d33aba --- /dev/null +++ b/searchcore/src/vespa/searchcore/proton/server/remove_operations_rate_tracker.cpp @@ -0,0 +1,39 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "remove_operations_rate_tracker.h" +#include <vespa/vespalib/util/time.h> + +namespace proton { + +RemoveOperationsRateTracker::RemoveOperationsRateTracker(double remove_batch_rate_threshold, + double remove_rate_threshold) + : _remove_batch_tracker(remove_batch_rate_threshold), + _remove_tracker(remove_rate_threshold) +{ +} + +void +RemoveOperationsRateTracker::notify_remove_batch() +{ + _remove_batch_tracker.observe(vespalib::steady_clock::now()); +} + +void +RemoveOperationsRateTracker::notify_remove() +{ + _remove_tracker.observe(vespalib::steady_clock::now()); +} + +bool +RemoveOperationsRateTracker::remove_batch_above_threshold() const +{ + return _remove_batch_tracker.above_threshold(vespalib::steady_clock::now()); +} + +bool +RemoveOperationsRateTracker::remove_above_threshold() const +{ + return _remove_tracker.above_threshold(vespalib::steady_clock::now()); +} + +} diff --git a/searchcore/src/vespa/searchcore/proton/server/remove_operations_rate_tracker.h b/searchcore/src/vespa/searchcore/proton/server/remove_operations_rate_tracker.h new file mode 100644 index 00000000000..b8c0315b9e9 --- /dev/null +++ b/searchcore/src/vespa/searchcore/proton/server/remove_operations_rate_tracker.h @@ -0,0 +1,35 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include <vespa/searchcore/proton/common/operation_rate_tracker.h> +#include <vespa/searchcore/proton/documentmetastore/operation_listener.h> + +namespace proton { + +/** + * Class that tracks the rate of remove operations handled by the document meta store. + * + * For each operation we can tell if it is above or below a given rate threshold. + */ +class RemoveOperationsRateTracker : public documentmetastore::OperationListener { +private: + OperationRateTracker _remove_batch_tracker; + OperationRateTracker _remove_tracker; + +public: + RemoveOperationsRateTracker(double remove_batch_rate_threshold, + double remove_rate_threshold); + + void notify_remove_batch() override; + void notify_remove() override; + + bool remove_batch_above_threshold() const; + bool remove_above_threshold() const; + + // Should only be used for testing + OperationRateTracker& get_remove_batch_tracker() { return _remove_batch_tracker; } + OperationRateTracker& get_remove_tracker() { return _remove_tracker; } +}; + +} diff --git a/searchcore/src/vespa/searchcore/proton/test/document_meta_store_observer.h b/searchcore/src/vespa/searchcore/proton/test/document_meta_store_observer.h index 5cc547b4dcb..8900e58ee18 100644 --- a/searchcore/src/vespa/searchcore/proton/test/document_meta_store_observer.h +++ b/searchcore/src/vespa/searchcore/proton/test/document_meta_store_observer.h @@ -3,9 +3,9 @@ #pragma once #include <vespa/searchcore/proton/documentmetastore/i_document_meta_store.h> +#include <vespa/searchcore/proton/documentmetastore/operation_listener.h> -namespace proton { -namespace test { +namespace proton::test { struct DocumentMetaStoreObserver : public IDocumentMetaStore { @@ -185,8 +185,10 @@ struct DocumentMetaStoreObserver : public IDocumentMetaStore virtual void foreach(const search::IGidToLidMapperVisitor &visitor) const override { _store.foreach(visitor); } + virtual void set_operation_listener(documentmetastore::OperationListener::SP op_listener) override { + _store.set_operation_listener(std::move(op_listener)); + } }; } -} diff --git a/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp b/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp index 0dc8db870d9..18a6a5a8188 100644 --- a/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp +++ b/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp @@ -383,6 +383,7 @@ struct Fixture { return denseSpec; } + vespalib::FileHeader get_file_header(); void set_example_tensors(); void assert_example_tensors(); void save_example_tensors_with_mock_index(); @@ -524,18 +525,25 @@ Fixture::testCompaction() TEST_DO(assertGetTensor(empty_xy_tensor, 4)); } -void -Fixture::testTensorTypeFileHeaderTag() +vespalib::FileHeader +Fixture::get_file_header() { - ensureSpace(4); - TEST_DO(save()); - vespalib::FileHeader header; FastOS_File file; vespalib::string file_name = attr_name + ".dat"; EXPECT_TRUE(file.OpenReadOnly(file_name.c_str())); (void) header.readFile(file); file.Close(); + return header; +} + +void +Fixture::testTensorTypeFileHeaderTag() +{ + ensureSpace(4); + TEST_DO(save()); + + auto header = get_file_header(); EXPECT_TRUE(header.hasTag("tensortype")); EXPECT_EQUAL(_typeSpec, header.getTag("tensortype").asString()); if (_useDenseTensorAttribute) { @@ -761,5 +769,13 @@ TEST_F("onLoad() uses saved nearest neighbor index if only minor index parameter index.expect_adds({}); } +TEST_F("Nearest neighbor index type is added to attribute file header", DenseTensorAttributeMockIndex) +{ + f.save_example_tensors_with_mock_index(); + auto header = f.get_file_header(); + EXPECT_TRUE(header.hasTag("nearest_neighbor_index")); + EXPECT_EQUAL("hnsw", header.getTag("nearest_neighbor_index").asString()); +} + TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/searchlib/src/vespa/searchlib/attribute/attribute_header.cpp b/searchlib/src/vespa/searchlib/attribute/attribute_header.cpp index b35a88fab77..9844632cc2f 100644 --- a/searchlib/src/vespa/searchlib/attribute/attribute_header.cpp +++ b/searchlib/src/vespa/searchlib/attribute/attribute_header.cpp @@ -19,6 +19,8 @@ const vespalib::string tensorTypeTag = "tensortype"; const vespalib::string predicateArityTag = "predicate.arity"; const vespalib::string predicateLowerBoundTag = "predicate.lower_bound"; const vespalib::string predicateUpperBoundTag = "predicate.upper_bound"; +const vespalib::string nearest_neighbor_index_tag = "nearest_neighbor_index"; +const vespalib::string hnsw_index_value = "hnsw"; const vespalib::string hnsw_max_links_tag = "hnsw.max_links_per_node"; const vespalib::string hnsw_neighbors_to_explore_tag = "hnsw.neighbors_to_explore_at_insert"; const vespalib::string hnsw_distance_metric = "hnsw.distance_metric"; @@ -201,6 +203,7 @@ AttributeHeader::addTags(vespalib::GenericHeader &header) const if (_basicType.type() == attribute::BasicType::Type::TENSOR) { header.putTag(Tag(tensorTypeTag, _tensorType.to_spec()));; if (_hnsw_index_params.has_value()) { + header.putTag(Tag(nearest_neighbor_index_tag, hnsw_index_value)); const auto& params = *_hnsw_index_params; header.putTag(Tag(hnsw_max_links_tag, params.max_links_per_node())); header.putTag(Tag(hnsw_neighbors_to_explore_tag, params.neighbors_to_explore_at_insert())); diff --git a/searchlib/src/vespa/searchlib/common/lid_usage_stats.h b/searchlib/src/vespa/searchlib/common/lid_usage_stats.h index b54a3ce8a6a..1dd3881892f 100644 --- a/searchlib/src/vespa/searchlib/common/lid_usage_stats.h +++ b/searchlib/src/vespa/searchlib/common/lid_usage_stats.h @@ -18,34 +18,29 @@ private: uint32_t _usedLids; uint32_t _lowestFreeLid; uint32_t _highestUsedLid; - TimePoint _last_remove_batch; public: LidUsageStats() : _lidLimit(0), _usedLids(0), _lowestFreeLid(0), - _highestUsedLid(0), - _last_remove_batch() + _highestUsedLid(0) { } LidUsageStats(uint32_t lidLimit, uint32_t usedLids, uint32_t lowestFreeLid, - uint32_t highestUsedLid, - TimePoint last_remove_batch) + uint32_t highestUsedLid) : _lidLimit(lidLimit), _usedLids(usedLids), _lowestFreeLid(lowestFreeLid), - _highestUsedLid(highestUsedLid), - _last_remove_batch(last_remove_batch) + _highestUsedLid(highestUsedLid) { } uint32_t getLidLimit() const { return _lidLimit; } uint32_t getUsedLids() const { return _usedLids; } uint32_t getLowestFreeLid() const { return _lowestFreeLid; } uint32_t getHighestUsedLid() const { return _highestUsedLid; } - const TimePoint& get_last_remove_batch() const { return _last_remove_batch; } uint32_t getLidBloat() const { // Account for reserved lid 0 int32_t lidBloat = getLidLimit() - getUsedLids() - 1; diff --git a/vespajlib/src/main/java/com/yahoo/concurrent/maintenance/package-info.java b/vespajlib/src/main/java/com/yahoo/concurrent/maintenance/package-info.java new file mode 100644 index 00000000000..71890d86c31 --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/concurrent/maintenance/package-info.java @@ -0,0 +1,8 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * @author mpolden + */ +@ExportPackage +package com.yahoo.concurrent.maintenance; + +import com.yahoo.osgi.annotation.ExportPackage; |