diff --git a/assembly/dependencies-apache-ignite-slim.xml b/assembly/dependencies-apache-ignite-slim.xml
index e98695c8a0c85..6d222c7d0446e 100644
--- a/assembly/dependencies-apache-ignite-slim.xml
+++ b/assembly/dependencies-apache-ignite-slim.xml
@@ -156,6 +156,7 @@
org.apache.ignite:ignite-ml-h2o-model-parserorg.apache.ignite:ignite-ml-spark-model-parserorg.apache.ignite:ignite-ml-xgboost-model-parser
+ org.apache.ignite:ignite-ml-catboost-model-parserorg.apache.ignite:ignite-osgiorg.apache.ignite:ignite-osgi-karaforg.apache.ignite:ignite-osgi-paxlogging
diff --git a/docs/_docs/index.adoc b/docs/_docs/index.adoc
index 2a3ceb66fe0c3..73686d8a20069 100644
--- a/docs/_docs/index.adoc
+++ b/docs/_docs/index.adoc
@@ -19,7 +19,7 @@ applications that can process terabytes of data with in-memory speed.
Ignite documentation introduces you to the project's main capabilities, shows how to use certain features, or how to
approach cluster optimizations and issues troubleshooting. If you are new to Ignite, then start with the
-link:docs/latest/quick-start/java[Quick Start Guides], and build the first application in a matter of 5-10 minutes.
+link:quick-start/java[Quick Start Guides], and build the first application in a matter of 5-10 minutes.
Otherwise, select the topic of your interest and have your problems solved, and questions answered.
Good luck with your Ignite journey!
diff --git a/examples/pom-standalone-lgpl.xml b/examples/pom-standalone-lgpl.xml
index 6d2fe4142285c..a21776df7fac0 100644
--- a/examples/pom-standalone-lgpl.xml
+++ b/examples/pom-standalone-lgpl.xml
@@ -110,6 +110,12 @@
to_be_replaced_by_ignite_version
+
+ org.apache.ignite
+ ignite-ml-catboost-model-parser
+ to_be_replaced_by_ignite_version
+
+
org.apache.igniteignite-ml-spark-model-parser
diff --git a/examples/pom-standalone.xml b/examples/pom-standalone.xml
index ca1d0972d8c98..ccce3554c072c 100644
--- a/examples/pom-standalone.xml
+++ b/examples/pom-standalone.xml
@@ -110,6 +110,12 @@
to_be_replaced_by_ignite_version
+
+ org.apache.ignite
+ ignite-ml-catboost-model-parser
+ to_be_replaced_by_ignite_version
+
+
org.apache.igniteignite-ml-spark-model-parser
diff --git a/examples/pom.xml b/examples/pom.xml
index 25a5b87852a8b..62087eda3c521 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -120,6 +120,12 @@
${project.version}
+
+ org.apache.ignite
+ ignite-ml-catboost-model-parser
+ ${project.version}
+
+
org.apache.igniteignite-ml-h2o-model-parser
diff --git a/examples/src/main/java/org/apache/ignite/examples/ml/inference/catboost/CatboostClassificationModelParserExample.java b/examples/src/main/java/org/apache/ignite/examples/ml/inference/catboost/CatboostClassificationModelParserExample.java
new file mode 100644
index 0000000000000..e6f9f657a8a18
--- /dev/null
+++ b/examples/src/main/java/org/apache/ignite/examples/ml/inference/catboost/CatboostClassificationModelParserExample.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.examples.ml.inference.catboost;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.HashMap;
+import java.util.Scanner;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.ml.catboost.CatboostClassificationModelParser;
+import org.apache.ignite.ml.inference.Model;
+import org.apache.ignite.ml.inference.builder.AsyncModelBuilder;
+import org.apache.ignite.ml.inference.builder.IgniteDistributedModelBuilder;
+import org.apache.ignite.ml.inference.reader.FileSystemModelReader;
+import org.apache.ignite.ml.inference.reader.ModelReader;
+import org.apache.ignite.ml.math.primitives.vector.NamedVector;
+import org.apache.ignite.ml.math.primitives.vector.VectorUtils;
+
+/**
+ * This example demonstrates how to import Catboost model and use imported model for distributed inference in Apache
+ * Ignite.
+ */
+public class CatboostClassificationModelParserExample {
+ /**
+ * Test model resource name.
+ */
+ private static final String TEST_MODEL_RES = "examples/src/main/resources/models/catboost/model_clf.cbm";
+
+ /**
+ * Test data.
+ */
+ private static final String TEST_DATA_RES = "examples/src/main/resources/datasets/amazon-employee-access-challenge-sample.csv";
+
+ /**
+ * Test expected results.
+ */
+ private static final String TEST_ER_RES = "examples/src/main/resources/datasets/amazon-employee-access-challenge-sample-catboost-expected-results.csv";
+
+ /**
+ * Parser.
+ */
+ private static final CatboostClassificationModelParser parser = new CatboostClassificationModelParser();
+
+ /**
+ * Run example.
+ */
+ public static void main(String... args) throws ExecutionException, InterruptedException,
+ FileNotFoundException {
+ try (Ignite ignite = Ignition.start("examples/config/example-ignite.xml")) {
+ File mdlRsrc = IgniteUtils.resolveIgnitePath(TEST_MODEL_RES);
+ if (mdlRsrc == null)
+ throw new IllegalArgumentException("File not found [resource_path=" + TEST_MODEL_RES + "]");
+
+ ModelReader reader = new FileSystemModelReader(mdlRsrc.getPath());
+
+ AsyncModelBuilder mdlBuilder = new IgniteDistributedModelBuilder(ignite, 4, 4);
+
+ File testData = IgniteUtils.resolveIgnitePath(TEST_DATA_RES);
+ if (testData == null)
+ throw new IllegalArgumentException("File not found [resource_path=" + TEST_DATA_RES + "]");
+
+ File testExpRes = IgniteUtils.resolveIgnitePath(TEST_ER_RES);
+ if (testExpRes == null)
+ throw new IllegalArgumentException("File not found [resource_path=" + TEST_ER_RES + "]");
+
+ try (Model> mdl = mdlBuilder.build(reader, parser);
+ Scanner testDataScanner = new Scanner(testData);
+ Scanner testExpResultsScanner = new Scanner(testExpRes)) {
+ String header = testDataScanner.nextLine();
+ String[] columns = header.split(",");
+
+ while (testDataScanner.hasNextLine()) {
+ String testDataStr = testDataScanner.nextLine();
+ String testExpResultsStr = testExpResultsScanner.nextLine();
+
+ HashMap testObj = new HashMap<>();
+ String[] values = testDataStr.split(",");
+
+ for (int i = 0; i < columns.length; i++) {
+ testObj.put(columns[i], Double.valueOf(values[i]));
+ }
+
+ double prediction = mdl.predict(VectorUtils.of(testObj)).get();
+ double expPrediction = Double.parseDouble(testExpResultsStr);
+
+ System.out.println("Expected: " + expPrediction + ", prediction: " + prediction);
+ }
+ }
+ }
+ finally {
+ System.out.flush();
+ }
+ }
+}
diff --git a/examples/src/main/java/org/apache/ignite/examples/ml/inference/catboost/CatboostRegressionModelParserExample.java b/examples/src/main/java/org/apache/ignite/examples/ml/inference/catboost/CatboostRegressionModelParserExample.java
new file mode 100644
index 0000000000000..3e5e25850afeb
--- /dev/null
+++ b/examples/src/main/java/org/apache/ignite/examples/ml/inference/catboost/CatboostRegressionModelParserExample.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.examples.ml.inference.catboost;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.HashMap;
+import java.util.Scanner;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.ml.catboost.CatboostRegressionModelParser;
+import org.apache.ignite.ml.inference.Model;
+import org.apache.ignite.ml.inference.builder.AsyncModelBuilder;
+import org.apache.ignite.ml.inference.builder.IgniteDistributedModelBuilder;
+import org.apache.ignite.ml.inference.reader.FileSystemModelReader;
+import org.apache.ignite.ml.inference.reader.ModelReader;
+import org.apache.ignite.ml.math.primitives.vector.NamedVector;
+import org.apache.ignite.ml.math.primitives.vector.VectorUtils;
+
+/**
+ * This example demonstrates how to import Catboost model and use imported model for distributed inference in Apache
+ * Ignite.
+ */
+public class CatboostRegressionModelParserExample {
+ /**
+ * Test model resource name.
+ * */
+ private static final String TEST_MODEL_RES = "examples/src/main/resources/models/catboost/model_reg.cbm";
+
+ /**
+ * Test data.
+ */
+ private static final String TEST_DATA_RES = "examples/src/main/resources/datasets/boston_housing_dataset.txt";
+
+ /**
+ * Test expected results.
+ */
+ private static final String TEST_ER_RES = "examples/src/main/resources/datasets/boston_housing_dataset-catboost-expected-results.txt";
+
+ /**
+ * Parser.
+ */
+ private static final CatboostRegressionModelParser parser = new CatboostRegressionModelParser();
+
+ /**
+ * Run example.
+ */
+ public static void main(String... args) throws ExecutionException, InterruptedException,
+ FileNotFoundException {
+ try (Ignite ignite = Ignition.start("examples/config/example-ignite.xml")) {
+ File mdlRsrc = IgniteUtils.resolveIgnitePath(TEST_MODEL_RES);
+ if (mdlRsrc == null)
+ throw new IllegalArgumentException("File not found [resource_path=" + TEST_MODEL_RES + "]");
+
+ ModelReader reader = new FileSystemModelReader(mdlRsrc.getPath());
+ AsyncModelBuilder mdlBuilder = new IgniteDistributedModelBuilder(ignite, 4, 4);
+
+ File testData = IgniteUtils.resolveIgnitePath(TEST_DATA_RES);
+ if (testData == null)
+ throw new IllegalArgumentException("File not found [resource_path=" + TEST_DATA_RES + "]");
+
+ File testExpRes = IgniteUtils.resolveIgnitePath(TEST_ER_RES);
+ if (testExpRes == null)
+ throw new IllegalArgumentException("File not found [resource_path=" + TEST_ER_RES + "]");
+
+ try (Model> mdl = mdlBuilder.build(reader, parser);
+ Scanner testDataScanner = new Scanner(testData);
+ Scanner testExpResultsScanner = new Scanner(testExpRes)) {
+ String[] columns = new String[]{
+ "f_0",
+ "f_1",
+ "f_2",
+ "f_3",
+ "f_4",
+ "f_5",
+ "f_6",
+ "f_7",
+ "f_8",
+ "f_9",
+ "f_10",
+ "f_11",
+ "f_12",
+ };
+
+ while (testDataScanner.hasNextLine()) {
+ String testDataStr = testDataScanner.nextLine();
+ String testExpResultsStr = testExpResultsScanner.nextLine();
+
+ HashMap testObj = new HashMap<>();
+ String[] values = testDataStr.split(",");
+
+ for (int i = 0; i < columns.length; i++) {
+ testObj.put(columns[i], Double.valueOf(values[i]));
+ }
+
+ double prediction = mdl.predict(VectorUtils.of(testObj)).get();
+ double expPrediction = Double.parseDouble(testExpResultsStr);
+
+ System.out.println("Expected: " + expPrediction + ", prediction: " + prediction);
+ }
+ }
+ }
+ finally {
+ System.out.flush();
+ }
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/util/InsertLast.java b/examples/src/main/java/org/apache/ignite/examples/ml/inference/catboost/package-info.java
similarity index 80%
rename from modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/util/InsertLast.java
rename to examples/src/main/java/org/apache/ignite/examples/ml/inference/catboost/package-info.java
index e7cfd411f3755..70bbbd2b8b902 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/util/InsertLast.java
+++ b/examples/src/main/java/org/apache/ignite/examples/ml/inference/catboost/package-info.java
@@ -15,10 +15,8 @@
* limitations under the License.
*/
-package org.apache.ignite.internal.processors.cache.persistence.tree.util;
-
/**
- * Rows with this marker interface will always be inserted in the very end of the tree.
+ * XGBoost model inference examples.
*/
-public interface InsertLast {
-}
+
+package org.apache.ignite.examples.ml.inference.catboost;
diff --git a/examples/src/main/java/org/apache/ignite/examples/ml/preprocessing/encoding/TargetEncoderExample.java b/examples/src/main/java/org/apache/ignite/examples/ml/preprocessing/encoding/TargetEncoderExample.java
new file mode 100644
index 0000000000000..e3864b6721d79
--- /dev/null
+++ b/examples/src/main/java/org/apache/ignite/examples/ml/preprocessing/encoding/TargetEncoderExample.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.examples.ml.preprocessing.encoding;
+
+import java.io.FileNotFoundException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.examples.ml.util.MLSandboxDatasets;
+import org.apache.ignite.examples.ml.util.SandboxMLCache;
+import org.apache.ignite.ml.composition.ModelsComposition;
+import org.apache.ignite.ml.composition.boosting.GDBTrainer;
+import org.apache.ignite.ml.composition.boosting.convergence.median.MedianOfMedianConvergenceCheckerFactory;
+import org.apache.ignite.ml.dataset.feature.extractor.Vectorizer;
+import org.apache.ignite.ml.dataset.feature.extractor.impl.ObjectArrayVectorizer;
+import org.apache.ignite.ml.preprocessing.Preprocessor;
+import org.apache.ignite.ml.preprocessing.encoding.EncoderTrainer;
+import org.apache.ignite.ml.preprocessing.encoding.EncoderType;
+import org.apache.ignite.ml.selection.scoring.evaluator.Evaluator;
+import org.apache.ignite.ml.selection.scoring.metric.classification.Accuracy;
+import org.apache.ignite.ml.tree.boosting.GDBBinaryClassifierOnTreesTrainer;
+
+/**
+ * Example that shows how to use Target Encoder preprocessor to encode labels presented as a mean target value.
+ *
+ * Code in this example launches Ignite grid and fills the cache with test data (based on mushrooms dataset).
+ *
+ * After that it defines preprocessors that extract features from an upstream data and encode category with avarage
+ * target value (categories).
+ *
+ * Then, it trains the model based on the processed data using gradient boosing decision tree classification.
+ *
+ * Finally, this example uses {@link Evaluator} functionality to compute metrics from predictions.
+ *
+ *
Daniele Miccii-Barreca (2001). A Preprocessing Scheme for High-Cardinality Categorical
+ * Attributes in Classification and Prediction Problems. SIGKDD Explor. Newsl. 3, 1.
+ * From http://dx.doi.org/10.1145/507533.507538
* This implementation will discard backups rather than place multiple on the same set of nodes. This avoids
* trying to cram more data onto remaining nodes when some have failed.
@@ -91,7 +91,7 @@ public class ClusterNodeAttributeAffinityBackupFilter implements IgniteBiPredica
public ClusterNodeAttributeAffinityBackupFilter(String... attributeNames) {
A.ensure(attributeNames.length > 0, "attributeNames.length > 0");
- this.attributeNames = attributeNames;
+ this.attributeNames = attributeNames.clone();
}
/**
@@ -128,4 +128,12 @@ public ClusterNodeAttributeAffinityBackupFilter(String... attributeNames) {
return true;
}
+ /**
+ * Gets attribute names.
+ *
+ * @return Attribute names.
+ */
+ public String[] getAttributeNames() {
+ return attributeNames.clone();
+ }
}
diff --git a/modules/core/src/main/java/org/apache/ignite/configuration/DataStorageConfiguration.java b/modules/core/src/main/java/org/apache/ignite/configuration/DataStorageConfiguration.java
index 2a1927b79fdaa..2fe48248bd128 100644
--- a/modules/core/src/main/java/org/apache/ignite/configuration/DataStorageConfiguration.java
+++ b/modules/core/src/main/java/org/apache/ignite/configuration/DataStorageConfiguration.java
@@ -72,6 +72,9 @@ public class DataStorageConfiguration implements Serializable {
/** */
private static final long serialVersionUID = 0L;
+ /** Value used for making WAL archive size unlimited */
+ public static final long UNLIMITED_WAL_ARCHIVE = -1;
+
/** Default data region start size (256 MB). */
public static final long DFLT_DATA_REGION_INITIAL_SIZE = 256L * 1024 * 1024;
@@ -594,21 +597,26 @@ public boolean isWalHistorySizeParameterUsed() {
/**
* Gets a max allowed size(in bytes) of WAL archives.
*
- * @return max size(in bytes) of WAL archive directory(always greater than 0).
+ * @return max size(in bytes) of WAL archive directory(greater than 0, or {@link #UNLIMITED_WAL_ARCHIVE} if
+ * WAL archive size is unlimited).
*/
public long getMaxWalArchiveSize() {
- return maxWalArchiveSize <= 0 ? DFLT_WAL_ARCHIVE_MAX_SIZE : maxWalArchiveSize;
+ return maxWalArchiveSize;
}
/**
* Sets a max allowed size(in bytes) of WAL archives.
*
- * If value is not positive, {@link #DFLT_WAL_ARCHIVE_MAX_SIZE} will be used.
+ * If value is not positive or {@link #UNLIMITED_WAL_ARCHIVE}, {@link #DFLT_WAL_ARCHIVE_MAX_SIZE} will be used.
*
* @param walArchiveMaxSize max size(in bytes) of WAL archive directory.
* @return {@code this} for chaining.
*/
public DataStorageConfiguration setMaxWalArchiveSize(long walArchiveMaxSize) {
+ if (walArchiveMaxSize != UNLIMITED_WAL_ARCHIVE)
+ A.ensure(walArchiveMaxSize > 0, "Max WAL archive size can be only greater than 0 " +
+ "or must be equal to " + UNLIMITED_WAL_ARCHIVE + " (to be unlimited)");
+
this.maxWalArchiveSize = walArchiveMaxSize;
return this;
diff --git a/modules/core/src/main/java/org/apache/ignite/events/EventType.java b/modules/core/src/main/java/org/apache/ignite/events/EventType.java
index 3da980109d906..6fb70569d73c9 100644
--- a/modules/core/src/main/java/org/apache/ignite/events/EventType.java
+++ b/modules/core/src/main/java/org/apache/ignite/events/EventType.java
@@ -925,6 +925,11 @@ public interface EventType {
/**
* Built-in event type: query execution.
+ * This event is triggered after a corresponding SQL query validated and before it is executed.
+ * Unlike {@link #EVT_CACHE_QUERY_EXECUTED}, {@code EVT_SQL_QUERY_EXECUTION} is fired only once for a request
+ * and does not relate to a specific cache.
+ * Enet includes the following information: qurey text and its arguments, security subject id.
+ *
*
* NOTE: all types in range from 1 to 1000 are reserved for
* internal Ignite events and should not be used by user-defined events.
diff --git a/modules/core/src/main/java/org/apache/ignite/events/SqlQueryExecutionEvent.java b/modules/core/src/main/java/org/apache/ignite/events/SqlQueryExecutionEvent.java
index 4700d7b9fff15..d8feb07551394 100644
--- a/modules/core/src/main/java/org/apache/ignite/events/SqlQueryExecutionEvent.java
+++ b/modules/core/src/main/java/org/apache/ignite/events/SqlQueryExecutionEvent.java
@@ -28,6 +28,10 @@
/**
* Query execution event.
+ * This event is triggered after a corresponding SQL query validated and before it is executed.
+ * Unlike {@link EventType#EVT_CACHE_QUERY_EXECUTED}, {@link EventType#EVT_SQL_QUERY_EXECUTION} is fired only once for a request
+ * and does not relate to a specific cache.
+ *
*
* Grid events are used for notification about what happens within the grid. Note that by
* design Ignite keeps all events generated on the local node locally and it provides
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java
index 56f976534fe84..08c9382334640 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java
@@ -39,6 +39,7 @@
import org.apache.ignite.internal.processors.authentication.IgniteAuthenticationProcessor;
import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
import org.apache.ignite.internal.processors.cache.mvcc.MvccProcessor;
+import org.apache.ignite.internal.processors.cache.persistence.defragmentation.IgniteDefragmentation;
import org.apache.ignite.internal.processors.cache.persistence.filename.PdsFoldersResolver;
import org.apache.ignite.internal.processors.cacheobject.IgniteCacheObjectProcessor;
import org.apache.ignite.internal.processors.closure.GridClosureProcessor;
@@ -452,6 +453,13 @@ public interface GridKernalContext extends Iterable {
*/
public GridEncryptionManager encryption();
+ /**
+ * Gets defragmentation manager.
+ *
+ * @return Defragmentation manager.
+ */
+ public IgniteDefragmentation defragmentation();
+
/**
* Gets workers registry.
*
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java
index ae589adff86e3..78d88a945ebe8 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java
@@ -58,6 +58,8 @@
import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
import org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl;
import org.apache.ignite.internal.processors.cache.mvcc.MvccProcessor;
+import org.apache.ignite.internal.processors.cache.persistence.defragmentation.IgniteDefragmentation;
+import org.apache.ignite.internal.processors.cache.persistence.defragmentation.IgniteDefragmentationImpl;
import org.apache.ignite.internal.processors.cache.persistence.filename.PdsFoldersResolver;
import org.apache.ignite.internal.processors.cacheobject.IgniteCacheObjectProcessor;
import org.apache.ignite.internal.processors.closure.GridClosureProcessor;
@@ -174,6 +176,10 @@ public class GridKernalContextImpl implements GridKernalContext, Externalizable
@GridToStringExclude
private GridEncryptionManager encryptionMgr;
+ /** */
+ @GridToStringExclude
+ private IgniteDefragmentation defragMgr;
+
/** */
@GridToStringExclude
private GridTracingManager tracingMgr;
@@ -557,6 +563,8 @@ protected GridKernalContextImpl(
marshCtx = new MarshallerContextImpl(plugins, clsFilter);
+ defragMgr = new IgniteDefragmentationImpl(this);
+
try {
spring = SPRING.create(false);
}
@@ -906,6 +914,11 @@ public void addHelper(Object helper) {
return encryptionMgr;
}
+ /** {@inheritDoc} */
+ @Override public IgniteDefragmentation defragmentation() {
+ return defragMgr;
+ }
+
/** {@inheritDoc} */
@Override public WorkersRegistry workersRegistry() {
return workersRegistry;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/IgniteMBeansManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/IgniteMBeansManager.java
index 550b60bf0eaae..81d84657bced3 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/managers/IgniteMBeansManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/IgniteMBeansManager.java
@@ -38,6 +38,7 @@
import org.apache.ignite.internal.TransactionsMXBeanImpl;
import org.apache.ignite.internal.managers.encryption.EncryptionMXBeanImpl;
import org.apache.ignite.internal.processors.cache.persistence.DataStorageMXBeanImpl;
+import org.apache.ignite.internal.processors.cache.persistence.defragmentation.DefragmentationMXBeanImpl;
import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotMXBeanImpl;
import org.apache.ignite.internal.processors.cache.warmup.WarmUpMXBeanImpl;
import org.apache.ignite.internal.processors.cluster.BaselineAutoAdjustMXBeanImpl;
@@ -51,6 +52,7 @@
import org.apache.ignite.mxbean.ClusterMetricsMXBean;
import org.apache.ignite.mxbean.ComputeMXBean;
import org.apache.ignite.mxbean.DataStorageMXBean;
+import org.apache.ignite.mxbean.DefragmentationMXBean;
import org.apache.ignite.mxbean.EncryptionMXBean;
import org.apache.ignite.mxbean.FailureHandlingMxBean;
import org.apache.ignite.mxbean.IgniteMXBean;
@@ -185,6 +187,10 @@ public void registerMBeansAfterNodeStarted(
SnapshotMXBean snpMXBean = new SnapshotMXBeanImpl(ctx);
registerMBean("Snapshot", snpMXBean.getClass().getSimpleName(), snpMXBean, SnapshotMXBean.class);
+ // Defragmentation.
+ DefragmentationMXBean defragMXBean = new DefragmentationMXBeanImpl(ctx);
+ registerMBean("Defragmentation", defragMXBean.getClass().getSimpleName(), defragMXBean, DefragmentationMXBean.class);
+
// Metrics configuration
MetricsMxBean metricsMxBean = new MetricsMxBeanImpl(ctx.metric(), log);
registerMBean("Metrics", metricsMxBean.getClass().getSimpleName(), metricsMxBean, MetricsMxBean.class);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/GridDiscoveryManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/GridDiscoveryManager.java
index 65a331fcb0e04..e686e55e178a3 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/GridDiscoveryManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/discovery/GridDiscoveryManager.java
@@ -37,7 +37,6 @@
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
-
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteClientDisconnectedException;
import org.apache.ignite.IgniteException;
@@ -101,6 +100,7 @@
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.P1;
import org.apache.ignite.internal.util.typedef.T2;
+import org.apache.ignite.internal.util.typedef.X;
import org.apache.ignite.internal.util.typedef.internal.CU;
import org.apache.ignite.internal.util.typedef.internal.LT;
import org.apache.ignite.internal.util.typedef.internal.S;
@@ -2740,18 +2740,19 @@ public synchronized void submit(GridFutureAdapter notificationFut, Runnable cmd)
try {
body0();
}
- catch (InterruptedException e) {
- if (!isCancelled)
- ctx.failure().process(new FailureContext(SYSTEM_WORKER_TERMINATION, e));
-
- throw e;
- }
catch (Throwable t) {
- U.error(log, "Exception in discovery notyfier worker thread.", t);
+ boolean isInterruptedException = X.hasCause(t, InterruptedException.class)
+ || X.hasCause(t, IgniteInterruptedException.class)
+ || X.hasCause(t, IgniteInterruptedCheckedException.class);
- FailureType type = t instanceof OutOfMemoryError ? CRITICAL_ERROR : SYSTEM_WORKER_TERMINATION;
+ if (!isInterruptedException)
+ U.error(log, "Exception in discovery notifier worker thread.", t);
- ctx.failure().process(new FailureContext(type, t));
+ if (!isInterruptedException || !isCancelled) {
+ FailureType type = t instanceof OutOfMemoryError ? CRITICAL_ERROR : SYSTEM_WORKER_TERMINATION;
+
+ ctx.failure().process(new FailureContext(type, t));
+ }
throw t;
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/IgniteWriteAheadLogManager.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/IgniteWriteAheadLogManager.java
index cb4fc306cdb49..f3d85c597c0ef 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/IgniteWriteAheadLogManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/wal/IgniteWriteAheadLogManager.java
@@ -146,15 +146,14 @@ public WALIterator replay(
public void release(WALPointer start) throws IgniteCheckedException;
/**
- * Gives a hint to WAL manager to clear entries logged before the given pointer. Some entries before the
- * the given pointer will be kept because there is a configurable WAL history size. Those entries may be used
- * for partial partition rebalancing.
+ * Gives a hint to WAL manager to clear entries logged before the given pointer.
+ * If entries are needed for binary recovery, they will not be affected.
+ * Some entries may be reserved eg for historical rebalance and they also will not be affected.
*
- * @param low Pointer since which WAL will be truncated. If null, WAL will be truncated from the oldest segment.
- * @param high Pointer for which it is safe to clear the log.
+ * @param high Upper border to which WAL segments will be deleted.
* @return Number of deleted WAL segments.
*/
- public int truncate(WALPointer low, WALPointer high);
+ public int truncate(@Nullable WALPointer high);
/**
* Notifies {@code this} about latest checkpoint pointer.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java
index 4a1aceb068bf2..ae33bf8e7aec1 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java
@@ -33,6 +33,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
@@ -150,7 +151,6 @@
import org.apache.ignite.internal.util.F0;
import org.apache.ignite.internal.util.IgniteCollectors;
import org.apache.ignite.internal.util.InitializationProtector;
-import org.apache.ignite.internal.util.StripedExecutor;
import org.apache.ignite.internal.util.future.GridCompoundFuture;
import org.apache.ignite.internal.util.future.GridFinishedFuture;
import org.apache.ignite.internal.util.future.GridFutureAdapter;
@@ -192,7 +192,6 @@
import static java.util.Arrays.asList;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
-import static org.apache.ignite.IgniteSystemProperties.IGNITE_ALLOW_START_CACHES_IN_PARALLEL;
import static org.apache.ignite.IgniteSystemProperties.IGNITE_CACHE_REMOVED_ENTRIES_TTL;
import static org.apache.ignite.IgniteSystemProperties.IGNITE_SKIP_CONFIGURATION_CONSISTENCY_CHECK;
import static org.apache.ignite.IgniteSystemProperties.getBoolean;
@@ -5465,12 +5464,12 @@ private void restorePartitionStates(
AtomicReference restoreStateError = new AtomicReference<>();
- StripedExecutor stripedExec = ctx.getStripedExecutorService();
+ ExecutorService sysPool = ctx.getSystemExecutorService();
- int roundRobin = 0;
+ CountDownLatch completionLatch = new CountDownLatch(forGroups.size());
for (CacheGroupContext grp : forGroups) {
- stripedExec.execute(roundRobin % stripedExec.stripesCount(), () -> {
+ sysPool.execute(() -> {
try {
long processed = grp.offheap().restorePartitionStates(partitionStates);
@@ -5487,14 +5486,15 @@ private void restorePartitionStates(
: new IgniteCheckedException(e)
);
}
+ finally {
+ completionLatch.countDown();
+ }
});
-
- roundRobin++;
}
try {
// Await completion restore state tasks in all stripes.
- stripedExec.awaitComplete();
+ completionLatch.await();
}
catch (InterruptedException e) {
throw new IgniteInterruptedException(e);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java
index 4a9435c378c93..65e4b4d1b663f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java
@@ -134,6 +134,8 @@
import org.apache.ignite.lang.IgniteRunnable;
import org.jetbrains.annotations.Nullable;
+import static java.util.Collections.emptySet;
+import static java.util.stream.Stream.concat;
import static org.apache.ignite.IgniteSystemProperties.IGNITE_LONG_OPERATIONS_DUMP_TIMEOUT_LIMIT;
import static org.apache.ignite.IgniteSystemProperties.IGNITE_PARTITION_RELEASE_FUTURE_DUMP_THRESHOLD;
import static org.apache.ignite.IgniteSystemProperties.IGNITE_THREAD_DUMP_ON_EXCHANGE_TIMEOUT;
@@ -1237,7 +1239,7 @@ private void updateTopologies(boolean crd) throws IgniteCheckedException {
top.update(null,
clientTop.partitionMap(true),
clientTop.fullUpdateCounters(),
- Collections.emptySet(),
+ emptySet(),
null,
null,
null,
@@ -3933,7 +3935,7 @@ private void finishExchangeOnCoordinator(@Nullable Collection sndRe
assert firstDiscoEvt instanceof DiscoveryCustomEvent;
if (activateCluster() || changedBaseline())
- assignPartitionsStates(true);
+ assignPartitionsStates(null);
DiscoveryCustomMessage discoveryCustomMessage = ((DiscoveryCustomEvent) firstDiscoEvt).customMessage();
@@ -3944,20 +3946,26 @@ private void finishExchangeOnCoordinator(@Nullable Collection sndRe
if (!F.isEmpty(caches))
resetLostPartitions(caches);
- assignPartitionsStates(true);
+ Set cacheGroupsToResetOwners = concat(exchActions.cacheGroupsToStart().stream()
+ .map(grp -> grp.descriptor().groupId()),
+ exchActions.cachesToResetLostPartitions().stream()
+ .map(CU::cacheId))
+ .collect(Collectors.toSet());
+
+ assignPartitionsStates(cacheGroupsToResetOwners);
}
}
else if (discoveryCustomMessage instanceof SnapshotDiscoveryMessage
&& ((SnapshotDiscoveryMessage)discoveryCustomMessage).needAssignPartitions()) {
markAffinityReassign();
- assignPartitionsStates(true);
+ assignPartitionsStates(null);
}
}
else if (exchCtx.events().hasServerJoin())
- assignPartitionsStates(true);
+ assignPartitionsStates(null);
else if (exchCtx.events().hasServerLeft())
- assignPartitionsStates(false);
+ assignPartitionsStates(emptySet());
// Validation should happen after resetting owners to avoid false desync reporting.
validatePartitionsState();
@@ -4248,9 +4256,10 @@ private void validatePartitionsState() {
}
/**
- * @param resetOwners True if reset partitions state needed, false otherwise.
+ * @param cacheGroupsToResetOwners Set of cache groups which need to reset partitions state,
+ * null if reset partitions state for all cache groups needed
*/
- private void assignPartitionsStates(boolean resetOwners) {
+ private void assignPartitionsStates(Set cacheGroupsToResetOwners) {
Map> supplyInfoMap = log.isInfoEnabled() ?
new ConcurrentHashMap<>() : null;
@@ -4266,12 +4275,17 @@ private void assignPartitionsStates(boolean resetOwners) {
: cctx.exchange().clientTopology(grpDesc.groupId(), events().discoveryCache());
if (CU.isPersistentCache(grpDesc.config(), cctx.gridConfig().getDataStorageConfiguration())) {
- List list = assignPartitionStates(top, resetOwners);
+ List list;
+
+ if (cacheGroupsToResetOwners == null || cacheGroupsToResetOwners.contains(grpDesc.groupId()))
+ list = assignPartitionStates(top, true);
+ else
+ list = assignPartitionStates(top, false);
if (supplyInfoMap != null && !F.isEmpty(list))
supplyInfoMap.put(grpDesc.cacheOrGroupName(), list);
}
- else if (resetOwners)
+ else if (cacheGroupsToResetOwners == null)
assignPartitionSizes(top);
return null;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IgniteCacheDatabaseSharedManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IgniteCacheDatabaseSharedManager.java
index 346b842585c5b..821d3a3016e93 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IgniteCacheDatabaseSharedManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IgniteCacheDatabaseSharedManager.java
@@ -627,15 +627,18 @@ private void checkWalArchiveSizeConfiguration(DataStorageConfiguration memCfg) t
LT.warn(log, "DataRegionConfiguration.maxWalArchiveSize instead DataRegionConfiguration.walHistorySize " +
"would be used for removing old archive wal files");
else if (memCfg.getMaxWalArchiveSize() == DFLT_WAL_ARCHIVE_MAX_SIZE)
- LT.warn(log, "walHistorySize was deprecated. maxWalArchiveSize should be used instead");
+ LT.warn(log, "walHistorySize was deprecated and does not have any effect anymore. " +
+ "maxWalArchiveSize should be used instead");
else
throw new IgniteCheckedException("Should be used only one of wal history size or max wal archive size." +
"(use DataRegionConfiguration.maxWalArchiveSize because DataRegionConfiguration.walHistorySize was deprecated)"
);
- if (memCfg.getMaxWalArchiveSize() < memCfg.getWalSegmentSize())
+ if (memCfg.getMaxWalArchiveSize() != DataStorageConfiguration.UNLIMITED_WAL_ARCHIVE
+ && memCfg.getMaxWalArchiveSize() < memCfg.getWalSegmentSize())
throw new IgniteCheckedException(
- "DataRegionConfiguration.maxWalArchiveSize should be greater than DataRegionConfiguration.walSegmentSize"
+ "DataRegionConfiguration.maxWalArchiveSize should be greater than DataRegionConfiguration.walSegmentSize " +
+ "or must be equal to " + DataStorageConfiguration.UNLIMITED_WAL_ARCHIVE + "."
);
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/checkpoint/CheckpointHistory.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/checkpoint/CheckpointHistory.java
index 11125792f0c1b..869f0dac2368b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/checkpoint/CheckpointHistory.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/checkpoint/CheckpointHistory.java
@@ -68,8 +68,8 @@ public class CheckpointHistory {
/** The maximal number of checkpoints hold in memory. */
private final int maxCpHistMemSize;
- /** If WalHistorySize was setted by user will use old way for removing checkpoints. */
- private final boolean isWalHistorySizeParameterEnabled;
+ /** Should WAL be truncated */
+ private final boolean isWalTruncationEnabled;
/** Map stores the earliest checkpoint for each partition from particular group. */
private final Map earliestCp = new ConcurrentHashMap<>();
@@ -80,9 +80,6 @@ public class CheckpointHistory {
/** Checking that checkpoint is applicable or not for given cache group. */
private final IgniteThrowableBiPredicate*Checkpoint timestamp*/Long, /*Group id*/Integer> checkpointInapplicable;
- /** It is available or not to truncate WAL on checkpoint finish. */
- private final boolean truncateWalOnCpFinish;
-
/** It is available or not to reserve checkpoint(deletion protection). */
private final boolean reservationDisabled;
@@ -103,15 +100,9 @@ public class CheckpointHistory {
this.wal = wal;
this.checkpointInapplicable = inapplicable;
- maxCpHistMemSize = Math.min(dsCfg.getWalHistorySize(),
- IgniteSystemProperties.getInteger(IGNITE_PDS_MAX_CHECKPOINT_MEMORY_HISTORY_SIZE,
- DFLT_PDS_MAX_CHECKPOINT_MEMORY_HISTORY_SIZE));
-
- isWalHistorySizeParameterEnabled = dsCfg.isWalHistorySizeParameterUsed();
+ isWalTruncationEnabled = dsCfg.getMaxWalArchiveSize() != DataStorageConfiguration.UNLIMITED_WAL_ARCHIVE;
- truncateWalOnCpFinish = dsCfg.isWalHistorySizeParameterUsed()
- ? dsCfg.getWalHistorySize() != Integer.MAX_VALUE
- : dsCfg.getMaxWalArchiveSize() != Long.MAX_VALUE;
+ maxCpHistMemSize = IgniteSystemProperties.getInteger(IGNITE_PDS_MAX_CHECKPOINT_MEMORY_HISTORY_SIZE, DFLT_PDS_MAX_CHECKPOINT_MEMORY_HISTORY_SIZE);
reservationDisabled = dsCfg.getWalMode() == WALMode.NONE;
}
@@ -317,7 +308,7 @@ private void addPartitionToEarliestCheckpoints(GroupPartitionId grpPartKey, Chec
* @return {@code true} if there is space for next checkpoint.
*/
public boolean hasSpace() {
- return histMap.size() + 1 <= maxCpHistMemSize;
+ return isWalTruncationEnabled || histMap.size() + 1 <= maxCpHistMemSize;
}
/**
@@ -334,30 +325,69 @@ public List onWalTruncated(WALPointer highBound) {
if (highBound.compareTo(cpPnt) <= 0)
break;
- if (wal.reserved(cpEntry.checkpointMark())) {
- U.warn(log, "Could not clear historyMap due to WAL reservation on cp: " + cpEntry +
- ", history map size is " + histMap.size());
-
+ if (!removeCheckpoint(cpEntry))
break;
- }
- synchronized (earliestCp) {
- CheckpointEntry deletedCpEntry = histMap.remove(cpEntry.timestamp());
+ removed.add(cpEntry);
+ }
- CheckpointEntry oldestCpInHistory = firstCheckpoint();
+ return removed;
+ }
- for (Map.Entry grpPartPerCp : earliestCp.entrySet()) {
- if (grpPartPerCp.getValue() == deletedCpEntry)
- grpPartPerCp.setValue(oldestCpInHistory);
- }
- }
+ /**
+ * Removes checkpoints from history.
+ *
+ * @return List of checkpoint entries removed from history.
+ */
+ public List removeCheckpoints(int countToRemove) {
+ if (countToRemove == 0)
+ return Collections.emptyList();
- removed.add(cpEntry);
+ List removed = new ArrayList<>();
+
+ for (Iterator> iterator = histMap.entrySet().iterator();
+ iterator.hasNext() && removed.size() < countToRemove; ) {
+ Map.Entry entry = iterator.next();
+
+ CheckpointEntry checkpoint = entry.getValue();
+
+ if (!removeCheckpoint(checkpoint))
+ break;
+
+ removed.add(checkpoint);
}
return removed;
}
+ /**
+ * Remove checkpoint from history
+ *
+ * @param checkpoint Checkpoint to be removed
+ * @return Whether checkpoint was removed from history
+ */
+ private boolean removeCheckpoint(CheckpointEntry checkpoint) {
+ if (wal.reserved(checkpoint.checkpointMark())) {
+ U.warn(log, "Could not clear historyMap due to WAL reservation on cp: " + checkpoint +
+ ", history map size is " + histMap.size());
+
+ return false;
+ }
+
+ synchronized (earliestCp) {
+ CheckpointEntry deletedCpEntry = histMap.remove(checkpoint.timestamp());
+
+ CheckpointEntry oldestCpInHistory = firstCheckpoint();
+
+ for (Map.Entry grpPartPerCp : earliestCp.entrySet()) {
+ if (grpPartPerCp.getValue() == deletedCpEntry)
+ grpPartPerCp.setValue(oldestCpInHistory);
+ }
+ }
+
+ return true;
+ }
+
/**
* Logs and clears checkpoint history after checkpoint finish.
*
@@ -366,21 +396,20 @@ public List onWalTruncated(WALPointer highBound) {
public List onCheckpointFinished(Checkpoint chp) {
chp.walSegsCoveredRange(calculateWalSegmentsCovered());
- WALPointer checkpointMarkUntilDel = isWalHistorySizeParameterEnabled //check for compatibility mode.
- ? checkpointMarkUntilDeleteByMemorySize()
- : newerPointer(checkpointMarkUntilDeleteByMemorySize(), checkpointMarkUntilDeleteByArchiveSize());
+ int removeCount = isWalTruncationEnabled
+ ? checkpointCountUntilDeleteByArchiveSize()
+ : (histMap.size() - maxCpHistMemSize);
- if (checkpointMarkUntilDel == null)
+ if (removeCount <= 0)
return Collections.emptyList();
- List deletedCheckpoints = onWalTruncated(checkpointMarkUntilDel);
+ List deletedCheckpoints = removeCheckpoints(removeCount);
- int deleted = 0;
+ if (isWalTruncationEnabled) {
+ int deleted = wal.truncate(firstCheckpointPointer());
- if (truncateWalOnCpFinish)
- deleted += wal.truncate(null, firstCheckpointPointer());
-
- chp.walFilesDeleted(deleted);
+ chp.walFilesDeleted(deleted);
+ }
return deletedCheckpoints;
}
@@ -420,28 +449,32 @@ private WALPointer checkpointMarkUntilDeleteByMemorySize() {
}
/**
- * Calculate mark until delete by maximum allowed archive size.
+ * Calculate count of checkpoints to delete by maximum allowed archive size.
*
- * @return Checkpoint mark until which checkpoints can be deleted(not including this pointer).
+ * @return Checkpoint count to be deleted.
*/
- @Nullable private WALPointer checkpointMarkUntilDeleteByArchiveSize() {
+ private int checkpointCountUntilDeleteByArchiveSize() {
long absFileIdxToDel = wal.maxArchivedSegmentToDelete();
if (absFileIdxToDel < 0)
- return null;
+ return 0;
long fileUntilDel = absFileIdxToDel + 1;
long checkpointFileIdx = absFileIdx(lastCheckpoint());
+ int countToRemove = 0;
+
for (CheckpointEntry cpEntry : histMap.values()) {
long currFileIdx = absFileIdx(cpEntry);
if (checkpointFileIdx <= currFileIdx || fileUntilDel <= currFileIdx)
- return cpEntry.checkpointMark();
+ return countToRemove;
+
+ countToRemove++;
}
- return lastCheckpoint().checkpointMark();
+ return histMap.size() - 1;
}
/**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/CachePartitionDefragmentationManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/CachePartitionDefragmentationManager.java
index 75b3458f48913..48616b63f6bca 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/CachePartitionDefragmentationManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/CachePartitionDefragmentationManager.java
@@ -19,12 +19,10 @@
import java.io.File;
import java.nio.file.Path;
-import java.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
@@ -46,6 +44,7 @@
import org.apache.ignite.internal.processors.cache.CacheGroupContext;
import org.apache.ignite.internal.processors.cache.CacheType;
import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
+import org.apache.ignite.internal.processors.cache.IgniteCacheOffheapManager;
import org.apache.ignite.internal.processors.cache.IgniteCacheOffheapManager.CacheDataStore;
import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
import org.apache.ignite.internal.processors.cache.persistence.CheckpointState;
@@ -93,6 +92,7 @@
import static org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager.DEFRAGMENTATION_MAPPING_REGION_NAME;
import static org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager.DEFRAGMENTATION_PART_REGION_NAME;
import static org.apache.ignite.internal.processors.cache.persistence.defragmentation.DefragmentationFileUtils.batchRenameDefragmentedCacheGroupPartitions;
+import static org.apache.ignite.internal.processors.cache.persistence.defragmentation.DefragmentationFileUtils.defragmentedIndexFile;
import static org.apache.ignite.internal.processors.cache.persistence.defragmentation.DefragmentationFileUtils.defragmentedIndexTmpFile;
import static org.apache.ignite.internal.processors.cache.persistence.defragmentation.DefragmentationFileUtils.defragmentedPartFile;
import static org.apache.ignite.internal.processors.cache.persistence.defragmentation.DefragmentationFileUtils.defragmentedPartMappingFile;
@@ -153,7 +153,7 @@ public class CachePartitionDefragmentationManager {
private final AtomicBoolean cancel = new AtomicBoolean();
/** */
- private final DefragmentationStatus status = new DefragmentationStatus();
+ private final Status status = new Status();
/** */
private final GridFutureAdapter> completionFut = new GridFutureAdapter<>();
@@ -220,7 +220,30 @@ public void beforeDefragmentation() throws IgniteCheckedException {
/** */
public void executeDefragmentation() throws IgniteCheckedException {
- status.onStart(cacheGrpCtxsForDefragmentation);
+ Map> oldStores = new HashMap<>();
+
+ for (CacheGroupContext oldGrpCtx : cacheGrpCtxsForDefragmentation) {
+ int grpId = oldGrpCtx.groupId();
+
+ final IgniteCacheOffheapManager offheap = oldGrpCtx.offheap();
+
+ List oldCacheDataStores = stream(offheap.cacheDataStores().spliterator(), false)
+ .filter(store -> {
+ try {
+ return filePageStoreMgr.exists(grpId, store.partId());
+ }
+ catch (IgniteCheckedException e) {
+ throw new IgniteException(e);
+ }
+ })
+ .collect(Collectors.toList());
+
+ oldStores.put(grpId, oldCacheDataStores);
+ }
+
+ int partitionCount = oldStores.values().stream().mapToInt(List::size).sum();
+
+ status.onStart(cacheGrpCtxsForDefragmentation, partitionCount);
try {
// Now the actual process starts.
@@ -234,8 +257,10 @@ public void executeDefragmentation() throws IgniteCheckedException {
File workDir = filePageStoreMgr.cacheWorkDir(oldGrpCtx.sharedGroup(), oldGrpCtx.cacheOrGroupName());
+ List oldCacheDataStores = oldStores.get(grpId);
+
if (skipAlreadyDefragmentedCacheGroup(workDir, grpId, log)) {
- status.onCacheGroupSkipped(oldGrpCtx);
+ status.onCacheGroupSkipped(oldGrpCtx, oldCacheDataStores.size());
continue;
}
@@ -243,17 +268,6 @@ public void executeDefragmentation() throws IgniteCheckedException {
try {
GridCacheOffheapManager offheap = (GridCacheOffheapManager)oldGrpCtx.offheap();
- List oldCacheDataStores = stream(offheap.cacheDataStores().spliterator(), false)
- .filter(store -> {
- try {
- return filePageStoreMgr.exists(grpId, store.partId());
- }
- catch (IgniteCheckedException e) {
- throw new IgniteException(e);
- }
- })
- .collect(Collectors.toList());
-
status.onCacheGroupStart(oldGrpCtx, oldCacheDataStores.size());
if (workDir == null || oldCacheDataStores.isEmpty()) {
@@ -386,9 +400,9 @@ public void executeDefragmentation() throws IgniteCheckedException {
partCtx.partPageMemory
);
- partCtx.createNewCacheDataStore(offheap);
+ partCtx.createNewCacheDataStore(offheap);
- copyPartitionData(partCtx, treeIter);
+ copyPartitionData(partCtx, treeIter);
DefragmentationPageReadWriteManager pageMgr = (DefragmentationPageReadWriteManager)partCtx.partPageMemory.pageManager();
@@ -450,7 +464,21 @@ public void executeDefragmentation() throws IgniteCheckedException {
.futureFor(CheckpointState.FINISHED);
}
+ PageStore oldIdxPageStore = filePageStoreMgr.getStore(grpId, INDEX_PARTITION);
+
idxDfrgFut = idxDfrgFut.chain(fut -> {
+ if (log.isDebugEnabled()) {
+ log.debug(S.toString(
+ "Index partition defragmented",
+ "grpId", grpId, false,
+ "oldPages", oldIdxPageStore.pages(), false,
+ "newPages", idxAllocationTracker.get() + 1, false,
+ "pageSize", pageSize, false,
+ "partFile", defragmentedIndexFile(workDir).getName(), false,
+ "workDir", workDir, false
+ ));
+ }
+
oldPageMem.invalidate(grpId, PageIdAllocator.INDEX_PARTITION);
PageMemoryEx partPageMem = (PageMemoryEx)partDataRegion.pageMemory();
@@ -476,8 +504,6 @@ public void executeDefragmentation() throws IgniteCheckedException {
return null;
});
- PageStore oldIdxPageStore = filePageStoreMgr.getStore(grpId, INDEX_PARTITION);
-
status.onIndexDefragmented(
oldGrpCtx,
oldIdxPageStore.size(),
@@ -596,8 +622,8 @@ private void checkCancellation() throws DefragmentationCancelledException {
}
/** */
- public String status() {
- return status.toString();
+ public Status status() {
+ return status;
}
/**
@@ -614,6 +640,9 @@ private void copyPartitionData(
CacheDataTree tree = partCtx.oldCacheDataStore.tree();
CacheDataTree newTree = partCtx.newCacheDataStore.tree();
+
+ newTree.enableSequentialWriteMode();
+
PendingEntriesTree newPendingTree = partCtx.newCacheDataStore.pendingTree();
AbstractFreeList freeList = partCtx.newCacheDataStore.getCacheStoreFreeList();
@@ -963,58 +992,110 @@ private static class DefragmentationCancelledException extends RuntimeException
private static final long serialVersionUID = 0L;
}
- /** */
- private class DefragmentationStatus {
- /** */
+ /** Defragmentation status. */
+ class Status {
+ /** Defragmentation start timestamp. */
private long startTs;
- /** */
+ /** Defragmentation finish timestamp. */
private long finishTs;
- /** */
- private final Set scheduledGroups = new TreeSet<>();
+ /** Total count of partitions. */
+ private int totalPartitionCount;
- /** */
- private final Map progressGroups
- = new TreeMap<>(comparing(CacheGroupContext::cacheOrGroupName));
+ /** Partitions, that are already defragmented. */
+ private int defragmentedPartitionCount;
- /** */
- private final Map finishedGroups
- = new TreeMap<>(comparing(CacheGroupContext::cacheOrGroupName));
+ /** Cache groups scheduled for defragmentation. */
+ private final Set scheduledGroups;
- /** */
- private final Set skippedGroups = new TreeSet<>();
+ /** Progress for cache group. */
+ private final Map progressGroups;
- /** */
- public synchronized void onStart(Set scheduledGroups) {
+ /** Finished cache groups. */
+ private final Map finishedGroups;
+
+ /** Skipped cache groups. */
+ private final Set skippedGroups;
+
+ /** Constructor. */
+ public Status() {
+ scheduledGroups = new TreeSet<>();
+ progressGroups = new TreeMap<>(comparing(CacheGroupContext::cacheOrGroupName));
+ finishedGroups = new TreeMap<>(comparing(CacheGroupContext::cacheOrGroupName));
+ skippedGroups = new TreeSet<>();
+ }
+
+ /** Copy constructor. */
+ public Status(
+ long startTs,
+ long finishTs,
+ Set scheduledGroups,
+ Map progressGroups,
+ Map finishedGroups,
+ Set skippedGroups
+ ) {
+ this.startTs = startTs;
+ this.finishTs = finishTs;
+ this.scheduledGroups = scheduledGroups;
+ this.progressGroups = progressGroups;
+ this.finishedGroups = finishedGroups;
+ this.skippedGroups = skippedGroups;
+ }
+
+ /**
+ * Mark the start of the defragmentation.
+ * @param scheduledGroups Groups scheduled for defragmentation.
+ * @param partitions Total partition count.
+ */
+ public synchronized void onStart(Set scheduledGroups, int partitions) {
startTs = System.currentTimeMillis();
+ totalPartitionCount = partitions;
- for (CacheGroupContext grp : scheduledGroups) {
+ for (CacheGroupContext grp : scheduledGroups)
this.scheduledGroups.add(grp.cacheOrGroupName());
- }
log.info("Defragmentation started.");
}
- /** */
- public synchronized void onCacheGroupStart(CacheGroupContext grpCtx, int parts) {
+ /**
+ * Mark the start of the cache group defragmentation.
+ * @param grpCtx Cache group context.
+ * @param parts Partition count.
+ */
+ private synchronized void onCacheGroupStart(CacheGroupContext grpCtx, int parts) {
scheduledGroups.remove(grpCtx.cacheOrGroupName());
progressGroups.put(grpCtx, new DefragmentationCacheGroupProgress(parts));
}
- /** */
- public synchronized void onPartitionDefragmented(CacheGroupContext grpCtx, long oldSize, long newSize) {
+ /**
+ * Mark the end of the partition defragmentation.
+ * @param grpCtx Cache group context.
+ * @param oldSize Old size.
+ * @param newSize New size;
+ */
+ private synchronized void onPartitionDefragmented(CacheGroupContext grpCtx, long oldSize, long newSize) {
progressGroups.get(grpCtx).onPartitionDefragmented(oldSize, newSize);
+
+ defragmentedPartitionCount++;
}
- /** */
- public synchronized void onIndexDefragmented(CacheGroupContext grpCtx, long oldSize, long newSize) {
+ /**
+ * Mark the end of the index partition defragmentation.
+ * @param grpCtx Cache group context.
+ * @param oldSize Old size.
+ * @param newSize New size;
+ */
+ private synchronized void onIndexDefragmented(CacheGroupContext grpCtx, long oldSize, long newSize) {
progressGroups.get(grpCtx).onIndexDefragmented(oldSize, newSize);
}
- /** */
- public synchronized void onCacheGroupFinish(CacheGroupContext grpCtx) {
+ /**
+ * Mark the end of the cache group defragmentation.
+ * @param grpCtx Cache group context.
+ */
+ private synchronized void onCacheGroupFinish(CacheGroupContext grpCtx) {
DefragmentationCacheGroupProgress progress = progressGroups.remove(grpCtx);
progress.onFinish();
@@ -1022,15 +1103,20 @@ public synchronized void onCacheGroupFinish(CacheGroupContext grpCtx) {
finishedGroups.put(grpCtx, progress);
}
- /** */
- public synchronized void onCacheGroupSkipped(CacheGroupContext grpCtx) {
+ /**
+ * Mark that cache group defragmentation was skipped.
+ * @param grpCtx Cache group context.
+ */
+ private synchronized void onCacheGroupSkipped(CacheGroupContext grpCtx, int partitions) {
scheduledGroups.remove(grpCtx.cacheOrGroupName());
skippedGroups.add(grpCtx.cacheOrGroupName());
+
+ defragmentedPartitionCount += partitions;
}
- /** */
- public synchronized void onFinish() {
+ /** Mark the end of the defragmentation. */
+ private synchronized void onFinish() {
finishTs = System.currentTimeMillis();
progressGroups.clear();
@@ -1040,67 +1126,80 @@ public synchronized void onFinish() {
log.info("Defragmentation process completed. Time: " + (finishTs - startTs) * 1e-3 + "s.");
}
- /** {@inheritDoc} */
- @Override public synchronized String toString() {
- StringBuilder sb = new StringBuilder();
-
- if (!finishedGroups.isEmpty()) {
- sb.append("Defragmentation is completed for cache groups:\n");
-
- for (Map.Entry entry : finishedGroups.entrySet()) {
- sb.append(" ").append(entry.getKey().cacheOrGroupName()).append(" - ");
-
- sb.append(entry.getValue().toString()).append('\n');
- }
- }
+ /** Copy object. */
+ private synchronized Status copy() {
+ return new Status(
+ startTs,
+ finishTs,
+ new HashSet<>(scheduledGroups),
+ new HashMap<>(progressGroups),
+ new HashMap<>(finishedGroups),
+ new HashSet<>(skippedGroups)
+ );
+ }
- if (!progressGroups.isEmpty()) {
- sb.append("Defragmentation is in progress for cache groups:\n");
+ /** */
+ public long getStartTs() {
+ return startTs;
+ }
- for (Map.Entry entry : progressGroups.entrySet()) {
- sb.append(" ").append(entry.getKey().cacheOrGroupName()).append(" - ");
+ /** */
+ public long getFinishTs() {
+ return finishTs;
+ }
- sb.append(entry.getValue().toString()).append('\n');
- }
- }
+ /** */
+ public Set getScheduledGroups() {
+ return scheduledGroups;
+ }
- if (!skippedGroups.isEmpty())
- sb.append("Skipped cache groups: ").append(String.join(", ", skippedGroups)).append('\n');
+ /** */
+ public Map getProgressGroups() {
+ return progressGroups;
+ }
- if (!scheduledGroups.isEmpty())
- sb.append("Awaiting defragmentation: ").append(String.join(", ", scheduledGroups)).append('\n');
+ /** */
+ public Map getFinishedGroups() {
+ return finishedGroups;
+ }
- return sb.toString();
+ /** */
+ public Set getSkippedGroups() {
+ return skippedGroups;
}
- }
- /** */
- private static class DefragmentationCacheGroupProgress {
/** */
- private static final DecimalFormat MB_FORMAT = new DecimalFormat(
- "#.##",
- DecimalFormatSymbols.getInstance(Locale.US)
- );
+ public int getTotalPartitionCount() {
+ return totalPartitionCount;
+ }
/** */
+ public int getDefragmentedPartitionCount() {
+ return defragmentedPartitionCount;
+ }
+ }
+
+ /** Cache group defragmentation progress. */
+ static class DefragmentationCacheGroupProgress {
+ /** Partition count. */
private final int partsTotal;
- /** */
+ /** Defragmented partitions. */
private int partsCompleted;
- /** */
+ /** Old cache group size. */
private long oldSize;
- /** */
+ /** New cache group size. */
private long newSize;
- /** */
+ /** Start timestamp. */
private final long startTs;
- /** */
+ /** Finish timestamp. */
private long finishTs;
- /** */
+ /** Constructor. */
public DefragmentationCacheGroupProgress(int parts) {
partsTotal = parts;
@@ -1128,43 +1227,38 @@ public void onIndexDefragmented(long oldSize, long newSize) {
}
/** */
- public void onFinish() {
- finishTs = System.currentTimeMillis();
+ public long getOldSize() {
+ return oldSize;
}
- /** {@inheritDoc} */
- @Override public String toString() {
- StringBuilder sb = new StringBuilder();
-
- if (finishTs == 0) {
- sb.append("partitions processed/all: ").append(partsCompleted).append("/").append(partsTotal);
-
- sb.append(", time elapsed: ");
-
- appendDuration(sb, System.currentTimeMillis());
- }
- else {
- double mb = 1024 * 1024;
-
- sb.append("size before/after: ").append(MB_FORMAT.format(oldSize / mb)).append("MB/");
- sb.append(MB_FORMAT.format(newSize / mb)).append("MB");
-
- sb.append(", time took: ");
+ /** */
+ public long getNewSize() {
+ return newSize;
+ }
- appendDuration(sb, finishTs);
- }
+ /** */
+ public long getStartTs() {
+ return startTs;
+ }
- return sb.toString();
+ /** */
+ public long getFinishTs() {
+ return finishTs;
}
/** */
- private void appendDuration(StringBuilder sb, long end) {
- long duration = Math.round((end - startTs) * 1e-3);
+ public int getPartsTotal() {
+ return partsTotal;
+ }
- long mins = duration / 60;
- long secs = duration % 60;
+ /** */
+ public int getPartsCompleted() {
+ return partsCompleted;
+ }
- sb.append(mins).append(" mins ").append(secs).append(" secs");
+ /** */
+ public void onFinish() {
+ finishTs = System.currentTimeMillis();
}
}
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/DefragmentationMXBeanImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/DefragmentationMXBeanImpl.java
new file mode 100644
index 0000000000000..1e3becba51319
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/DefragmentationMXBeanImpl.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.defragmentation;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.mxbean.DefragmentationMXBean;
+
+/**
+ * Defragmentation MX bean implementation.
+ */
+public class DefragmentationMXBeanImpl implements DefragmentationMXBean {
+ /** Defragmentation manager. */
+ private final IgniteDefragmentation defragmentation;
+
+ public DefragmentationMXBeanImpl(GridKernalContext ctx) {
+ this.defragmentation = ctx.defragmentation();
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean schedule(String cacheNames) {
+ final List caches = Arrays.stream(cacheNames.split(","))
+ .filter(s -> !s.isEmpty())
+ .collect(Collectors.toList());
+
+ try {
+ defragmentation.schedule(caches);
+
+ return true;
+ }
+ catch (IgniteCheckedException e) {
+ return false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean cancel() {
+ try {
+ defragmentation.cancel();
+
+ return true;
+ }
+ catch (IgniteCheckedException e) {
+ return false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean inProgress() {
+ return defragmentation.inProgress();
+ }
+
+ /** {@inheritDoc} */
+ @Override public int processedPartitions() {
+ return defragmentation.processedPartitions();
+ }
+
+ /** {@inheritDoc} */
+ @Override public int totalPartitions() {
+ return defragmentation.totalPartitions();
+ }
+
+ /** {@inheritDoc} */
+ @Override public long startTime() {
+ return defragmentation.startTime();
+ }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/IgniteDefragmentation.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/IgniteDefragmentation.java
new file mode 100644
index 0000000000000..a5dc811f90880
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/IgniteDefragmentation.java
@@ -0,0 +1,341 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.defragmentation;
+
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import org.apache.ignite.IgniteCheckedException;
+
+/**
+ * Defragmentation operation service.
+ */
+public interface IgniteDefragmentation {
+ /**
+ * Schedule defragmentaton on next start of the node.
+ *
+ * @param cacheNames Names of caches to run defragmentation on.
+ * @return Result of the scheduling.
+ * @throws IgniteCheckedException If failed.
+ */
+ ScheduleResult schedule(List cacheNames) throws IgniteCheckedException;
+
+ /**
+ * Cancel scheduled or ongoing defragmentation.
+ * @return Result of the cancellation.
+ * @throws IgniteCheckedException If failed.
+ */
+ CancelResult cancel() throws IgniteCheckedException;
+
+ /**
+ * Get the status of the ongoing defragmentation.
+ * @return Defragmentation status.
+ * @throws IgniteCheckedException If failed.
+ */
+ DefragmentationStatus status() throws IgniteCheckedException;
+
+ /**
+ * @return {@code true} if there is an ongoing defragmentation.
+ */
+ boolean inProgress();
+
+ /**
+ * @return Number of processed partitions, or 0 if there is no ongoing defragmentation.
+ */
+ int processedPartitions();
+
+ /**
+ * @return Number of total partitions, or 0 if there is no ongoing defragmentation.
+ */
+ int totalPartitions();
+
+ /**
+ * @return Timestamp of the beginning of the ongoing defragmentation or 0 if there is none.
+ */
+ long startTime();
+
+ /** Result of the scheduling. */
+ public enum ScheduleResult {
+ /**
+ * Successfully scheduled.
+ */
+ SUCCESS,
+
+ /**
+ * Successfuly scheduled, superseding previously scheduled defragmentation.
+ */
+ SUCCESS_SUPERSEDED_PREVIOUS
+ }
+
+ /** Result of the cancellation. */
+ public enum CancelResult {
+ /**
+ * Cancelled scheduled defragmentation.
+ */
+ CANCELLED_SCHEDULED,
+
+ /**
+ * Nothing to cancel, no ongoing defragmentation.
+ */
+ SCHEDULED_NOT_FOUND,
+
+ /**
+ * Cancelled ongoing defragmentation.
+ */
+ CANCELLED,
+
+ /**
+ * Defragmentation is already completed or cancelled.
+ */
+ COMPLETED_OR_CANCELLED
+ }
+
+ /** */
+ public static class DefragmentationStatus {
+ /** */
+ private final Map completedCaches;
+
+ /** */
+ private final Map inProgressCaches;
+
+ /** */
+ private final Set awaitingCaches;
+
+ /** */
+ private final Set skippedCaches;
+
+ /** */
+ private final int totalPartitions;
+
+ /** */
+ private final int processedPartitions;
+
+ /** */
+ private final long startTs;
+
+ /** */
+ private final long totalElapsedTime;
+
+ public DefragmentationStatus(
+ Map completedCaches,
+ Map inProgressCaches,
+ Set awaitingCaches,
+ Set skippedCaches,
+ int totalPartitions,
+ int processedPartitions,
+ long startTs,
+ long totalElapsedTime
+ ) {
+ this.completedCaches = completedCaches;
+ this.inProgressCaches = inProgressCaches;
+ this.awaitingCaches = awaitingCaches;
+ this.skippedCaches = skippedCaches;
+ this.totalPartitions = totalPartitions;
+ this.processedPartitions = processedPartitions;
+ this.startTs = startTs;
+ this.totalElapsedTime = totalElapsedTime;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ if (!completedCaches.isEmpty()) {
+ sb.append("Defragmentation is completed for cache groups:\n");
+
+ for (Map.Entry entry : completedCaches.entrySet()) {
+ sb.append(" ").append(entry.getKey()).append(" - ");
+
+ sb.append(entry.getValue().toString()).append('\n');
+ }
+ }
+
+ if (!inProgressCaches.isEmpty()) {
+ sb.append("Defragmentation is in progress for cache groups:\n");
+
+ for (Map.Entry entry : inProgressCaches.entrySet()) {
+ sb.append(" ").append(entry.getKey()).append(" - ");
+
+ sb.append(entry.getValue().toString()).append('\n');
+ }
+ }
+
+ if (!skippedCaches.isEmpty())
+ sb.append("Skipped cache groups: ").append(String.join(", ", skippedCaches)).append('\n');
+
+ if (!awaitingCaches.isEmpty())
+ sb.append("Awaiting defragmentation: ").append(String.join(", ", awaitingCaches)).append('\n');
+
+ return sb.toString();
+ }
+
+ /** */
+ public Map getCompletedCaches() {
+ return completedCaches;
+ }
+
+ /** */
+ public Map getInProgressCaches() {
+ return inProgressCaches;
+ }
+
+ /** */
+ public Set getAwaitingCaches() {
+ return awaitingCaches;
+ }
+
+ /** */
+ public Set getSkippedCaches() {
+ return skippedCaches;
+ }
+
+ /** */
+ public long getTotalElapsedTime() {
+ return totalElapsedTime;
+ }
+
+ /** */
+ public int getTotalPartitions() {
+ return totalPartitions;
+ }
+
+ /** */
+ public int getProcessedPartitions() {
+ return processedPartitions;
+ }
+
+ /** */
+ public long getStartTs() {
+ return startTs;
+ }
+ }
+
+ /** */
+ abstract class DefragmentationInfo {
+ /** */
+ long elapsedTime;
+
+ public DefragmentationInfo(long elapsedTime) {
+ this.elapsedTime = elapsedTime;
+ }
+
+ /** */
+ void appendDuration(StringBuilder sb, long elapsedTime) {
+ long duration = Math.round(elapsedTime * 1e-3);
+
+ long mins = duration / 60;
+ long secs = duration % 60;
+
+ sb.append(mins).append(" mins ").append(secs).append(" secs");
+ }
+
+ /** */
+ public long getElapsedTime() {
+ return elapsedTime;
+ }
+ }
+
+ /** */
+ public static class CompletedDefragmentationInfo extends DefragmentationInfo {
+ /** */
+ private static final DecimalFormat MB_FORMAT = new DecimalFormat(
+ "#.##",
+ DecimalFormatSymbols.getInstance(Locale.US)
+ );
+
+ /** */
+ long sizeBefore;
+
+ /** */
+ long sizeAfter;
+
+ public CompletedDefragmentationInfo(long elapsedTime, long sizeBefore, long sizeAfter) {
+ super(elapsedTime);
+ this.sizeBefore = sizeBefore;
+ this.sizeAfter = sizeAfter;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ double mb = 1024 * 1024;
+
+ sb.append("size before/after: ").append(MB_FORMAT.format(sizeBefore / mb)).append("MB/");
+ sb.append(MB_FORMAT.format(sizeAfter / mb)).append("MB");
+
+ sb.append(", time took: ");
+
+ appendDuration(sb, elapsedTime);
+
+ return sb.toString();
+ }
+
+ /** */
+ public long getSizeBefore() {
+ return sizeBefore;
+ }
+
+ /** */
+ public long getSizeAfter() {
+ return sizeAfter;
+ }
+ }
+
+ /** */
+ public static class InProgressDefragmentationInfo extends DefragmentationInfo {
+ /** */
+ int processedPartitions;
+
+ /** */
+ int totalPartitions;
+
+ public InProgressDefragmentationInfo(long elapsedTime, int processedPartitions, int totalPartitions) {
+ super(elapsedTime);
+ this.processedPartitions = processedPartitions;
+ this.totalPartitions = totalPartitions;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("partitions processed/all: ").append(processedPartitions).append("/").append(totalPartitions);
+
+ sb.append(", time elapsed: ");
+
+ appendDuration(sb, elapsedTime);
+
+ return sb.toString();
+ }
+
+ /** */
+ public int getProcessedPartitions() {
+ return processedPartitions;
+ }
+
+ /** */
+ public int getTotalPartitions() {
+ return totalPartitions;
+ }
+ }
+
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/IgniteDefragmentationImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/IgniteDefragmentationImpl.java
new file mode 100644
index 0000000000000..5c443baac411b
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/defragmentation/IgniteDefragmentationImpl.java
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.defragmentation;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager;
+import org.apache.ignite.internal.processors.cache.persistence.IgniteCacheDatabaseSharedManager;
+import org.apache.ignite.internal.processors.cache.persistence.defragmentation.CachePartitionDefragmentationManager.Status;
+import org.apache.ignite.maintenance.MaintenanceAction;
+import org.apache.ignite.maintenance.MaintenanceRegistry;
+import org.apache.ignite.maintenance.MaintenanceTask;
+
+import static org.apache.ignite.internal.processors.cache.persistence.defragmentation.CachePartitionDefragmentationManager.DEFRAGMENTATION_MNTC_TASK_NAME;
+import static org.apache.ignite.internal.processors.cache.persistence.defragmentation.maintenance.DefragmentationParameters.toStore;
+
+/**
+ * Defragmentation operation service implementation.
+ */
+public class IgniteDefragmentationImpl implements IgniteDefragmentation {
+ /** Kernal context. */
+ private final GridKernalContext ctx;
+
+ public IgniteDefragmentationImpl(GridKernalContext ctx) {
+ this.ctx = ctx;
+ }
+
+ /** {@inheritDoc} */
+ @Override public ScheduleResult schedule(List cacheNames) throws IgniteCheckedException {
+ final MaintenanceRegistry maintenanceRegistry = ctx.maintenanceRegistry();
+
+ MaintenanceTask oldTask;
+
+ try {
+ oldTask = maintenanceRegistry.registerMaintenanceTask(toStore(cacheNames != null ? cacheNames : Collections.emptyList()));
+ }
+ catch (IgniteCheckedException e) {
+ throw new IgniteCheckedException("Scheduling failed: " + e.getMessage());
+ }
+
+ return oldTask != null ? ScheduleResult.SUCCESS_SUPERSEDED_PREVIOUS : ScheduleResult.SUCCESS;
+ }
+
+ /** {@inheritDoc} */
+ @Override public CancelResult cancel() throws IgniteCheckedException {
+ final MaintenanceRegistry maintenanceRegistry = ctx.maintenanceRegistry();
+
+ if (!maintenanceRegistry.isMaintenanceMode()) {
+ boolean deleted = maintenanceRegistry.unregisterMaintenanceTask(DEFRAGMENTATION_MNTC_TASK_NAME);
+
+ return deleted ? CancelResult.CANCELLED_SCHEDULED : CancelResult.SCHEDULED_NOT_FOUND;
+ }
+ else {
+ List> actions;
+
+ try {
+ actions = maintenanceRegistry.actionsForMaintenanceTask(DEFRAGMENTATION_MNTC_TASK_NAME);
+ }
+ catch (IgniteException e) {
+ return CancelResult.COMPLETED_OR_CANCELLED;
+ }
+
+ Optional> stopAct = actions.stream().filter(a -> "stop".equals(a.name())).findAny();
+
+ assert stopAct.isPresent();
+
+ try {
+ Object res = stopAct.get().execute();
+
+ assert res instanceof Boolean;
+
+ boolean cancelled = (Boolean)res;
+
+ return cancelled ? CancelResult.CANCELLED : CancelResult.COMPLETED_OR_CANCELLED;
+ }
+ catch (Exception e) {
+ throw new IgniteCheckedException("Exception occurred: " + e.getMessage(), e);
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public DefragmentationStatus status() throws IgniteCheckedException {
+ final MaintenanceRegistry maintenanceRegistry = ctx.maintenanceRegistry();
+
+ if (!maintenanceRegistry.isMaintenanceMode())
+ throw new IgniteCheckedException("Node is not in maintenance mode.");
+
+ IgniteCacheDatabaseSharedManager dbMgr = ctx.cache().context().database();
+
+ assert dbMgr instanceof GridCacheDatabaseSharedManager;
+
+ CachePartitionDefragmentationManager defrgMgr = ((GridCacheDatabaseSharedManager)dbMgr)
+ .defragmentationManager();
+
+ if (defrgMgr == null)
+ throw new IgniteCheckedException("There's no active defragmentation process on the node.");
+
+ final Status status = defrgMgr.status();
+
+ final long startTs = status.getStartTs();
+ final long finishTs = status.getFinishTs();
+ final long elapsedTime = finishTs != 0 ? finishTs - startTs : System.currentTimeMillis() - startTs;
+
+ Map completedCaches = new HashMap<>();
+ Map progressCaches = new HashMap<>();
+
+ status.getFinishedGroups().forEach((context, progress) -> {
+ final String name = context.cacheOrGroupName();
+
+ final long oldSize = progress.getOldSize();
+ final long newSize = progress.getNewSize();
+ final long cgElapsedTime = progress.getFinishTs() - progress.getStartTs();
+
+ final CompletedDefragmentationInfo info = new CompletedDefragmentationInfo(cgElapsedTime, oldSize, newSize);
+ completedCaches.put(name, info);
+ });
+
+ status.getProgressGroups().forEach((context, progress) -> {
+ final String name = context.cacheOrGroupName();
+
+ final long cgElapsedTime = System.currentTimeMillis() - progress.getStartTs();
+ final int partsTotal = progress.getPartsTotal();
+ final int partsCompleted = progress.getPartsCompleted();
+
+ final InProgressDefragmentationInfo info = new InProgressDefragmentationInfo(cgElapsedTime, partsCompleted, partsTotal);
+ progressCaches.put(name, info);
+ });
+
+ return new DefragmentationStatus(
+ completedCaches,
+ progressCaches,
+ status.getScheduledGroups(),
+ status.getSkippedGroups(),
+ status.getTotalPartitionCount(),
+ status.getDefragmentedPartitionCount(),
+ startTs,
+ elapsedTime
+ );
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean inProgress() {
+ final Status status = getStatus();
+
+ return status != null && status.getFinishTs() == 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int processedPartitions() {
+ final Status status = getStatus();
+
+ if (status == null)
+ return 0;
+
+ return status.getDefragmentedPartitionCount();
+ }
+
+ /** {@inheritDoc} */
+ @Override public int totalPartitions() {
+ final CachePartitionDefragmentationManager.Status status = getStatus();
+
+ if (status == null)
+ return 0;
+
+ return status.getTotalPartitionCount();
+ }
+
+ /** {@inheritDoc} */
+ @Override public long startTime() {
+ final CachePartitionDefragmentationManager.Status status = getStatus();
+
+ if (status == null)
+ return 0;
+
+ return status.getStartTs();
+ }
+
+ /**
+ * Get defragmentation status.
+ * @return Defragmentation status or {@code null} if there is no ongoing defragmentation.
+ */
+ private Status getStatus() {
+ final MaintenanceRegistry maintenanceRegistry = ctx.maintenanceRegistry();
+
+ if (!maintenanceRegistry.isMaintenanceMode())
+ return null;
+
+ IgniteCacheDatabaseSharedManager dbMgr = ctx.cache().context().database();
+
+ assert dbMgr instanceof GridCacheDatabaseSharedManager;
+
+ CachePartitionDefragmentationManager defrgMgr = ((GridCacheDatabaseSharedManager) dbMgr)
+ .defragmentationManager();
+
+ if (defrgMgr == null)
+ return null;
+
+ return defrgMgr.status();
+ }
+
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java
index c50e75dadf142..10e2c6617a278 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java
@@ -63,7 +63,6 @@
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.LongListReuseBag;
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseBag;
import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
-import org.apache.ignite.internal.processors.cache.persistence.tree.util.InsertLast;
import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandlerWrapper;
import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
@@ -154,6 +153,9 @@ public abstract class BPlusTree extends DataStructure implements
/** Failure processor. */
private final FailureProcessor failureProcessor;
+ /** Flag for enabling single-threaded append-only tree creation. */
+ private boolean sequentialWriteOptsEnabled;
+
/** */
private final GridTreePrinter treePrinter = new GridTreePrinter() {
/** */
@@ -884,6 +886,11 @@ public final String getName() {
return name;
}
+ /** Flag for enabling single-threaded append-only tree creation. */
+ public void enableSequentialWriteMode() {
+ sequentialWriteOptsEnabled = true;
+ }
+
/**
* Initialize new tree.
*
@@ -1398,6 +1405,8 @@ public final R findOne(L row, TreeRowClosure c, Object x) throws Ignit
* @throws IgniteCheckedException If failed.
*/
private void doFind(Get g) throws IgniteCheckedException {
+ assert !sequentialWriteOptsEnabled;
+
for (;;) { // Go down with retries.
g.init();
@@ -2054,6 +2063,8 @@ private Result invokeDown(final Invoke x, final long pageId, final long backId,
* @throws IgniteCheckedException If failed.
*/
private T doRemove(L row, boolean needOld) throws IgniteCheckedException {
+ assert !sequentialWriteOptsEnabled;
+
checkDestroyed();
Remove r = new Remove(row, needOld);
@@ -2711,7 +2722,8 @@ private boolean splitPage(
long pageId, long page, long pageAddr, BPlusIO io, long fwdId, long fwdBuf, int idx
) throws IgniteCheckedException {
int cnt = io.getCount(pageAddr);
- int mid = cnt >>> 1;
+
+ int mid = sequentialWriteOptsEnabled ? (int)(cnt * 0.85) : cnt >>> 1;
boolean res = false;
@@ -2767,7 +2779,7 @@ private Result askNeighbor(long pageId, Get g, boolean back) throws IgniteChecke
* @return Result code.
* @throws IgniteCheckedException If failed.
*/
- private Result putDown(final Put p, final long pageId, final long fwdId, final int lvl)
+ private Result putDown(final Put p, final long pageId, final long fwdId, int lvl)
throws IgniteCheckedException {
assert lvl >= 0 : lvl;
@@ -5302,8 +5314,11 @@ private int findInsertionPoint(int lvl, BPlusIO io, long buf, int low, int cn
throws IgniteCheckedException {
assert row != null;
- if (row instanceof InsertLast)
+ if (sequentialWriteOptsEnabled) {
+ assert io.getForward(buf) == 0L;
+
return -cnt - 1;
+ }
int high = cnt - 1;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/AbstractWalRecordsIterator.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/AbstractWalRecordsIterator.java
index 8730c1f700640..5bf7d399fe459 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/AbstractWalRecordsIterator.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/AbstractWalRecordsIterator.java
@@ -401,7 +401,7 @@ protected AbstractReadFileHandle initReadHandle(
SegmentIO fileIO = null;
try {
- fileIO = desc.toIO(ioFactory);
+ fileIO = desc.toReadOnlyIO(ioFactory);
SegmentHeader segmentHeader;
@@ -513,6 +513,6 @@ protected interface AbstractFileDescriptor {
* @return One of implementation of {@link FileIO}.
* @throws IOException if creation of fileIo was not success.
*/
- SegmentIO toIO(FileIOFactory fileIOFactory) throws IOException;
+ SegmentIO toReadOnlyIO(FileIOFactory fileIOFactory) throws IOException;
}
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/FileDescriptor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/FileDescriptor.java
index 2f088d19f6979..f654c3213ce44 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/FileDescriptor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/FileDescriptor.java
@@ -27,6 +27,8 @@
import org.apache.ignite.internal.util.typedef.internal.SB;
import org.jetbrains.annotations.Nullable;
+import static java.nio.file.StandardOpenOption.READ;
+
/**
* WAL file descriptor.
*/
@@ -144,8 +146,8 @@ public String getAbsolutePath() {
}
/** {@inheritDoc} */
- @Override public SegmentIO toIO(FileIOFactory fileIOFactory) throws IOException {
- FileIO fileIO = isCompressed() ? new UnzipFileIO(file()) : fileIOFactory.create(file());
+ @Override public SegmentIO toReadOnlyIO(FileIOFactory fileIOFactory) throws IOException {
+ FileIO fileIO = isCompressed() ? new UnzipFileIO(file()) : fileIOFactory.create(file(), READ);
return new SegmentIO(idx, fileIO);
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/FileWriteAheadLogManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/FileWriteAheadLogManager.java
index ff64f5b034156..de277533cb076 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/FileWriteAheadLogManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/FileWriteAheadLogManager.java
@@ -290,8 +290,8 @@ public class FileWriteAheadLogManager extends GridCacheSharedManagerAdapter impl
/** Failure processor */
private final FailureProcessor failureProcessor;
- /** */
- private IgniteConfiguration igCfg;
+ /** Ignite configuration. */
+ private final IgniteConfiguration igCfg;
/** Persistence metrics tracker. */
private DataStorageMetricsImpl metrics;
@@ -400,7 +400,12 @@ public class FileWriteAheadLogManager extends GridCacheSharedManagerAdapter impl
*/
private final Map segmentSize = new ConcurrentHashMap<>();
+ /** Pointer to the last successful checkpoint until which WAL segments can be safely deleted. */
+ private volatile WALPointer lastCheckpointPtr = new WALPointer(0, 0, 0);
+
/**
+ * Constructor.
+ *
* @param ctx Kernal context.
*/
public FileWriteAheadLogManager(final GridKernalContext ctx) {
@@ -428,8 +433,8 @@ public FileWriteAheadLogManager(final GridKernalContext ctx) {
fileHandleManagerFactory = new FileHandleManagerFactory(dsCfg);
maxSegCountWithoutCheckpoint =
- (long)((U.adjustedWalHistorySize(dsCfg, log) * CHECKPOINT_TRIGGER_ARCHIVE_SIZE_PERCENTAGE)
- / dsCfg.getWalSegmentSize());
+ (long)((U.adjustedWalHistorySize(dsCfg, log) * CHECKPOINT_TRIGGER_ARCHIVE_SIZE_PERCENTAGE)
+ / dsCfg.getWalSegmentSize());
switchSegmentRecordOffset = isArchiverEnabled() ? new AtomicLongArray(dsCfg.getWalSegments()) : null;
}
@@ -973,7 +978,8 @@ private FileWriteHandle closeBufAndRollover(
log,
segmentAware,
segmentRouter,
- lockedSegmentFileInputFactory);
+ lockedSegmentFileInputFactory
+ );
try {
iter.init(); // Make sure iterator is closed on any error.
@@ -989,25 +995,27 @@ private FileWriteHandle closeBufAndRollover(
/** {@inheritDoc} */
@Override public boolean reserve(WALPointer start) {
- assert start != null : "Invalid start pointer: " + start;
+ assert start != null;
if (mode == WALMode.NONE)
return false;
- segmentAware.reserve(start.index());
+ // Protection from deletion.
+ boolean reserved = segmentAware.reserve(start.index());
- if (!hasIndex(start.index())) {
- segmentAware.release(start.index());
+ // Segment presence check.
+ if (reserved && !hasIndex(start.index())) {
+ segmentAware.reserve(start.index());
- return false;
+ reserved = false;
}
- return true;
+ return reserved;
}
/** {@inheritDoc} */
@Override public void release(WALPointer start) {
- assert start != null : "Invalid start pointer: " + start;
+ assert start != null;
if (mode == WALMode.NONE)
return;
@@ -1016,16 +1024,16 @@ private FileWriteHandle closeBufAndRollover(
}
/**
- * @param absIdx Absolulte index to check.
- * @return {@code true} if has this index.
+ * Checking for the existence of an index.
+ *
+ * @param absIdx Segment index.
+ * @return {@code True} exists.
*/
private boolean hasIndex(long absIdx) {
String segmentName = fileName(absIdx);
- String zipSegmentName = segmentName + ZIP_SUFFIX;
-
boolean inArchive = new File(walArchiveDir, segmentName).exists() ||
- new File(walArchiveDir, zipSegmentName).exists();
+ new File(walArchiveDir, segmentName + ZIP_SUFFIX).exists();
if (inArchive)
return true;
@@ -1039,30 +1047,25 @@ private boolean hasIndex(long absIdx) {
}
/** {@inheritDoc} */
- @Override public int truncate(WALPointer low, WALPointer high) {
+ @Override public int truncate(@Nullable WALPointer high) {
if (high == null)
return 0;
- // File pointer bound: older entries will be deleted from archive
-
- FileDescriptor[] descs = scan(walArchiveDir.listFiles(WAL_SEGMENT_COMPACTED_OR_RAW_FILE_FILTER));
+ FileDescriptor[] descs = walArchiveFiles();
int deleted = 0;
for (FileDescriptor desc : descs) {
- if (low != null && desc.idx < low.index())
- continue;
-
- // Do not delete reserved or locked segment and any segment after it.
- if (segmentReservedOrLocked(desc.idx))
- return deleted;
-
long archivedAbsIdx = segmentAware.lastArchivedAbsoluteIndex();
long lastArchived = archivedAbsIdx >= 0 ? archivedAbsIdx : lastArchivedIndex();
- // We need to leave at least one archived segment to correctly determine the archive index.
- if (desc.idx < high.index() && desc.idx < lastArchived) {
+ if (desc.idx >= lastCheckpointPtr.index() // We cannot delete segments needed for binary recovery.
+ || desc.idx >= lastArchived // We cannot delete last segment, it is needed at start of node and avoid gaps.
+ || !segmentAware.minReserveIndex(desc.idx)) // We cannot delete reserved segment.
+ return deleted;
+
+ if (desc.idx < high.index()) {
if (!desc.file.delete()) {
U.warn(log, "Failed to remove obsolete WAL segment (make sure the process has enough rights): " +
desc.file.getAbsolutePath());
@@ -1099,8 +1102,7 @@ private boolean segmentReservedOrLocked(long absIdx) {
/** {@inheritDoc} */
@Override public void notchLastCheckpointPtr(WALPointer ptr) {
- if (compressor != null)
- segmentAware.keepUncompressedIdxFrom(ptr.index());
+ lastCheckpointPtr = ptr;
}
/** {@inheritDoc} */
@@ -1117,9 +1119,7 @@ private boolean segmentReservedOrLocked(long absIdx) {
if (lastArchived == -1)
return 0;
- int res = (int)(lastArchived - lastTruncated);
-
- return res >= 0 ? res : 0;
+ return Math.max((int)(lastArchived - lastTruncated), 0);
}
/** {@inheritDoc} */
@@ -1191,7 +1191,7 @@ private long lastArchivedIndex() {
@Nullable private FileDescriptor readFileDescriptor(File file, FileIOFactory ioFactory) {
FileDescriptor ds = new FileDescriptor(file);
- try (SegmentIO fileIO = ds.toIO(ioFactory)) {
+ try (SegmentIO fileIO = ds.toReadOnlyIO(ioFactory)) {
// File may be empty when LOG_ONLY mode is enabled and mmap is disabled.
if (fileIO.size() == 0)
return null;
@@ -1369,9 +1369,13 @@ private FileWriteHandle restoreWriteHandle(@Nullable WALPointer lastReadPtr) thr
FileWriteHandle hnd = fileHandleManager.initHandle(fileIO, off + len, ser);
- if (archiver0 != null)
- segmentAware.curAbsWalIdx(absIdx);
- else
+ segmentAware.curAbsWalIdx(absIdx);
+
+ FileDescriptor[] walArchiveFiles = walArchiveFiles();
+
+ segmentAware.minReserveIndex(F.isEmpty(walArchiveFiles) ? -1 : walArchiveFiles[0].idx - 1);
+
+ if (archiver0 == null)
segmentAware.setLastArchivedAbsoluteIndex(absIdx - 1);
// Getting segment sizes.
@@ -1494,17 +1498,17 @@ private FileWriteHandle initNextWriteHandle(FileWriteHandle cur) throws IgniteCh
* @throws StorageException If failed.
*/
private void checkOrPrepareFiles() throws StorageException {
- // Clean temp files.
- {
- File[] tmpFiles = walWorkDir.listFiles(WAL_SEGMENT_TEMP_FILE_FILTER);
-
- if (!F.isEmpty(tmpFiles)) {
- for (File tmp : tmpFiles) {
- if (!tmp.delete()) {
- throw new StorageException("Failed to delete previously created temp file " +
- "(make sure Ignite process has enough rights): " + tmp.getAbsolutePath());
- }
- }
+ Collection tmpFiles = new HashSet<>();
+
+ for (File walDir : F.asList(walWorkDir, walArchiveDir)) {
+ tmpFiles.addAll(F.asList(walDir.listFiles(WAL_SEGMENT_TEMP_FILE_FILTER)));
+ tmpFiles.addAll(F.asList(walDir.listFiles(WAL_SEGMENT_TEMP_FILE_COMPACTED_FILTER)));
+ }
+
+ for (File tmpFile : tmpFiles) {
+ if (tmpFile.exists() && !tmpFile.delete()) {
+ throw new StorageException("Failed to delete previously created temp file " +
+ "(make sure Ignite process has enough rights): " + tmpFile.getAbsolutePath());
}
}
@@ -1605,6 +1609,7 @@ private File pollNextFile(long curIdx) throws StorageException, IgniteInterrupte
FileArchiver archiver0 = archiver;
if (archiver0 == null) {
+ segmentAware.curAbsWalIdx(curIdx + 1);
segmentAware.setLastArchivedAbsoluteIndex(curIdx);
return new File(walWorkDir, fileName(curIdx + 1));
@@ -1634,7 +1639,9 @@ private File pollNextFile(long curIdx) throws StorageException, IgniteInterrupte
}
/**
- * Files from archive WAL directory.
+ * Files from {@link #walArchiveDir}.
+ *
+ * @return Raw or compressed WAL segments from archive.
*/
private FileDescriptor[] walArchiveFiles() {
return scan(walArchiveDir.listFiles(WAL_SEGMENT_COMPACTED_OR_RAW_FILE_FILTER));
@@ -1642,8 +1649,8 @@ private FileDescriptor[] walArchiveFiles() {
/** {@inheritDoc} */
@Override public long maxArchivedSegmentToDelete() {
- //When maxWalArchiveSize==MAX_VALUE deleting files is not permit.
- if (dsCfg.getMaxWalArchiveSize() == Long.MAX_VALUE)
+ //When maxWalArchiveSize==-1 deleting files is not permitted.
+ if (dsCfg.getMaxWalArchiveSize() == DataStorageConfiguration.UNLIMITED_WAL_ARCHIVE)
return -1;
FileDescriptor[] archivedFiles = walArchiveFiles();
@@ -1984,22 +1991,6 @@ private long nextAbsoluteSegmentIndex() throws StorageException, IgniteInterrupt
}
}
- /**
- * @param absIdx Segment absolute index.
- * @return
{@code True} if can read, no lock is held,
{@code false} if work segment, need
- * release segment later, use {@link #releaseWorkSegment} for unlock
- */
- public boolean checkCanReadArchiveOrReserveWorkSegment(long absIdx) {
- return segmentAware.checkCanReadArchiveOrReserveWorkSegment(absIdx);
- }
-
- /**
- * @param absIdx Segment absolute index.
- */
- public void releaseWorkSegment(long absIdx) {
- segmentAware.releaseWorkSegment(absIdx);
- }
-
/**
* Moves WAL segment from work folder to archive folder. Temp file is used to do movement.
*
@@ -2081,18 +2072,12 @@ private void allocateRemainingFiles() throws StorageException {
checkFiles(
1,
true,
- new IgnitePredicate() {
- @Override public boolean apply(Integer integer) {
- return !checkStop();
- }
- },
- new CI1() {
- @Override public void apply(Integer idx) {
- synchronized (FileArchiver.this) {
- formatted = idx;
+ (IgnitePredicate)integer -> !checkStop(),
+ (CI1)idx -> {
+ synchronized (FileArchiver.this) {
+ formatted = idx;
- FileArchiver.this.notifyAll();
- }
+ FileArchiver.this.notifyAll();
}
}
);
@@ -2131,15 +2116,6 @@ private class FileCompressor extends FileCompressorWorker {
/** */
private void init() {
- File[] toDel = walArchiveDir.listFiles(WAL_SEGMENT_TEMP_FILE_COMPACTED_FILTER);
-
- for (File f : toDel) {
- if (isCancelled())
- return;
-
- f.delete();
- }
-
for (int i = 1; i < calculateThreadCount(); i++) {
FileCompressorWorker worker = new FileCompressorWorker(i, log);
@@ -2400,7 +2376,7 @@ private void deleteObsoleteRawSegments() {
if (segmentReservedOrLocked(desc.idx))
return;
- if (desc.idx < segmentAware.keepUncompressedIdxFrom() && duplicateIndices.contains(desc.idx)) {
+ if (desc.idx < lastCheckpointPtr.index() && duplicateIndices.contains(desc.idx)) {
if (desc.file.exists() && !desc.file.delete()) {
U.warn(log, "Failed to remove obsolete WAL segment " +
"(make sure the process has enough rights): " + desc.file.getAbsolutePath() +
@@ -2416,13 +2392,13 @@ private void deleteObsoleteRawSegments() {
*/
private class FileDecompressor extends GridWorker {
/** Decompression futures. */
- private Map> decompressionFutures = new HashMap<>();
+ private final Map> decompressionFutures = new HashMap<>();
/** Segments queue. */
private final PriorityBlockingQueue segmentsQueue = new PriorityBlockingQueue<>();
/** Byte array for draining data. */
- private byte[] arr = new byte[BUF_SIZE];
+ private final byte[] arr = new byte[BUF_SIZE];
/**
* @param log Logger.
@@ -2730,18 +2706,16 @@ private static class RecordsIterator extends AbstractWalRecordsIterator {
private final DataStorageConfiguration dsCfg;
/** Optional start pointer. */
- @Nullable
- private WALPointer start;
+ @Nullable private final WALPointer start;
/** Optional end pointer. */
- @Nullable
- private WALPointer end;
+ @Nullable private final WALPointer end;
/** Manager of segment location. */
- private SegmentRouter segmentRouter;
+ private final SegmentRouter segmentRouter;
/** Holder of actual information of latest manipulation on WAL segments. */
- private SegmentAware segmentAware;
+ private final SegmentAware segmentAware;
/**
* @param cctx Shared context.
@@ -2756,10 +2730,10 @@ private static class RecordsIterator extends AbstractWalRecordsIterator {
* @param log Logger @throws IgniteCheckedException If failed to initialize WAL segment.
* @param segmentAware Segment aware.
* @param segmentRouter Segment router.
- * @param segmentFileInputFactory
+ * @param segmentFileInputFactory Factory to provide I/O interfaces for read primitives with files.
*/
private RecordsIterator(
- GridCacheSharedContext cctx,
+ GridCacheSharedContext, ?> cctx,
File walArchiveDir,
File walWorkDir,
@Nullable WALPointer start,
@@ -2774,13 +2748,15 @@ private RecordsIterator(
SegmentRouter segmentRouter,
SegmentFileInputFactory segmentFileInputFactory
) throws IgniteCheckedException {
- super(log,
+ super(
+ log,
cctx,
serializerFactory,
ioFactory,
dsCfg.getWalRecordIteratorBufferSize(),
segmentFileInputFactory
);
+
this.walArchiveDir = walArchiveDir;
this.walWorkDir = walWorkDir;
this.archiver = archiver;
@@ -2890,57 +2866,70 @@ private void init() throws IgniteCheckedException {
curWalSegmIdx++;
- boolean readArchive = canReadArchiveOrReserveWork(curWalSegmIdx); //lock during creation handle.
+ // Segment deletion protection.
+ if (!segmentAware.reserve(curWalSegmIdx))
+ throw new IgniteCheckedException("Segment does not exist: " + curWalSegmIdx);
- FileDescriptor fd = null;
- ReadFileHandle nextHandle;
try {
- fd = segmentRouter.findSegment(curWalSegmIdx);
+ // Protection against transferring a segment to the archive by #archiver.
+ boolean readArchive = archiver != null && !segmentAware.lock(curWalSegmIdx);
- if (log.isDebugEnabled())
- log.debug("Reading next file [absIdx=" + curWalSegmIdx + ", file=" + fd.file.getAbsolutePath() + ']');
+ FileDescriptor fd = null;
+ ReadFileHandle nextHandle;
+ try {
+ fd = segmentRouter.findSegment(curWalSegmIdx);
- nextHandle = initReadHandle(fd, start != null && curWalSegmIdx == start.index() ? start : null);
- }
- catch (FileNotFoundException e) {
- if (readArchive)
- throw new IgniteCheckedException("Missing WAL segment in the archive", e);
- else {
- // Log only when no segments were read. This will help us avoiding logging on the end of the WAL.
- if (curRec == null && curWalSegment == null) {
- File workDirFile = new File(walWorkDir, fileName(curWalSegmIdx % dsCfg.getWalSegments()));
- File archiveDirFile = new File(walArchiveDir, fileName(curWalSegmIdx));
-
- U.warn(
- log,
- "Next segment file is not found [" +
- "curWalSegmIdx=" + curWalSegmIdx
- + ", start=" + start
- + ", end=" + end
- + ", filePath=" + (fd == null ? "" : fd.file.getAbsolutePath())
- + ", walWorkDir=" + walWorkDir
- + ", walWorkDirContent=" + listFileNames(walWorkDir)
- + ", walArchiveDir=" + walArchiveDir
- + ", walArchiveDirContent=" + listFileNames(walArchiveDir)
- + ", workDirFile=" + workDirFile.getName()
- + ", exists=" + workDirFile.exists()
- + ", archiveDirFile=" + archiveDirFile.getName()
- + ", exists=" + archiveDirFile.exists()
- + "]",
- e
- );
+ if (log.isDebugEnabled()) {
+ log.debug("Reading next file [absIdx=" + curWalSegmIdx +
+ ", file=" + fd.file.getAbsolutePath() + ']');
}
- nextHandle = null;
+ nextHandle = initReadHandle(fd, start != null && curWalSegmIdx == start.index() ? start : null);
}
- }
+ catch (FileNotFoundException e) {
+ if (readArchive)
+ throw new IgniteCheckedException("Missing WAL segment in the archive: " + curWalSegment, e);
+ else {
+ // Log only when no segments were read. This will help us avoiding logging on the end of the WAL.
+ if (curRec == null && curWalSegment == null) {
+ File workDirFile = new File(walWorkDir, fileName(curWalSegmIdx % dsCfg.getWalSegments()));
+ File archiveDirFile = new File(walArchiveDir, fileName(curWalSegmIdx));
+
+ U.warn(
+ log,
+ "Next segment file is not found [" +
+ "curWalSegmIdx=" + curWalSegmIdx
+ + ", start=" + start
+ + ", end=" + end
+ + ", filePath=" + (fd == null ? "" : fd.file.getAbsolutePath())
+ + ", walWorkDir=" + walWorkDir
+ + ", walWorkDirContent=" + listFileNames(walWorkDir)
+ + ", walArchiveDir=" + walArchiveDir
+ + ", walArchiveDirContent=" + listFileNames(walArchiveDir)
+ + ", workDirFile=" + workDirFile.getName()
+ + ", exists=" + workDirFile.exists()
+ + ", archiveDirFile=" + archiveDirFile.getName()
+ + ", exists=" + archiveDirFile.exists()
+ + "]",
+ e
+ );
+ }
- if (!readArchive)
- releaseWorkSegment(curWalSegmIdx);
+ nextHandle = null;
+ }
+ }
+ finally {
+ if (archiver != null && !readArchive)
+ segmentAware.unlock(curWalSegmIdx);
+ }
- curRec = null;
+ curRec = null;
- return nextHandle;
+ return nextHandle;
+ }
+ finally {
+ segmentAware.release(curWalSegmIdx);
+ }
}
/** */
@@ -2955,62 +2944,46 @@ private static List listFileNames(File dir) {
/** {@inheritDoc} */
@Override protected IgniteCheckedException handleRecordException(Exception e, @Nullable WALPointer ptr) {
- if (e instanceof IgniteCheckedException)
- if (X.hasCause(e, IgniteDataIntegrityViolationException.class))
- // This means that there is no explicit last sengment, so we iterate unil the very end.
- if (end == null) {
- long nextWalSegmentIdx = curWalSegmIdx + 1;
-
- if (!isArchiverEnabled())
- if (canIgnoreCrcError(nextWalSegmentIdx, nextWalSegmentIdx, e, ptr))
- return null;
-
+ if (e instanceof IgniteCheckedException && X.hasCause(e, IgniteDataIntegrityViolationException.class)) {
+ // This means that there is no explicit last segment, so we iterate until the very end.
+ if (end == null) {
+ long nextWalSegmentIdx = curWalSegmIdx + 1;
+
+ if (archiver == null) {
+ if (canIgnoreCrcError(nextWalSegmentIdx, nextWalSegmentIdx, e, ptr))
+ return null;
+ }
+ else {
// Check that we should not look this segment up in archive directory.
// Basically the same check as in "advanceSegment" method.
- if (isArchiverEnabled() && archiver != null)
- if (!canReadArchiveOrReserveWork(nextWalSegmentIdx))
- try {
- long workIdx = nextWalSegmentIdx % dsCfg.getWalSegments();
- if (canIgnoreCrcError(workIdx, nextWalSegmentIdx, e, ptr))
- return null;
- }
- finally {
- releaseWorkSegment(nextWalSegmentIdx);
+ // Segment deletion protection.
+ if (segmentAware.reserve(nextWalSegmentIdx)) {
+ try {
+ // Protection against transferring a segment to the archive by #archiver.
+ if (segmentAware.lock(nextWalSegmentIdx)) {
+ try {
+ long workIdx = nextWalSegmentIdx % dsCfg.getWalSegments();
+
+ if (canIgnoreCrcError(workIdx, nextWalSegmentIdx, e, ptr))
+ return null;
+ }
+ finally {
+ segmentAware.unlock(nextWalSegmentIdx);
+ }
}
+ }
+ finally {
+ segmentAware.release(nextWalSegmentIdx);
+ }
+ }
}
+ }
+ }
return super.handleRecordException(e, ptr);
}
- /**
- * @param absIdx Absolute index to check.
- * @return
{@code True} if we can safely read the archive,
{@code false} if the segment has
- * not been archived yet. In this case the corresponding work segment is reserved (will not be deleted until
- * release). Use {@link #releaseWorkSegment} for unlock
- */
- private boolean canReadArchiveOrReserveWork(long absIdx) {
- return archiver != null && archiver.checkCanReadArchiveOrReserveWorkSegment(absIdx);
- }
-
- /**
- * @param absIdx Absolute index to release.
- */
- private void releaseWorkSegment(long absIdx) {
- if (archiver != null)
- archiver.releaseWorkSegment(absIdx);
- }
-
- /**
- * Check that archiver is enabled
- */
- private boolean isArchiverEnabled() {
- if (walArchiveDir != null && walWorkDir != null)
- return !walArchiveDir.equals(walWorkDir);
-
- return !new File(dsCfg.getWalArchivePath()).equals(new File(dsCfg.getWalPath()));
- }
-
/**
* @param workIdx Work index.
* @param walSegmentIdx Wal segment index.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentArchivedStorage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentArchivedStorage.java
index 438b92217405b..53b3b598a97eb 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentArchivedStorage.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentArchivedStorage.java
@@ -43,21 +43,10 @@ class SegmentArchivedStorage extends SegmentObservable {
/**
* @param segmentLockStorage Protects WAL work segments from moving.
*/
- private SegmentArchivedStorage(SegmentLockStorage segmentLockStorage) {
+ SegmentArchivedStorage(SegmentLockStorage segmentLockStorage) {
this.segmentLockStorage = segmentLockStorage;
}
- /**
- * @param segmentLockStorage Protects WAL work segments from moving.
- */
- static SegmentArchivedStorage buildArchivedStorage(SegmentLockStorage segmentLockStorage) {
- SegmentArchivedStorage archivedStorage = new SegmentArchivedStorage(segmentLockStorage);
-
- segmentLockStorage.addObserver(archivedStorage::onSegmentUnlocked);
-
- return archivedStorage;
- }
-
/**
* @return Last archived segment absolute index.
*/
@@ -105,7 +94,7 @@ synchronized void awaitSegmentArchived(long awaitIdx) throws IgniteInterruptedCh
*/
synchronized void markAsMovedToArchive(long toArchive) throws IgniteInterruptedCheckedException {
try {
- while (segmentLockStorage.locked(toArchive) && !interrupted)
+ while (!segmentLockStorage.minLockIndex(toArchive) && !interrupted)
wait();
}
catch (InterruptedException e) {
@@ -145,7 +134,7 @@ private void checkInterrupted() throws IgniteInterruptedCheckedException {
/**
* Callback for waking up waiters of this object when unlocked happened.
*/
- private synchronized void onSegmentUnlocked(long segmentId) {
+ synchronized void onSegmentUnlocked(long segmentId) {
notifyAll();
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentAware.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentAware.java
index be60895b365f2..89523db552f83 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentAware.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentAware.java
@@ -19,10 +19,6 @@
import org.apache.ignite.internal.IgniteInterruptedCheckedException;
-import static org.apache.ignite.internal.processors.cache.persistence.wal.aware.SegmentArchivedStorage.buildArchivedStorage;
-import static org.apache.ignite.internal.processors.cache.persistence.wal.aware.SegmentCompressStorage.buildCompressStorage;
-import static org.apache.ignite.internal.processors.cache.persistence.wal.aware.SegmentCurrentStateStorage.buildCurrentStateStorage;
-
/**
* Holder of actual information of latest manipulation on WAL segments.
*/
@@ -34,7 +30,7 @@ public class SegmentAware {
private final SegmentLockStorage segmentLockStorage = new SegmentLockStorage();
/** Manages last archived index, emulates archivation in no-archiver mode. */
- private final SegmentArchivedStorage segmentArchivedStorage = buildArchivedStorage(segmentLockStorage);
+ private final SegmentArchivedStorage segmentArchivedStorage;
/** Storage of actual information about current index of compressed segments. */
private final SegmentCompressStorage segmentCompressStorage;
@@ -43,12 +39,21 @@ public class SegmentAware {
private final SegmentCurrentStateStorage segmentCurrStateStorage;
/**
+ * Constructor.
+ *
* @param walSegmentsCnt Total WAL segments count.
* @param compactionEnabled Is wal compaction enabled.
*/
public SegmentAware(int walSegmentsCnt, boolean compactionEnabled) {
- segmentCurrStateStorage = buildCurrentStateStorage(walSegmentsCnt, segmentArchivedStorage);
- segmentCompressStorage = buildCompressStorage(segmentArchivedStorage, compactionEnabled);
+ segmentArchivedStorage = new SegmentArchivedStorage(segmentLockStorage);
+
+ segmentCurrStateStorage = new SegmentCurrentStateStorage(walSegmentsCnt);
+ segmentCompressStorage = new SegmentCompressStorage(compactionEnabled);
+
+ segmentArchivedStorage.addObserver(segmentCurrStateStorage::onSegmentArchived);
+ segmentArchivedStorage.addObserver(segmentCompressStorage::onSegmentArchived);
+
+ segmentLockStorage.addObserver(segmentArchivedStorage::onSegmentUnlocked);
}
/**
@@ -132,20 +137,6 @@ public long lastCompressedIdx() {
return segmentCompressStorage.lastCompressedIdx();
}
- /**
- * @param idx Minimum raw segment index that should be preserved from deletion.
- */
- public void keepUncompressedIdxFrom(long idx) {
- segmentCompressStorage.keepUncompressedIdxFrom(idx);
- }
-
- /**
- * @return Minimum raw segment index that should be preserved from deletion.
- */
- public long keepUncompressedIdxFrom() {
- return segmentCompressStorage.keepUncompressedIdxFrom();
- }
-
/**
* Update current WAL index.
*
@@ -184,10 +175,14 @@ public long lastArchivedAbsoluteIndex() {
}
/**
+ * Segment reservation. It will be successful if segment is {@code >} than
+ * the {@link #minReserveIndex minimum}.
+ *
* @param absIdx Index for reservation.
+ * @return {@code True} if the reservation was successful.
*/
- public void reserve(long absIdx) {
- reservationStorage.reserve(absIdx);
+ public boolean reserve(long absIdx) {
+ return reservationStorage.reserve(absIdx);
}
/**
@@ -208,9 +203,9 @@ public void release(long absIdx) {
}
/**
- * Check if WAL segment locked (protected from move to archive)
+ * Check if WAL segment locked (protected from move to archive).
*
- * @param absIdx Index for check reservation.
+ * @param absIdx Index for check locking.
* @return {@code True} if index is locked.
*/
public boolean locked(long absIdx) {
@@ -218,27 +213,20 @@ public boolean locked(long absIdx) {
}
/**
- * @param absIdx Segment absolute index.
- * @return
{@code True} if can read, no lock is held,
{@code false} if work segment, need release
- * segment later, use {@link #releaseWorkSegment} for unlock
- */
- public boolean checkCanReadArchiveOrReserveWorkSegment(long absIdx) {
- return lastArchivedAbsoluteIndex() >= absIdx || segmentLockStorage.lockWorkSegment(absIdx);
- }
-
- /**
- * Visible for test.
+ * Segment lock. It will be successful if segment is {@code >} than
+ * the {@link #lastArchivedAbsoluteIndex last archived}.
*
- * @param absIdx Segment absolute index. segment later, use {@link #releaseWorkSegment} for unlock
+ * @param absIdx Index to lock.
+ * @return {@code True} if the lock was successful.
*/
- void lockWorkSegment(long absIdx) {
- segmentLockStorage.lockWorkSegment(absIdx);
+ public boolean lock(long absIdx) {
+ return segmentLockStorage.lockWorkSegment(absIdx);
}
/**
- * @param absIdx Segment absolute index.
+ * @param absIdx Index to unlock.
*/
- public void releaseWorkSegment(long absIdx) {
+ public void unlock(long absIdx) {
segmentLockStorage.releaseWorkSegment(absIdx);
}
@@ -274,4 +262,28 @@ public void forceInterrupt() {
segmentCurrStateStorage.forceInterrupt();
}
+
+ /**
+ * Increasing minimum segment index after that can be reserved.
+ * Value will be updated if it is greater than the current one.
+ * If segment is already reserved, the update will fail.
+ *
+ * @param absIdx Absolut segment index.
+ * @return {@code True} if update is successful.
+ */
+ public boolean minReserveIndex(long absIdx) {
+ return reservationStorage.minReserveIndex(absIdx);
+ }
+
+ /**
+ * Increasing minimum segment index after that can be locked.
+ * Value will be updated if it is greater than the current one.
+ * If segment is already reserved, the update will fail.
+ *
+ * @param absIdx Absolut segment index.
+ * @return {@code True} if update is successful.
+ */
+ public boolean minLockIndex(long absIdx) {
+ return segmentLockStorage.minLockIndex(absIdx);
+ }
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentCompressStorage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentCompressStorage.java
index 5d88e5233ecc4..62fe69d7c1a47 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentCompressStorage.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentCompressStorage.java
@@ -30,9 +30,6 @@ public class SegmentCompressStorage {
/** Flag of interrupt waiting on this object. */
private volatile boolean interrupted;
- /** Manages last archived index, emulates archivation in no-archiver mode. */
- private final SegmentArchivedStorage segmentArchivedStorage;
-
/** If WAL compaction enabled. */
private final boolean compactionEnabled;
@@ -51,32 +48,15 @@ public class SegmentCompressStorage {
/** Compressed segment with maximal index. */
private long lastMaxCompressedIdx = -1L;
- /** Min uncompressed index to keep. */
- private volatile long minUncompressedIdxToKeep = -1L;
-
/**
- * @param segmentArchivedStorage Storage of last archived segment.
+ * Constructor.
+ *
* @param compactionEnabled If WAL compaction enabled.
*/
- private SegmentCompressStorage(SegmentArchivedStorage segmentArchivedStorage, boolean compactionEnabled) {
- this.segmentArchivedStorage = segmentArchivedStorage;
-
+ SegmentCompressStorage(boolean compactionEnabled) {
this.compactionEnabled = compactionEnabled;
}
- /**
- * @param segmentArchivedStorage Storage of last archived segment.
- * @param compactionEnabled If WAL compaction enabled.
- */
- static SegmentCompressStorage buildCompressStorage(SegmentArchivedStorage segmentArchivedStorage,
- boolean compactionEnabled) {
- SegmentCompressStorage storage = new SegmentCompressStorage(segmentArchivedStorage, compactionEnabled);
-
- segmentArchivedStorage.addObserver(storage::onSegmentArchived);
-
- return storage;
- }
-
/**
* Callback after segment compression finish.
*
@@ -148,27 +128,13 @@ private void checkInterrupted() throws IgniteInterruptedCheckedException {
/**
* Callback for waking up compressor when new segment is archived.
*/
- private synchronized void onSegmentArchived(long lastAbsArchivedIdx) {
+ synchronized void onSegmentArchived(long lastAbsArchivedIdx) {
while (lastEnqueuedToCompressIdx < lastAbsArchivedIdx && compactionEnabled)
segmentsToCompress.add(++lastEnqueuedToCompressIdx);
notifyAll();
}
- /**
- * @param idx Minimum raw segment index that should be preserved from deletion.
- */
- void keepUncompressedIdxFrom(long idx) {
- minUncompressedIdxToKeep = idx;
- }
-
- /**
- * @return Minimum raw segment index that should be preserved from deletion.
- */
- long keepUncompressedIdxFrom() {
- return minUncompressedIdxToKeep;
- }
-
/**
* Reset interrupted flag.
*/
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentCurrentStateStorage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentCurrentStateStorage.java
index 73394972db182..6672879b1a6c8 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentCurrentStateStorage.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentCurrentStateStorage.java
@@ -22,7 +22,7 @@
/**
* Storage of absolute current segment index.
*/
-class SegmentCurrentStateStorage {
+class SegmentCurrentStateStorage extends SegmentObservable {
/** Flag of interrupt of waiting on this object. */
private volatile boolean interrupted;
@@ -32,38 +32,22 @@ class SegmentCurrentStateStorage {
/** Total WAL segments count. */
private final int walSegmentsCnt;
- /** Manages last archived index, emulates archivation in no-archiver mode. */
- private final SegmentArchivedStorage segmentArchivedStorage;
-
/**
* Absolute current segment index WAL Manager writes to. Guarded by this. Incremented during rollover.
* Also may be directly set if WAL is resuming logging after start.
*/
private volatile long curAbsWalIdx = -1;
- /**
- * @param walSegmentsCnt Total WAL segments count.
- * @param segmentArchivedStorage Last archived segment storage.
- */
- private SegmentCurrentStateStorage(int walSegmentsCnt, SegmentArchivedStorage segmentArchivedStorage) {
- this.walSegmentsCnt = walSegmentsCnt;
- this.segmentArchivedStorage = segmentArchivedStorage;
- }
+ /** Last archived file absolute index. */
+ private volatile long lastAbsArchivedIdx = -1;
/**
+ * Constructor.
+ *
* @param walSegmentsCnt Total WAL segments count.
- * @param segmentArchivedStorage Last archived segment storage.
*/
- static SegmentCurrentStateStorage buildCurrentStateStorage(
- int walSegmentsCnt,
- SegmentArchivedStorage segmentArchivedStorage
- ) {
-
- SegmentCurrentStateStorage currStorage = new SegmentCurrentStateStorage(walSegmentsCnt, segmentArchivedStorage);
-
- segmentArchivedStorage.addObserver(currStorage::onSegmentArchived);
-
- return currStorage;
+ SegmentCurrentStateStorage(int walSegmentsCnt) {
+ this.walSegmentsCnt = walSegmentsCnt;
}
/**
@@ -87,13 +71,11 @@ synchronized void awaitSegment(long absSegIdx) throws IgniteInterruptedCheckedEx
* Waiting until archivation of next segment will be allowed.
*/
synchronized long waitNextSegmentForArchivation() throws IgniteInterruptedCheckedException {
- long lastArchivedSegment = segmentArchivedStorage.lastArchivedAbsoluteIndex();
-
//We can archive segment if it less than current work segment so for archivate lastArchiveSegment + 1
// we should be ensure that currentWorkSegment = lastArchiveSegment + 2
- awaitSegment(lastArchivedSegment + 2);
+ awaitSegment(lastAbsArchivedIdx + 2);
- return lastArchivedSegment + 1;
+ return lastAbsArchivedIdx + 1;
}
/**
@@ -102,23 +84,31 @@ synchronized long waitNextSegmentForArchivation() throws IgniteInterruptedChecke
*
* @return Next absolute segment index.
*/
- synchronized long nextAbsoluteSegmentIndex() throws IgniteInterruptedCheckedException {
- curAbsWalIdx++;
+ long nextAbsoluteSegmentIndex() throws IgniteInterruptedCheckedException {
+ long nextAbsIdx;
- notifyAll();
+ synchronized (this) {
+ curAbsWalIdx++;
- try {
- while (curAbsWalIdx - segmentArchivedStorage.lastArchivedAbsoluteIndex() > walSegmentsCnt && !forceInterrupted)
- wait();
- }
- catch (InterruptedException e) {
- throw new IgniteInterruptedCheckedException(e);
+ notifyAll();
+
+ try {
+ while (curAbsWalIdx - lastAbsArchivedIdx > walSegmentsCnt && !forceInterrupted)
+ wait();
+ }
+ catch (InterruptedException e) {
+ throw new IgniteInterruptedCheckedException(e);
+ }
+
+ if (forceInterrupted)
+ throw new IgniteInterruptedCheckedException("Interrupt waiting of change archived idx");
+
+ nextAbsIdx = curAbsWalIdx;
}
- if (forceInterrupted)
- throw new IgniteInterruptedCheckedException("Interrupt waiting of change archived idx");
+ notifyObservers(nextAbsIdx);
- return curAbsWalIdx;
+ return nextAbsIdx;
}
/**
@@ -126,10 +116,14 @@ synchronized long nextAbsoluteSegmentIndex() throws IgniteInterruptedCheckedExce
*
* @param curAbsWalIdx New current WAL index.
*/
- synchronized void curAbsWalIdx(long curAbsWalIdx) {
- this.curAbsWalIdx = curAbsWalIdx;
+ void curAbsWalIdx(long curAbsWalIdx) {
+ synchronized (this) {
+ this.curAbsWalIdx = curAbsWalIdx;
- notifyAll();
+ notifyAll();
+ }
+
+ notifyObservers(curAbsWalIdx);
}
/**
@@ -160,8 +154,12 @@ synchronized void forceInterrupt() {
/**
* Callback for waking up awaiting when new segment is archived.
+ *
+ * @param lastAbsArchivedIdx Last archived file absolute index.
*/
- private synchronized void onSegmentArchived(long lastAbsArchivedIdx) {
+ synchronized void onSegmentArchived(long lastAbsArchivedIdx) {
+ this.lastAbsArchivedIdx = lastAbsArchivedIdx;
+
notifyAll();
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentLockStorage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentLockStorage.java
index 6588769edd01b..a5a79486ec4b4 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentLockStorage.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentLockStorage.java
@@ -29,7 +29,10 @@ public class SegmentLockStorage extends SegmentObservable {
* Maps absolute segment index to locks counter. Lock on segment protects from archiving segment and may come from
* {@link FileWriteAheadLogManager.RecordsIterator} during WAL replay. Map itself is guarded by this.
*/
- private Map locked = new ConcurrentHashMap<>();
+ private final Map locked = new ConcurrentHashMap<>();
+
+ /** Maximum segment index that can be locked. */
+ private volatile long minLockIdx = -1;
/**
* Check if WAL segment locked (protected from move to archive)
@@ -37,17 +40,22 @@ public class SegmentLockStorage extends SegmentObservable {
* @param absIdx Index for check reservation.
* @return {@code True} if index is locked.
*/
- public boolean locked(long absIdx) {
+ boolean locked(long absIdx) {
return locked.containsKey(absIdx);
}
/**
- * @param absIdx Segment absolute index.
- * @return
{@code True} if can read, no lock is held,
{@code false} if work segment, need release
- * segment later, use {@link #releaseWorkSegment} for unlock
+ * Segment lock. It will be successful if segment is {@code >} than the {@link #minLockIdx minimum}.
+ *
+ * @param absIdx Index to lock.
+ * @return {@code True} if the lock was successful.
*/
- boolean lockWorkSegment(long absIdx) {
- locked.compute(absIdx, (idx, count) -> count == null ? 1 : count + 1);
+ synchronized boolean lockWorkSegment(long absIdx) {
+ if (absIdx > minLockIdx) {
+ locked.merge(absIdx, 1, Integer::sum);
+
+ return true;
+ }
return false;
}
@@ -64,4 +72,21 @@ void releaseWorkSegment(long absIdx) {
notifyObservers(absIdx);
}
+
+ /**
+ * Increasing minimum segment index that can be locked.
+ * Value will be updated if it is greater than the current one.
+ * If segment is already locked, the update will fail.
+ *
+ * @param absIdx Absolut segment index.
+ * @return {@code True} if update is successful.
+ */
+ synchronized boolean minLockIndex(long absIdx) {
+ if (locked(absIdx))
+ return false;
+
+ minLockIdx = Math.max(minLockIdx, absIdx);
+
+ return true;
+ }
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentReservationStorage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentReservationStorage.java
index 50c2bbf067d84..42eece70761b2 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentReservationStorage.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/aware/SegmentReservationStorage.java
@@ -27,13 +27,25 @@ class SegmentReservationStorage {
* Maps absolute segment index to reservation counter. If counter > 0 then we wouldn't delete all segments which has
* index >= reserved segment index. Guarded by {@code this}.
*/
- private NavigableMap reserved = new TreeMap<>();
+ private final NavigableMap reserved = new TreeMap<>();
+
+ /** Maximum segment index that can be reserved. */
+ private long minReserveIdx = -1;
/**
+ * Segment reservation. It will be successful if segment is {@code >} than the {@link #minReserveIdx minimum}.
+ *
* @param absIdx Index for reservation.
+ * @return {@code True} if the reservation was successful.
*/
- synchronized void reserve(long absIdx) {
- reserved.merge(absIdx, 1, (a, b) -> a + b);
+ synchronized boolean reserve(long absIdx) {
+ if (absIdx > minReserveIdx) {
+ reserved.merge(absIdx, 1, Integer::sum);
+
+ return true;
+ }
+
+ return false;
}
/**
@@ -59,4 +71,21 @@ synchronized void release(long absIdx) {
else
reserved.put(absIdx, cur - 1);
}
+
+ /**
+ * Increasing minimum segment index that can be reserved.
+ * Value will be updated if it is greater than the current one.
+ * If segment is already reserved, the update will fail.
+ *
+ * @param absIdx Absolut segment index.
+ * @return {@code True} if update is successful.
+ */
+ synchronized boolean minReserveIndex(long absIdx) {
+ if (reserved(absIdx))
+ return false;
+
+ minReserveIdx = Math.max(minReserveIdx, absIdx);
+
+ return true;
+ }
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/io/LockedReadFileInput.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/io/LockedReadFileInput.java
index 6bb47863f4afd..13a905fd99234 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/io/LockedReadFileInput.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/io/LockedReadFileInput.java
@@ -17,6 +17,7 @@
package org.apache.ignite.internal.processors.cache.persistence.wal.io;
+import java.io.FileNotFoundException;
import java.io.IOException;
import org.apache.ignite.internal.processors.cache.persistence.file.FileIO;
import org.apache.ignite.internal.processors.cache.persistence.wal.ByteBufferExpander;
@@ -69,19 +70,29 @@ final class LockedReadFileInput extends SimpleFileInput {
if (available >= requested)
return;
- boolean readArchive = segmentAware.checkCanReadArchiveOrReserveWorkSegment(segmentId);
+ // Segment deletion protection.
+ if (!segmentAware.reserve(segmentId))
+ throw new FileNotFoundException("Segment does not exist: " + segmentId);
+
try {
- if (readArchive && !isLastReadFromArchive) {
- isLastReadFromArchive = true;
+ // Protection against transferring a segment to the archive by #archiver.
+ boolean readArchive = !segmentAware.lock(segmentId);
+ try {
+ if (readArchive && !isLastReadFromArchive) {
+ isLastReadFromArchive = true;
- refreshIO();
- }
+ refreshIO();
+ }
- super.ensure(requested);
+ super.ensure(requested);
+ }
+ finally {
+ if (!readArchive)
+ segmentAware.unlock(segmentId);
+ }
}
finally {
- if (!readArchive)
- segmentAware.releaseWorkSegment(segmentId);
+ segmentAware.release(segmentId);
}
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/io/LockedSegmentFileInputFactory.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/io/LockedSegmentFileInputFactory.java
index 909f912659c5e..6e38b70393e0c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/io/LockedSegmentFileInputFactory.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/io/LockedSegmentFileInputFactory.java
@@ -63,7 +63,7 @@ public LockedSegmentFileInputFactory(
id -> {
FileDescriptor segment = segmentRouter.findSegment(id);
- return segment.toIO(fileIOFactory);
+ return segment.toReadOnlyIO(fileIOFactory);
}
);
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/IgniteWalIteratorFactory.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/IgniteWalIteratorFactory.java
index dd06244d4f71f..68fc432483eda 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/IgniteWalIteratorFactory.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/IgniteWalIteratorFactory.java
@@ -332,7 +332,7 @@ private FileDescriptor readFileDescriptor(File file, FileIOFactory ioFactory) {
FileDescriptor ds = new FileDescriptor(file);
try (
- SegmentIO fileIO = ds.toIO(ioFactory);
+ SegmentIO fileIO = ds.toReadOnlyIO(ioFactory);
ByteBufferExpander buf = new ByteBufferExpander(HEADER_RECORD_SIZE, ByteOrder.nativeOrder())
) {
final DataInput in = segmentFileInputFactory.createFileInput(fileIO, buf);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java
index d5a9ed05f5956..e0e2092d770f5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java
@@ -54,6 +54,7 @@
import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
import org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl;
import org.apache.ignite.internal.processors.cache.mvcc.MvccProcessor;
+import org.apache.ignite.internal.processors.cache.persistence.defragmentation.IgniteDefragmentation;
import org.apache.ignite.internal.processors.cache.persistence.filename.PdsFolderSettings;
import org.apache.ignite.internal.processors.cache.persistence.filename.PdsFoldersResolver;
import org.apache.ignite.internal.processors.cacheobject.IgniteCacheObjectProcessor;
@@ -490,6 +491,11 @@ protected IgniteConfiguration prepareIgniteConfiguration() {
return null;
}
+ /** {@inheritDoc} */
+ @Override public IgniteDefragmentation defragmentation() {
+ return null;
+ }
+
/** {@inheritDoc} */
@Override public WorkersRegistry workersRegistry() {
return null;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneWalRecordsIterator.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneWalRecordsIterator.java
index 912aecd348ae9..b2f9975f58111 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneWalRecordsIterator.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneWalRecordsIterator.java
@@ -326,7 +326,7 @@ private boolean checkBounds(long idx) {
SegmentHeader segmentHeader;
while (true) {
try {
- fileIO = fd.toIO(ioFactory);
+ fileIO = fd.toReadOnlyIO(ioFactory);
segmentHeader = readSegmentHeader(fileIO, FILE_INPUT_FACTORY);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageImpl.java
index 4ffd0caaa7396..880f6454eb610 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageImpl.java
@@ -28,6 +28,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
@@ -41,6 +42,7 @@
import org.apache.ignite.internal.GridKernalContext;
import org.apache.ignite.internal.IgniteInternalFuture;
import org.apache.ignite.internal.IgniteInterruptedCheckedException;
+import org.apache.ignite.internal.NodeStoppingException;
import org.apache.ignite.internal.managers.discovery.DiscoveryCustomMessage;
import org.apache.ignite.internal.managers.discovery.DiscoveryLocalJoinData;
import org.apache.ignite.internal.managers.discovery.GridDiscoveryManager;
@@ -59,12 +61,14 @@
import org.apache.ignite.internal.processors.subscription.GridInternalSubscriptionProcessor;
import org.apache.ignite.internal.util.IgniteUtils;
import org.apache.ignite.internal.util.future.GridFutureAdapter;
+import org.apache.ignite.internal.util.typedef.X;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteBiTuple;
import org.apache.ignite.lang.IgniteFuture;
import org.apache.ignite.marshaller.jdk.JdkMarshaller;
import org.apache.ignite.spi.IgniteNodeValidationResult;
+import org.apache.ignite.spi.IgniteSpiException;
import org.apache.ignite.spi.discovery.DiscoveryDataBag;
import org.apache.ignite.spi.discovery.DiscoveryDataBag.GridDiscoveryData;
import org.apache.ignite.spi.discovery.DiscoveryDataBag.JoiningNodeDiscoveryData;
@@ -175,6 +179,12 @@ public class DistributedMetaStorageImpl extends GridProcessorAdapter
*/
private final ConcurrentMap> updateFuts = new ConcurrentHashMap<>();
+ /** */
+ private final ReadWriteLock updateFutsStopLock = new ReentrantReadWriteLock();
+
+ /** */
+ private boolean stopped;
+
/**
* Lock to access/update data and component's state.
*/
@@ -287,7 +297,7 @@ public DistributedMetaStorageImpl(GridKernalContext ctx) {
finally {
lock.writeLock().unlock();
- cancelUpdateFutures();
+ cancelUpdateFutures(nodeStoppingException(), true);
}
}
@@ -914,7 +924,7 @@ private String validatePayload(DistributedMetaStorageJoiningNodeData joiningData
ver = INITIAL_VERSION;
- cancelUpdateFutures();
+ cancelUpdateFutures(new IgniteCheckedException("Client was disconnected during the operation."), false);
}
finally {
lock.writeLock().unlock();
@@ -924,13 +934,28 @@ private String validatePayload(DistributedMetaStorageJoiningNodeData joiningData
/**
* Cancel all waiting futures and clear the map.
*/
- private void cancelUpdateFutures() {
- for (GridFutureAdapter fut : updateFuts.values())
- fut.onDone(new IgniteCheckedException("Client was disconnected during the operation."));
+ private void cancelUpdateFutures(Exception e, boolean stop) {
+ updateFutsStopLock.writeLock().lock();
+
+ try {
+ stopped = stop;
+
+ for (GridFutureAdapter fut : updateFuts.values())
+ fut.onDone(e);
- updateFuts.clear();
+ updateFuts.clear();
+ }
+ finally {
+ updateFutsStopLock.writeLock().unlock();
+ }
+ }
+
+ /** */
+ private static NodeStoppingException nodeStoppingException() {
+ return new NodeStoppingException("Node is stopping.");
}
+
/** {@inheritDoc} */
@Override public IgniteInternalFuture> onReconnected(boolean clusterRestarted) {
assert isClient;
@@ -1033,14 +1058,12 @@ else if (!isClient && ver.id() > 0) {
* @throws IgniteCheckedException If there was an error while sending discovery message.
*/
private GridFutureAdapter> startWrite(String key, byte[] valBytes) throws IgniteCheckedException {
- if (!isSupported(ctx))
- throw new IgniteCheckedException(NOT_SUPPORTED_MSG);
-
UUID reqId = UUID.randomUUID();
- GridFutureAdapter fut = new GridFutureAdapter<>();
+ GridFutureAdapter> fut = prepareWriteFuture(key, reqId);
- updateFuts.put(reqId, fut);
+ if (fut.isDone())
+ return fut;
DiscoveryCustomMessage msg = new DistributedMetaStorageUpdateMessage(reqId, key, valBytes);
@@ -1054,14 +1077,12 @@ private GridFutureAdapter> startWrite(String key, byte[] valBytes) throws Igni
*/
private GridFutureAdapter startCas(String key, byte[] expValBytes, byte[] newValBytes)
throws IgniteCheckedException {
- if (!isSupported(ctx))
- throw new IgniteCheckedException(NOT_SUPPORTED_MSG);
-
UUID reqId = UUID.randomUUID();
- GridFutureAdapter fut = new GridFutureAdapter<>();
+ GridFutureAdapter fut = prepareWriteFuture(key, reqId);
- updateFuts.put(reqId, fut);
+ if (fut.isDone())
+ return fut;
DiscoveryCustomMessage msg = new DistributedMetaStorageCasMessage(reqId, key, expValBytes, newValBytes);
@@ -1070,6 +1091,58 @@ private GridFutureAdapter startCas(String key, byte[] expValBytes, byte
return fut;
}
+ /**
+ * This method will perform some preliminary checks before starting write or cas operation.
+ * It also updates {@link #updateFuts} in case if everything's ok.
+ *
+ * Tricky part is exception handling from "isSupported" method. It can be thrown by
+ * {@code ZookeeperDiscoveryImpl#checkState()} method, but we can't just leave it as is.
+ * There are components that rely on distributed metastorage throwing {@link NodeStoppingException}.
+ *
+ * @return Future that must be returned immediately or {@code null}.
+ * @throws IgniteCheckedException If cluster can't perform this update.
+ */
+ private GridFutureAdapter prepareWriteFuture(String key, UUID reqId) throws IgniteCheckedException {
+ boolean supported;
+
+ try {
+ supported = isSupported(ctx);
+ }
+ catch (Exception e) {
+ if (X.hasCause(e, IgniteSpiException.class) && e.getMessage() != null && e.getMessage().contains("Node stopped.")) {
+ GridFutureAdapter fut = new GridFutureAdapter<>();
+
+ fut.onDone(nodeStoppingException());
+
+ return fut;
+ }
+
+ throw e;
+ }
+
+ if (!supported)
+ throw new IgniteCheckedException(NOT_SUPPORTED_MSG);
+
+ GridFutureAdapter fut = new GridFutureAdapter<>();
+
+ updateFutsStopLock.readLock().lock();
+
+ try {
+ if (stopped) {
+ fut.onDone(nodeStoppingException());
+
+ return fut;
+ }
+
+ updateFuts.put(reqId, fut);
+ }
+ finally {
+ updateFutsStopLock.readLock().unlock();
+ }
+
+ return fut;
+ }
+
/**
* Invoked when {@link DistributedMetaStorageUpdateMessage} received. Attempts to store received data (depends on
* current {@link #bridge} value). Invokes failure handler with critical error if attempt failed for some reason.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/GridMetricManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/GridMetricManager.java
index fddf7ff91fab2..267b2a11ccab2 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/GridMetricManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/metric/GridMetricManager.java
@@ -439,8 +439,11 @@ else if (m instanceof HistogramMetric)
opsFut.markInitialized();
opsFut.get();
}
+ catch (NodeStoppingException ignored) {
+ // No-op.
+ }
catch (IgniteCheckedException e) {
- throw new IgniteException(e);
+ log.error("Failed to remove metrics configuration.", e);
}
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformConfigurationUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformConfigurationUtils.java
index e02b73d3e1103..fbe3218014e31 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformConfigurationUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/utils/PlatformConfigurationUtils.java
@@ -49,6 +49,7 @@
import org.apache.ignite.cache.QueryIndex;
import org.apache.ignite.cache.QueryIndexType;
import org.apache.ignite.cache.affinity.AffinityFunction;
+import org.apache.ignite.cache.affinity.rendezvous.ClusterNodeAttributeAffinityBackupFilter;
import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction;
import org.apache.ignite.cache.eviction.EvictionPolicy;
import org.apache.ignite.cache.eviction.fifo.FifoEvictionPolicy;
@@ -452,6 +453,18 @@ public static PlatformAffinityFunction readAffinityFunction(BinaryRawReaderEx in
f.setPartitions(partitions);
f.setExcludeNeighbors(exclNeighbours);
baseFunc = f;
+
+ int attrCnt = in.readInt();
+ if (attrCnt > 0) {
+ String[] attrs = new String[attrCnt];
+
+ for (int i = 0; i < attrCnt; i++) {
+ attrs[i] = in.readString();
+ }
+
+ f.setAffinityBackupFilter(new ClusterNodeAttributeAffinityBackupFilter(attrs));
+ }
+
break;
}
default:
@@ -492,17 +505,23 @@ private static void writeAffinityFunction(BinaryRawWriter out, AffinityFunction
out.writeBoolean(f0.isExcludeNeighbors());
out.writeByte((byte) 0); // override flags
out.writeObject(null); // user func
+
+ writeAffinityBackupFilter(out, f0.getAffinityBackupFilter());
}
else if (f instanceof PlatformAffinityFunction) {
PlatformAffinityFunction f0 = (PlatformAffinityFunction) f;
AffinityFunction baseFunc = f0.getBaseFunc();
if (baseFunc instanceof RendezvousAffinityFunction) {
+ RendezvousAffinityFunction rendezvous = (RendezvousAffinityFunction) baseFunc;
+
out.writeByte((byte) 2);
out.writeInt(f0.partitions());
- out.writeBoolean(((RendezvousAffinityFunction) baseFunc).isExcludeNeighbors());
+ out.writeBoolean(rendezvous.isExcludeNeighbors());
out.writeByte(f0.getOverrideFlags());
out.writeObject(f0.getUserFunc());
+
+ writeAffinityBackupFilter(out, rendezvous.getAffinityBackupFilter());
}
else {
out.writeByte((byte) 3);
@@ -516,6 +535,26 @@ else if (f instanceof PlatformAffinityFunction) {
out.writeByte((byte)0);
}
+ /**
+ * Writes affinity backup filter.
+ *
+ * @param out Stream.
+ * @param filter Filter.
+ */
+ private static void writeAffinityBackupFilter(BinaryRawWriter out, Object filter) {
+ if (filter instanceof ClusterNodeAttributeAffinityBackupFilter) {
+ ClusterNodeAttributeAffinityBackupFilter backupFilter = (ClusterNodeAttributeAffinityBackupFilter) filter;
+
+ String[] attrs = backupFilter.getAttributeNames();
+ out.writeInt(attrs.length);
+
+ for (String attr : attrs)
+ out.writeString(attr);
+ }
+ else
+ out.writeInt(-1);
+ }
+
/**
* Writes the eviction policy.
* @param out Stream.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
index 2dc9910ab5709..3cf5c38021780 100755
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
@@ -10797,7 +10797,8 @@ else if (regCfg.getMaxSize() < 8 * GB)
* @return User-set max WAL archive size of triple size of the maximum checkpoint buffer.
*/
public static long adjustedWalHistorySize(DataStorageConfiguration dsCfg, @Nullable IgniteLogger log) {
- if (dsCfg.getMaxWalArchiveSize() != DataStorageConfiguration.DFLT_WAL_ARCHIVE_MAX_SIZE)
+ if (dsCfg.getMaxWalArchiveSize() != DataStorageConfiguration.UNLIMITED_WAL_ARCHIVE &&
+ dsCfg.getMaxWalArchiveSize() != DataStorageConfiguration.DFLT_WAL_ARCHIVE_MAX_SIZE)
return dsCfg.getMaxWalArchiveSize();
// Find out the maximum checkpoint buffer size.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/defragmentation/VisorDefragmentationTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/defragmentation/VisorDefragmentationTask.java
index 14cea626e7724..88fde8b5af6f6 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/defragmentation/VisorDefragmentationTask.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/defragmentation/VisorDefragmentationTask.java
@@ -17,27 +17,17 @@
package org.apache.ignite.internal.visor.defragmentation;
-import java.util.Collections;
import java.util.List;
-import java.util.Optional;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteException;
import org.apache.ignite.compute.ComputeJobResult;
-import org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager;
-import org.apache.ignite.internal.processors.cache.persistence.IgniteCacheDatabaseSharedManager;
-import org.apache.ignite.internal.processors.cache.persistence.defragmentation.CachePartitionDefragmentationManager;
+import org.apache.ignite.internal.processors.cache.persistence.defragmentation.IgniteDefragmentation;
import org.apache.ignite.internal.processors.task.GridInternal;
import org.apache.ignite.internal.processors.task.GridVisorManagementTask;
import org.apache.ignite.internal.visor.VisorJob;
import org.apache.ignite.internal.visor.VisorMultiNodeTask;
-import org.apache.ignite.maintenance.MaintenanceAction;
-import org.apache.ignite.maintenance.MaintenanceRegistry;
-import org.apache.ignite.maintenance.MaintenanceTask;
import org.jetbrains.annotations.Nullable;
-import static org.apache.ignite.internal.processors.cache.persistence.defragmentation.CachePartitionDefragmentationManager.DEFRAGMENTATION_MNTC_TASK_NAME;
-import static org.apache.ignite.internal.processors.cache.persistence.defragmentation.maintenance.DefragmentationParameters.toStore;
-
/** */
@GridInternal
@GridVisorManagementTask
@@ -120,91 +110,71 @@ protected VisorDefragmentationJob(@Nullable VisorDefragmentationTaskArg arg, boo
/** */
private VisorDefragmentationTaskResult runSchedule(VisorDefragmentationTaskArg arg) {
- MaintenanceRegistry mntcReg = ignite.context().maintenanceRegistry();
+ final IgniteDefragmentation defragmentation = ignite.context().defragmentation();
- MaintenanceTask oldTask;
+ final IgniteDefragmentation.ScheduleResult scheduleResult;
try {
- List cacheNames = arg.cacheNames();
-
- oldTask = mntcReg.registerMaintenanceTask(toStore(cacheNames == null ? Collections.emptyList() : cacheNames));
+ scheduleResult = defragmentation.schedule(arg.cacheNames());
}
catch (IgniteCheckedException e) {
- return new VisorDefragmentationTaskResult(false, "Scheduling failed: " + e.getMessage());
+ return new VisorDefragmentationTaskResult(false, e.getMessage());
}
- return new VisorDefragmentationTaskResult(
- true,
- "Scheduling completed successfully." +
- (oldTask == null ? "" : " Previously scheduled task has been removed.")
- );
+ String message;
+
+ switch (scheduleResult) {
+ case SUCCESS_SUPERSEDED_PREVIOUS:
+ message = "Scheduling completed successfully. Previously scheduled task has been removed.";
+ break;
+ case SUCCESS:
+ default:
+ message = "Scheduling completed successfully.";
+ break;
+ }
+
+ return new VisorDefragmentationTaskResult(true, message);
}
/** */
private VisorDefragmentationTaskResult runStatus(VisorDefragmentationTaskArg arg) {
- MaintenanceRegistry mntcReg = ignite.context().maintenanceRegistry();
-
- if (!mntcReg.isMaintenanceMode())
- return new VisorDefragmentationTaskResult(false, "Node is not in maintenance node.");
-
- IgniteCacheDatabaseSharedManager dbMgr = ignite.context().cache().context().database();
+ final IgniteDefragmentation defragmentation = ignite.context().defragmentation();
- assert dbMgr instanceof GridCacheDatabaseSharedManager;
-
- CachePartitionDefragmentationManager defrgMgr = ((GridCacheDatabaseSharedManager)dbMgr)
- .defragmentationManager();
-
- if (defrgMgr == null)
- return new VisorDefragmentationTaskResult(true, "There's no active defragmentation process on the node.");
-
- return new VisorDefragmentationTaskResult(true, defrgMgr.status());
+ try {
+ return new VisorDefragmentationTaskResult(true, defragmentation.status().toString());
+ } catch (IgniteCheckedException e) {
+ return new VisorDefragmentationTaskResult(false, e.getMessage());
+ }
}
/** */
private VisorDefragmentationTaskResult runCancel(VisorDefragmentationTaskArg arg) {
- assert arg.cacheNames() == null : "Cancelling specific caches is not yet implemented";
-
- MaintenanceRegistry mntcReg = ignite.context().maintenanceRegistry();
-
- if (!mntcReg.isMaintenanceMode()) {
- boolean deleted = mntcReg.unregisterMaintenanceTask(DEFRAGMENTATION_MNTC_TASK_NAME);
+ final IgniteDefragmentation defragmentation = ignite.context().defragmentation();
- String msg = deleted
- ? "Scheduled defragmentation task cancelled successfully."
- : "Scheduled defragmentation task is not found.";
-
- return new VisorDefragmentationTaskResult(true, msg);
- }
- else {
- List> actions;
-
- try {
- actions = mntcReg.actionsForMaintenanceTask(DEFRAGMENTATION_MNTC_TASK_NAME);
- }
- catch (IgniteException e) {
- return new VisorDefragmentationTaskResult(true, "Defragmentation is already completed or has been cancelled previously.");
+ try {
+ final IgniteDefragmentation.CancelResult cancelResult = defragmentation.cancel();
+
+ String message;
+
+ switch (cancelResult) {
+ case SCHEDULED_NOT_FOUND:
+ message = "Scheduled defragmentation task is not found.";
+ break;
+ case CANCELLED:
+ message = "Defragmentation cancelled successfully.";
+ break;
+ case COMPLETED_OR_CANCELLED:
+ message = "Defragmentation is already completed or has been cancelled previously.";
+ break;
+ case CANCELLED_SCHEDULED:
+ default:
+ message = "Scheduled defragmentation task cancelled successfully.";
+ break;
}
- Optional> stopAct = actions.stream().filter(a -> "stop".equals(a.name())).findAny();
-
- assert stopAct.isPresent();
-
- try {
- Object res = stopAct.get().execute();
-
- assert res instanceof Boolean;
-
- boolean cancelled = (Boolean)res;
-
- String msg = cancelled
- ? "Defragmentation cancelled successfully."
- : "Defragmentation is already completed or has been cancelled previously.";
-
- return new VisorDefragmentationTaskResult(true, msg);
- }
- catch (Exception e) {
- return new VisorDefragmentationTaskResult(false, "Exception occurred: " + e.getMessage());
- }
+ return new VisorDefragmentationTaskResult(true, message);
+ } catch (IgniteCheckedException e) {
+ return new VisorDefragmentationTaskResult(false, e.getMessage());
}
}
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/defragmentation/VisorDefragmentationTaskArg.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/defragmentation/VisorDefragmentationTaskArg.java
index 1b1c8b12ba023..9e6ec53f5e48b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/defragmentation/VisorDefragmentationTaskArg.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/defragmentation/VisorDefragmentationTaskArg.java
@@ -33,9 +33,6 @@ public class VisorDefragmentationTaskArg extends IgniteDataTransferObject {
/** */
private VisorDefragmentationOperation operation;
- /** */
- private List nodeIds;
-
/** */
private List cacheNames;
@@ -47,12 +44,10 @@ public VisorDefragmentationTaskArg() {
/** */
public VisorDefragmentationTaskArg(
VisorDefragmentationOperation operation,
- List nodeIds,
List cacheNames
) {
this.operation = operation;
- this.nodeIds = nodeIds;
this.cacheNames = cacheNames;
}
@@ -61,11 +56,6 @@ public VisorDefragmentationOperation operation() {
return operation;
}
- /** */
- public List nodeIds() {
- return nodeIds;
- }
-
/** */
public List cacheNames() {
return cacheNames;
@@ -75,8 +65,6 @@ public List cacheNames() {
@Override protected void writeExternalData(ObjectOutput out) throws IOException {
U.writeEnum(out, operation);
- U.writeCollection(out, nodeIds);
-
U.writeCollection(out, cacheNames);
}
@@ -84,8 +72,6 @@ public List cacheNames() {
@Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException, ClassNotFoundException {
operation = U.readEnum(in, VisorDefragmentationOperation.class);
- nodeIds = U.readList(in);
-
cacheNames = U.readList(in);
}
}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/misc/VisorWalTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/misc/VisorWalTask.java
index 19b3e921fdf83..d535751c36f9d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/misc/VisorWalTask.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/misc/VisorWalTask.java
@@ -237,7 +237,7 @@ Collection deleteUnusedWalSegments(
dbMgr.onWalTruncated(lowBoundForTruncate);
- int num = wal.truncate(null, lowBoundForTruncate);
+ int num = wal.truncate(lowBoundForTruncate);
if (walFiles != null) {
sortWalFiles(walFiles);
diff --git a/modules/core/src/main/java/org/apache/ignite/mxbean/DefragmentationMXBean.java b/modules/core/src/main/java/org/apache/ignite/mxbean/DefragmentationMXBean.java
new file mode 100644
index 0000000000000..22a5e2de9c39a
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/mxbean/DefragmentationMXBean.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.mxbean;
+
+/**
+ * JMX bean for defragmentation manager.
+ */
+@MXBeanDescription("MBean that provides access for defragmentation features.")
+public interface DefragmentationMXBean {
+ /**
+ * Schedule defragmentation for given caches.
+ *
+ * @param cacheNames Names of caches to run defragmentation on, comma separated.
+ * @return {@code true} if defragmentation is scheduled, {@code false} otherwise.
+ */
+ @MXBeanDescription("Schedule defragmentation.")
+ public boolean schedule(@MXBeanParameter(name = "cacheNames", description = "Names of caches to run defragmentation on.") String cacheNames);
+
+ /**
+ * Cancel defragmentation.
+ *
+ * @return {@code true} if defragmentation was canceled, {@code false} otherwise.
+ */
+ @MXBeanDescription("Cancel current defragmentation.")
+ public boolean cancel();
+
+ /**
+ * Get defragmentation status.
+ *
+ * @return {@code true} if defragmentation is in progress right now.
+ */
+ @MXBeanDescription("Cancel current defragmentation.")
+ public boolean inProgress();
+
+ /**
+ * Get count of processed partitions.
+ *
+ * @return {@code true} if defragmentation is in progress right now.
+ */
+ @MXBeanDescription("Processed partitions.")
+ public int processedPartitions();
+
+ /**
+ * Get total count of partitions.
+ *
+ * @return {@code true} if defragmentation is in progress right now.
+ */
+ @MXBeanDescription("Total partitions.")
+ public int totalPartitions();
+
+ /**
+ * Get defragmentation's start time.
+ *
+ * @return {@code true} if defragmentation is in progress right now.
+ */
+ @MXBeanDescription("Start time.")
+ public long startTime();
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/TestStorageUtils.java b/modules/core/src/test/java/org/apache/ignite/TestStorageUtils.java
new file mode 100644
index 0000000000000..17ff24197d0c8
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/TestStorageUtils.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite;
+
+import org.apache.ignite.binary.BinaryObject;
+import org.apache.ignite.cache.CacheEntry;
+import org.apache.ignite.internal.pagemem.wal.record.DataEntry;
+import org.apache.ignite.internal.processors.cache.CacheObjectImpl;
+import org.apache.ignite.internal.processors.cache.GridCacheContext;
+import org.apache.ignite.internal.processors.cache.GridCacheOperation;
+import org.apache.ignite.internal.processors.cache.KeyCacheObject;
+import org.apache.ignite.internal.processors.cache.KeyCacheObjectImpl;
+import org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager;
+import org.apache.ignite.internal.processors.cache.persistence.IgniteCacheDatabaseSharedManager;
+import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ * Test methods for storage manipulation.
+ */
+public class TestStorageUtils {
+ /**
+ * Corrupts data entry.
+ *
+ * @param ctx Context.
+ * @param key Key.
+ * @param breakCntr Break counter.
+ * @param breakData Break data.
+ * @param ver GridCacheVersion to use.
+ * @param brokenValPostfix Postfix to add to value if breakData flag is set to true.
+ */
+ public static void corruptDataEntry(
+ GridCacheContext, ?> ctx,
+ Object key,
+ boolean breakCntr,
+ boolean breakData,
+ GridCacheVersion ver,
+ String brokenValPostfix
+ ) {
+ int partId = ctx.affinity().partition(key);
+
+ try {
+ long updateCntr = ctx.topology().localPartition(partId).updateCounter();
+
+ CacheEntry