summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java4
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java15
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/DistributorCluster.java14
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/ml/ConvertedModel.java10
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java31
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java16
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ArtifactRepository.java2
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java5
-rw-r--r--dist/vespa.spec3
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flags.java12
-rw-r--r--model-integration/src/main/java/ai/vespa/rankingexpression/importer/ImportedModel.java6
-rw-r--r--model-integration/src/main/java/ai/vespa/rankingexpression/importer/configmodelview/ImportedMlModel.java3
-rw-r--r--model-integration/src/main/java/ai/vespa/rankingexpression/importer/onnx/TypeConverter.java8
-rw-r--r--model-integration/src/test/java/ai/vespa/rankingexpression/importer/onnx/OnnxMnistSoftmaxImportTestCase.java8
-rw-r--r--model-integration/src/test/java/ai/vespa/rankingexpression/importer/onnx/TestableModel.java4
-rw-r--r--model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/RegressionTestCase.java4
-rw-r--r--model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/TensorFlowMnistSoftmaxImportTestCase.java4
-rw-r--r--model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/TestableTensorFlowModel.java4
-rw-r--r--model-integration/src/test/java/ai/vespa/rankingexpression/importer/tensorflow/Tf2OnnxImportTestCase.java4
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainer.java4
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/StorageMaintainerTest.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/CuratorDatabaseClient.java27
-rw-r--r--searchcore/CMakeLists.txt1
-rw-r--r--searchcore/src/tests/proton/common/operation_rate_tracker/CMakeLists.txt9
-rw-r--r--searchcore/src/tests/proton/common/operation_rate_tracker/operation_rate_tracker_test.cpp90
-rw-r--r--searchcore/src/tests/proton/documentdb/lid_space_compaction/lid_space_compaction_test.cpp139
-rw-r--r--searchcore/src/tests/proton/documentdb/maintenancecontroller/maintenancecontroller_test.cpp4
-rw-r--r--searchcore/src/tests/proton/documentmetastore/documentmetastore_test.cpp40
-rw-r--r--searchcore/src/vespa/searchcore/config/proton.def19
-rw-r--r--searchcore/src/vespa/searchcore/proton/common/CMakeLists.txt3
-rw-r--r--searchcore/src/vespa/searchcore/proton/common/operation_rate_tracker.cpp33
-rw-r--r--searchcore/src/vespa/searchcore/proton/common/operation_rate_tracker.h38
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp19
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.h12
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/i_document_meta_store.h4
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/operation_listener.h20
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/CMakeLists.txt7
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.cpp9
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/document_db_maintenance_config.h9
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/documentdbconfigmanager.cpp3
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/i_lid_space_compaction_handler.h9
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_handler.cpp9
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_handler.h1
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_job.cpp23
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/lid_space_compaction_job.h5
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/remove_operations_rate_tracker.cpp39
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/remove_operations_rate_tracker.h35
-rw-r--r--searchcore/src/vespa/searchcore/proton/test/document_meta_store_observer.h8
-rw-r--r--searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp26
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attribute_header.cpp3
-rw-r--r--searchlib/src/vespa/searchlib/common/lid_usage_stats.h11
-rw-r--r--vespajlib/src/main/java/com/yahoo/concurrent/maintenance/package-info.java8
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;