This is an automated email from the ASF dual-hosted git repository.
tkalkirill pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new c454736a3b IGNITE-19290 Removing garbage from the partition, taking
into account the safe time (#1952)
c454736a3b is described below
commit c454736a3b361e135d7300c6c83f6069a3e2de1e
Author: Kirill Tkalenko <[email protected]>
AuthorDate: Tue May 2 13:32:45 2023 +0300
IGNITE-19290 Removing garbage from the partition, taking into account the
safe time (#1952)
---
.../ignite/distributed/ItTablePersistenceTest.java | 11 +-
.../distributed/ItTxDistributedTestSingleNode.java | 15 +-
.../internal/table/distributed/LowWatermark.java | 2 +-
.../table/distributed/StorageUpdateHandler.java | 96 +++-------
.../internal/table/distributed/TableManager.java | 8 +-
.../ignite/internal/table/distributed/gc/MvGc.java | 73 +++++---
.../AbstractMvStorageUpdateHandlerTest.java | 6 +-
.../internal/table/distributed/IndexBaseTest.java | 6 +-
.../PartitionGcOnWriteConcurrentTest.java | 167 -----------------
.../table/distributed/PartitionGcOnWriteTest.java | 197 ---------------------
.../distributed/StorageUpdateHandlerTest.java | 128 ++++++++++++-
.../internal/table/distributed/gc/MvGcTest.java | 50 +++++-
.../raft/PartitionCommandListenerTest.java | 11 +-
.../PartitionReplicaListenerIndexLockingTest.java | 9 +-
.../replication/PartitionReplicaListenerTest.java | 5 +-
.../table/impl/DummyInternalTableImpl.java | 10 +-
16 files changed, 314 insertions(+), 480 deletions(-)
diff --git
a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTablePersistenceTest.java
b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTablePersistenceTest.java
index a996d2d73d..df4562b1c7 100644
---
a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTablePersistenceTest.java
+++
b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTablePersistenceTest.java
@@ -69,6 +69,7 @@ import
org.apache.ignite.internal.storage.engine.MvTableStorage;
import org.apache.ignite.internal.storage.rocksdb.RocksDbStorageEngine;
import
org.apache.ignite.internal.storage.rocksdb.configuration.schema.RocksDbStorageEngineConfiguration;
import org.apache.ignite.internal.table.InternalTable;
+import org.apache.ignite.internal.table.distributed.LowWatermark;
import org.apache.ignite.internal.table.distributed.StorageUpdateHandler;
import org.apache.ignite.internal.table.distributed.TableMessagesFactory;
import org.apache.ignite.internal.table.distributed.command.FinishTxCommand;
@@ -376,18 +377,24 @@ public class ItTablePersistenceTest extends
ItAbstractListenerSnapshotTest<Parti
PartitionDataStorage partitionDataStorage = new
TestPartitionDataStorage(mvPartitionStorage);
+ PendingComparableValuesTracker<HybridTimestamp> safeTime =
new PendingComparableValuesTracker<>(
+ new HybridTimestamp(1, 0)
+ );
+
StorageUpdateHandler storageUpdateHandler = new
StorageUpdateHandler(
0,
partitionDataStorage,
DummyInternalTableImpl.createTableIndexStoragesSupplier(Map.of()),
- zoneCfg.dataStorage()
+ zoneCfg.dataStorage(),
+ safeTime,
+ mock(LowWatermark.class)
);
PartitionListener listener = new PartitionListener(
partitionDataStorage,
storageUpdateHandler,
new TestTxStateStorage(),
- new PendingComparableValuesTracker<>(new
HybridTimestamp(1, 0)),
+ safeTime,
new PendingComparableValuesTracker<>(0L)
);
diff --git
a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTxDistributedTestSingleNode.java
b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTxDistributedTestSingleNode.java
index 933dd1d29d..215d04fb91 100644
---
a/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTxDistributedTestSingleNode.java
+++
b/modules/table/src/integrationTest/java/org/apache/ignite/distributed/ItTxDistributedTestSingleNode.java
@@ -27,6 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
+import static org.mockito.Mockito.mock;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.ArrayList;
@@ -81,6 +82,7 @@ import org.apache.ignite.internal.table.TableImpl;
import org.apache.ignite.internal.table.TxAbstractTest;
import org.apache.ignite.internal.table.distributed.HashIndexLocker;
import org.apache.ignite.internal.table.distributed.IndexLocker;
+import org.apache.ignite.internal.table.distributed.LowWatermark;
import org.apache.ignite.internal.table.distributed.StorageUpdateHandler;
import org.apache.ignite.internal.table.distributed.TableMessageGroup;
import
org.apache.ignite.internal.table.distributed.TableSchemaAwareIndexStorage;
@@ -123,7 +125,6 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mockito;
/**
* Distributed transaction test using a single partition table.
@@ -359,8 +360,8 @@ public class ItTxDistributedTestSingleNode extends
TxAbstractTest {
1,
consistentIdToNode,
clientTxManager,
- Mockito.mock(MvTableStorage.class),
- Mockito.mock(TxStateTableStorage.class),
+ mock(MvTableStorage.class),
+ mock(TxStateTableStorage.class),
startClient() ? clientReplicaSvc :
replicaServices.get(localNodeName),
startClient() ? clientClock : clocks.get(localNodeName)
), new DummySchemaManagerImpl(ACCOUNTS_SCHEMA),
clientTxManager.lockManager());
@@ -372,8 +373,8 @@ public class ItTxDistributedTestSingleNode extends
TxAbstractTest {
1,
consistentIdToNode,
clientTxManager,
- Mockito.mock(MvTableStorage.class),
- Mockito.mock(TxStateTableStorage.class),
+ mock(MvTableStorage.class),
+ mock(TxStateTableStorage.class),
startClient() ? clientReplicaSvc :
replicaServices.get(localNodeName),
startClient() ? clientClock : clocks.get(localNodeName)
), new DummySchemaManagerImpl(CUSTOMERS_SCHEMA),
clientTxManager.lockManager());
@@ -447,7 +448,9 @@ public class ItTxDistributedTestSingleNode extends
TxAbstractTest {
partId,
partitionDataStorage,
DummyInternalTableImpl.createTableIndexStoragesSupplier(Map.of(pkStorage.get().id(),
pkStorage.get())),
- dsCfg
+ dsCfg,
+ safeTime,
+ mock(LowWatermark.class)
);
TopologyAwareRaftGroupServiceFactory
topologyAwareRaftGroupServiceFactory = new TopologyAwareRaftGroupServiceFactory(
diff --git
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/LowWatermark.java
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/LowWatermark.java
index 6fbf6609da..b84771e89a 100644
---
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/LowWatermark.java
+++
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/LowWatermark.java
@@ -148,7 +148,7 @@ public class LowWatermark implements ManuallyCloseable {
}
@Override
- public void close() throws Exception {
+ public void close() {
if (!closeGuard.compareAndSet(false, true)) {
return;
}
diff --git
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/StorageUpdateHandler.java
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/StorageUpdateHandler.java
index 102ebacd3c..159859cf47 100644
---
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/StorageUpdateHandler.java
+++
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/StorageUpdateHandler.java
@@ -26,7 +26,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
-import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import org.apache.ignite.internal.hlc.HybridTimestamp;
import org.apache.ignite.internal.schema.BinaryRow;
@@ -41,6 +40,7 @@ import org.apache.ignite.internal.storage.RowId;
import org.apache.ignite.internal.table.distributed.raft.PartitionDataStorage;
import
org.apache.ignite.internal.table.distributed.replicator.TablePartitionId;
import org.apache.ignite.internal.util.Cursor;
+import org.apache.ignite.internal.util.PendingComparableValuesTracker;
import org.jetbrains.annotations.Nullable;
/**
@@ -55,12 +55,14 @@ public class StorageUpdateHandler {
private final TableIndexStoragesSupplier indexes;
- /** Last recorded GC low watermark. */
- private final AtomicReference<HybridTimestamp> lastRecordedLwm = new
AtomicReference<>();
-
- /** Data storage configuration. */
private final DataStorageConfiguration dsCfg;
+ /** Partition safe time tracker. */
+ private final PendingComparableValuesTracker<HybridTimestamp>
safeTimeTracker;
+
+ /** Low watermark. */
+ private final LowWatermark lowWatermark;
+
/**
* The constructor.
*
@@ -68,17 +70,22 @@ public class StorageUpdateHandler {
* @param storage Partition data storage.
* @param indexes Indexes supplier.
* @param dsCfg Data storage configuration.
+ * @param safeTimeTracker Partition safe time tracker.
*/
public StorageUpdateHandler(
int partitionId,
PartitionDataStorage storage,
TableIndexStoragesSupplier indexes,
- DataStorageConfiguration dsCfg
+ DataStorageConfiguration dsCfg,
+ PendingComparableValuesTracker<HybridTimestamp> safeTimeTracker,
+ LowWatermark lowWatermark
) {
this.partitionId = partitionId;
this.storage = storage;
this.indexes = indexes;
this.dsCfg = dsCfg;
+ this.safeTimeTracker = safeTimeTracker;
+ this.lowWatermark = lowWatermark;
}
/**
@@ -96,28 +103,6 @@ public class StorageUpdateHandler {
TablePartitionId commitPartitionId,
@Nullable ByteBuffer rowBuffer,
@Nullable Consumer<RowId> onReplication
- ) {
- handleUpdate(txId, rowUuid, commitPartitionId, rowBuffer,
onReplication, null);
- }
-
- /**
- * Handles single update.
- *
- * @param txId Transaction id.
- * @param rowUuid Row UUID.
- * @param commitPartitionId Commit partition id.
- * @param rowBuffer Row buffer.
- * @param onReplication Callback on replication.
- * @param lowWatermark GC low watermark.
- */
- // TODO: https://issues.apache.org/jira/browse/IGNITE-18909 Pass low
watermark.
- public void handleUpdate(
- UUID txId,
- UUID rowUuid,
- TablePartitionId commitPartitionId,
- @Nullable ByteBuffer rowBuffer,
- @Nullable Consumer<RowId> onReplication,
- @Nullable HybridTimestamp lowWatermark
) {
storage.runConsistently(() -> {
BinaryRow row = rowBuffer != null ? new ByteBufferRow(rowBuffer) :
null;
@@ -141,24 +126,7 @@ public class StorageUpdateHandler {
return null;
});
- executeBatchGc(lowWatermark);
- }
-
- /**
- * Handles multiple updates.
- *
- * @param txId Transaction id.
- * @param rowsToUpdate Collection of rows to update.
- * @param commitPartitionId Commit partition id.
- * @param onReplication On replication callback.
- */
- public void handleUpdateAll(
- UUID txId,
- Map<UUID, ByteBuffer> rowsToUpdate,
- TablePartitionId commitPartitionId,
- @Nullable Consumer<Collection<RowId>> onReplication
- ) {
- handleUpdateAll(txId, rowsToUpdate, commitPartitionId, onReplication,
null);
+ executeBatchGc();
}
/**
@@ -168,15 +136,12 @@ public class StorageUpdateHandler {
* @param rowsToUpdate Collection of rows to update.
* @param commitPartitionId Commit partition id.
* @param onReplication On replication callback.
- * @param lowWatermark GC low watermark.
*/
- // TODO: https://issues.apache.org/jira/browse/IGNITE-18909 Pass low
watermark.
public void handleUpdateAll(
UUID txId,
Map<UUID, ByteBuffer> rowsToUpdate,
TablePartitionId commitPartitionId,
- @Nullable Consumer<Collection<RowId>> onReplication,
- @Nullable HybridTimestamp lowWatermark
+ @Nullable Consumer<Collection<RowId>> onReplication
) {
storage.runConsistently(() -> {
UUID commitTblId = commitPartitionId.tableId();
@@ -208,29 +173,17 @@ public class StorageUpdateHandler {
return null;
});
- executeBatchGc(lowWatermark);
+ executeBatchGc();
}
- private void executeBatchGc(@Nullable HybridTimestamp newLwm) {
- if (newLwm == null) {
+ void executeBatchGc() {
+ HybridTimestamp lwm = lowWatermark.getLowWatermark();
+
+ if (lwm == null || safeTimeTracker.current().compareTo(lwm) < 0) {
return;
}
- @Nullable HybridTimestamp oldLwm;
- do {
- oldLwm = lastRecordedLwm.get();
-
- if (oldLwm != null && newLwm.compareTo(oldLwm) <= 0) {
- break;
- }
- } while (!lastRecordedLwm.compareAndSet(oldLwm, newLwm));
-
- if (oldLwm == null || newLwm.compareTo(oldLwm) > 0) {
- // Iff the lwm we have is the new lwm.
- // Otherwise our newLwm is either smaller than last recorded lwm
or last recorded lwm has changed
- // concurrently and it become greater. If that's the case, another
thread will perform the GC.
- vacuumBatch(newLwm, dsCfg.gcOnUpdateBatchSize().value());
- }
+ vacuumBatch(lwm, dsCfg.gcOnUpdateBatchSize().value());
}
/**
@@ -447,4 +400,11 @@ public class StorageUpdateHandler {
index.storage().setNextRowIdToBuild(finish ? null :
lastRowId.increment());
}
+
+ /**
+ * Returns the partition safe time tracker.
+ */
+ public PendingComparableValuesTracker<HybridTimestamp>
getSafeTimeTracker() {
+ return safeTimeTracker;
+ }
}
diff --git
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java
index e5480730c3..c7a89bba12 100644
---
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java
+++
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/TableManager.java
@@ -742,7 +742,9 @@ public class TableManager extends Producer<TableEvent,
TableEventParameters> imp
partId,
storage,
table.indexStorageAdapters(partId),
- dsCfg
+ dsCfg,
+ safeTimeTracker,
+ lowWatermark
);
mvGc.addStorage(replicaGrpId,
storageUpdateHandler);
@@ -2063,7 +2065,9 @@ public class TableManager extends Producer<TableEvent,
TableEventParameters> imp
partId,
partitionDataStorage,
tbl.indexStorageAdapters(partId),
- dstZoneCfg.dataStorage()
+ dstZoneCfg.dataStorage(),
+ safeTimeTracker,
+ lowWatermark
);
RaftGroupOptions groupOptions =
groupOptionsForPartition(
diff --git
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/gc/MvGc.java
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/gc/MvGc.java
index a29f4e6d36..4686233c18 100644
---
a/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/gc/MvGc.java
+++
b/modules/table/src/main/java/org/apache/ignite/internal/table/distributed/gc/MvGc.java
@@ -41,6 +41,7 @@ import
org.apache.ignite.internal.table.distributed.StorageUpdateHandler;
import
org.apache.ignite.internal.table.distributed.replicator.TablePartitionId;
import org.apache.ignite.internal.thread.NamedThreadFactory;
import org.apache.ignite.internal.util.IgniteSpinBusyLock;
+import org.apache.ignite.internal.util.TrackerClosedException;
import org.apache.ignite.lang.ErrorGroups.GarbageCollector;
import org.apache.ignite.lang.IgniteInternalException;
import org.jetbrains.annotations.TestOnly;
@@ -194,8 +195,8 @@ public class MvGc implements ManuallyCloseable {
}
private void scheduleGcForStorage(TablePartitionId tablePartitionId) {
- executor.submit(() -> inBusyLock(() -> {
- CompletableFuture<Void> future = new CompletableFuture<>();
+ inBusyLock(() -> {
+ CompletableFuture<Void> currentGcFuture = new
CompletableFuture<>();
GcStorageHandler storageHandler =
storageHandlerByPartitionId.compute(tablePartitionId, (tablePartId,
gcStorageHandler) -> {
if (gcStorageHandler == null) {
@@ -206,7 +207,7 @@ public class MvGc implements ManuallyCloseable {
CompletableFuture<Void> inProgressFuture =
gcStorageHandler.gcInProgressFuture.get();
if (inProgressFuture == null || inProgressFuture.isDone()) {
- boolean casResult =
gcStorageHandler.gcInProgressFuture.compareAndSet(inProgressFuture, future);
+ boolean casResult =
gcStorageHandler.gcInProgressFuture.compareAndSet(inProgressFuture,
currentGcFuture);
assert casResult : tablePartId;
} else {
@@ -221,35 +222,63 @@ public class MvGc implements ManuallyCloseable {
return;
}
- if (storageHandler.gcInProgressFuture.get() != future) {
+ if (storageHandler.gcInProgressFuture.get() != currentGcFuture) {
// Someone in parallel is already collecting garbage, we will
try once again after completion of gcInProgressFuture.
return;
}
try {
- for (int i = 0; i < GC_BATCH_SIZE; i++) {
- HybridTimestamp lowWatermark = lowWatermarkReference.get();
+ HybridTimestamp lowWatermark = lowWatermarkReference.get();
- assert lowWatermark != null : tablePartitionId;
+ assert lowWatermark != null : tablePartitionId;
- // If storage has been deleted or there is no garbage,
then for now we will stop collecting garbage for this storage.
- if
(!storageHandlerByPartitionId.containsKey(tablePartitionId)
- ||
!storageHandler.storageUpdateHandler.vacuum(lowWatermark)) {
- return;
- }
- }
- } catch (Throwable t) {
- future.completeExceptionally(t);
+ // If the storage has been deleted, then garbage collection is
no longer necessary.
+ if
(!storageHandlerByPartitionId.containsKey(tablePartitionId)) {
+ currentGcFuture.complete(null);
- return;
- } finally {
- if (!future.isCompletedExceptionally()) {
- future.complete(null);
+ return;
}
- }
- scheduleGcForStorage(tablePartitionId);
- }));
+ StorageUpdateHandler storageUpdateHandler =
storageHandler.storageUpdateHandler;
+
+ // We can only start garbage collection when the partition
safe time is reached.
+ storageUpdateHandler.getSafeTimeTracker()
+ .waitFor(lowWatermark)
+ .thenApplyAsync(unused -> {
+ for (int i = 0; i < GC_BATCH_SIZE; i++) {
+ // If the storage has been deleted or there is
no garbage, then we will stop.
+ if
(!storageHandlerByPartitionId.containsKey(tablePartitionId)
+ ||
!storageUpdateHandler.vacuum(lowWatermark)) {
+ return false;
+ }
+ }
+
+ return true;
+ }, executor)
+ .whenComplete((isGarbageLeft, throwable) -> {
+ if (throwable != null) {
+ if (throwable instanceof TrackerClosedException
+ || throwable.getCause() instanceof
TrackerClosedException) {
+ currentGcFuture.complete(null);
+ } else {
+
currentGcFuture.completeExceptionally(throwable);
+ }
+
+ return;
+ }
+
+ currentGcFuture.complete(null);
+
+ // If there is garbage left and the storage has
not been deleted, then we will schedule the next garbage
+ // collection.
+ if (isGarbageLeft &&
storageHandlerByPartitionId.containsKey(tablePartitionId)) {
+ scheduleGcForStorage(tablePartitionId);
+ }
+ });
+ } catch (Throwable t) {
+ currentGcFuture.completeExceptionally(t);
+ }
+ });
}
private <T> T inBusyLock(Supplier<T> supplier) {
diff --git
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/AbstractMvStorageUpdateHandlerTest.java
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/AbstractMvStorageUpdateHandlerTest.java
index 8f060b55fe..dbae8274c0 100644
---
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/AbstractMvStorageUpdateHandlerTest.java
+++
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/AbstractMvStorageUpdateHandlerTest.java
@@ -21,6 +21,7 @@ import static
org.apache.ignite.internal.testframework.IgniteTestUtils.runRace;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.Mockito.mock;
import java.util.Map;
import java.util.UUID;
@@ -40,6 +41,7 @@ import
org.apache.ignite.internal.storage.engine.StorageEngine;
import org.apache.ignite.internal.table.distributed.raft.PartitionDataStorage;
import org.apache.ignite.internal.table.impl.DummyInternalTableImpl;
import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.internal.util.PendingComparableValuesTracker;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -95,7 +97,9 @@ abstract class AbstractMvStorageUpdateHandlerTest extends
BaseMvStoragesTest {
PARTITION_ID,
partitionDataStorage,
DummyInternalTableImpl.createTableIndexStoragesSupplier(Map.of()),
- distributionZoneConfig.dataStorage()
+ distributionZoneConfig.dataStorage(),
+ new
PendingComparableValuesTracker<>(HybridTimestamp.MAX_VALUE),
+ mock(LowWatermark.class)
);
}
diff --git
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/IndexBaseTest.java
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/IndexBaseTest.java
index fbb24b636c..66cce1a5c0 100644
---
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/IndexBaseTest.java
+++
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/IndexBaseTest.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.table.distributed;
import static java.util.Collections.singletonMap;
import static java.util.stream.Collectors.toList;
+import static org.mockito.Mockito.mock;
import java.util.List;
import java.util.Map;
@@ -46,6 +47,7 @@ import
org.apache.ignite.internal.storage.index.impl.TestSortedIndexStorage;
import
org.apache.ignite.internal.table.distributed.replicator.TablePartitionId;
import org.apache.ignite.internal.table.impl.DummyInternalTableImpl;
import org.apache.ignite.internal.util.Cursor;
+import org.apache.ignite.internal.util.PendingComparableValuesTracker;
import org.jetbrains.annotations.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -129,7 +131,9 @@ public abstract class IndexBaseTest extends
BaseMvStoragesTest {
PARTITION_ID,
new TestPartitionDataStorage(storage),
DummyInternalTableImpl.createTableIndexStoragesSupplier(indexes),
- dsCfg
+ dsCfg,
+ new
PendingComparableValuesTracker<>(HybridTimestamp.MAX_VALUE),
+ mock(LowWatermark.class)
);
}
diff --git
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/PartitionGcOnWriteConcurrentTest.java
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/PartitionGcOnWriteConcurrentTest.java
deleted file mode 100644
index c782bb327e..0000000000
---
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/PartitionGcOnWriteConcurrentTest.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * 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.table.distributed;
-
-import static org.apache.ignite.internal.testframework.IgniteTestUtils.runRace;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.atMostOnce;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.nio.ByteBuffer;
-import java.util.Collections;
-import java.util.Map;
-import java.util.UUID;
-import java.util.function.Consumer;
-import org.apache.ignite.distributed.TestPartitionDataStorage;
-import
org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
-import
org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
-import org.apache.ignite.internal.hlc.HybridClock;
-import org.apache.ignite.internal.hlc.HybridClockImpl;
-import org.apache.ignite.internal.hlc.HybridTimestamp;
-import org.apache.ignite.internal.schema.ByteBufferRow;
-import
org.apache.ignite.internal.schema.configuration.storage.DataStorageConfiguration;
-import org.apache.ignite.internal.storage.MvPartitionStorage;
-import org.apache.ignite.internal.storage.MvPartitionStorage.WriteClosure;
-import
org.apache.ignite.internal.table.distributed.replicator.TablePartitionId;
-import org.apache.ignite.internal.table.impl.DummyInternalTableImpl;
-import org.apache.ignite.internal.testframework.IgniteTestUtils.RunnableX;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.EnumSource;
-
-/** Tests for concurrent cooperative GC (GC that is executed on write). */
-@ExtendWith(ConfigurationExtension.class)
-public class PartitionGcOnWriteConcurrentTest {
- private static final int PARTITION_ID = 1;
- private static final TablePartitionId TABLE_PARTITION_ID = new
TablePartitionId(UUID.randomUUID(), PARTITION_ID);
- private static final HybridClock CLOCK = new HybridClockImpl();
-
- private MvPartitionStorage storage;
- private StorageUpdateHandler storageUpdateHandler;
-
- @BeforeEach
- void setUp(@InjectConfiguration DataStorageConfiguration dsCfg) {
- storage = mock(MvPartitionStorage.class);
- doAnswer(invocation -> {
- WriteClosure<?> cls = invocation.getArgument(0);
-
- return cls.execute();
- }).when(storage).runConsistently(any());
-
- when(storage.pollForVacuum(any())).thenReturn(null);
-
- storageUpdateHandler = new StorageUpdateHandler(
- PARTITION_ID,
- new TestPartitionDataStorage(storage),
-
DummyInternalTableImpl.createTableIndexStoragesSupplier(Map.of()),
- dsCfg
- );
- }
-
- @ParameterizedTest
- @EnumSource(UpdateType.class)
- void testSameLwm(UpdateType updateType) {
- HybridTimestamp lwm = CLOCK.now();
-
- runRace(createRunnable(updateType, lwm), createRunnable(updateType,
lwm));
-
- verify(storage, times(1)).pollForVacuum(lwm);
- }
-
- @ParameterizedTest
- @EnumSource(UpdateType.class)
- void testDifferentLwm(UpdateType updateType) {
- int count = 10;
-
- HybridTimestamp[] timestamps = new HybridTimestamp[count];
-
- RunnableX[] runnables = new RunnableX[count];
-
- for (int i = 0; i < count; i++) {
- HybridTimestamp ts = CLOCK.now();
-
- timestamps[i] = ts;
-
- runnables[i] = createRunnable(updateType, ts);
- }
-
- runRace(runnables);
-
- for (int i = 0; i < count - 1; i++) {
- verify(storage, atMostOnce()).pollForVacuum(timestamps[i]);
- }
-
- verify(storage, times(1)).pollForVacuum(timestamps[count - 1]);
- }
-
- @ParameterizedTest
- @EnumSource(UpdateType.class)
- void testDifferentLwmWithPreviousVacuums(UpdateType updateType) throws
Throwable {
- HybridTimestamp lwm1 = CLOCK.now();
- HybridTimestamp lwm2 = CLOCK.now();
- HybridTimestamp lwm3 = CLOCK.now();
-
- createRunnable(updateType, lwm1).run();
-
- runRace(createRunnable(updateType, lwm2), createRunnable(updateType,
lwm3));
-
- verify(storage, times(1)).pollForVacuum(lwm1);
- verify(storage, atMostOnce()).pollForVacuum(lwm2);
- verify(storage, times(1)).pollForVacuum(lwm3);
- }
-
- private RunnableX createRunnable(UpdateType updateType, HybridTimestamp
lwm) {
- if (updateType == UpdateType.UPDATE) {
- //noinspection unchecked
- return () -> storageUpdateHandler.handleUpdate(
- UUID.randomUUID(),
- UUID.randomUUID(),
- TABLE_PARTITION_ID,
- buffer(),
- mock(Consumer.class),
- lwm
- );
- } else {
- //noinspection unchecked
- return () -> storageUpdateHandler.handleUpdateAll(
- UUID.randomUUID(),
- Collections.emptyMap(),
- TABLE_PARTITION_ID,
- mock(Consumer.class),
- lwm
- );
- }
- }
-
- private static ByteBuffer buffer() {
- ByteBuffer buf = mock(ByteBuffer.class);
- when(buf.order()).thenReturn(ByteBufferRow.ORDER);
-
- return buf;
- }
-
- private enum UpdateType {
- UPDATE,
- UPDATE_ALL;
- }
-}
diff --git
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/PartitionGcOnWriteTest.java
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/PartitionGcOnWriteTest.java
deleted file mode 100644
index 39721d97b1..0000000000
---
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/PartitionGcOnWriteTest.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * 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.table.distributed;
-
-import static java.util.Collections.singletonMap;
-import static java.util.stream.Collectors.toList;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import org.apache.ignite.distributed.TestPartitionDataStorage;
-import
org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
-import
org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
-import org.apache.ignite.internal.hlc.HybridTimestamp;
-import org.apache.ignite.internal.schema.BinaryRow;
-import
org.apache.ignite.internal.schema.configuration.storage.DataStorageConfiguration;
-import org.apache.ignite.internal.storage.BaseMvStoragesTest;
-import org.apache.ignite.internal.storage.ReadResult;
-import org.apache.ignite.internal.storage.RowId;
-import org.apache.ignite.internal.storage.impl.TestMvPartitionStorage;
-import
org.apache.ignite.internal.table.distributed.replicator.TablePartitionId;
-import org.apache.ignite.internal.table.impl.DummyInternalTableImpl;
-import org.apache.ignite.internal.util.Cursor;
-import org.jetbrains.annotations.Nullable;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.EnumSource;
-
-/** Tests for cooperative GC (GC that is executed on write). */
-@ExtendWith(ConfigurationExtension.class)
-public class PartitionGcOnWriteTest extends BaseMvStoragesTest {
- private static final int WRITES_COUNT = 10;
- private static final int GC_BATCH_SIZE = 7;
- private static final UUID TX_ID = UUID.randomUUID();
- private static final int PARTITION_ID = 1;
- private static final TablePartitionId TABLE_PARTITION_ID = new
TablePartitionId(UUID.randomUUID(), PARTITION_ID);
-
- private TestMvPartitionStorage storage;
- private StorageUpdateHandler storageUpdateHandler;
-
- @BeforeEach
- void setUp(@InjectConfiguration("mock.gcOnUpdateBatchSize=" +
GC_BATCH_SIZE) DataStorageConfiguration dsCfg) {
- storage = new TestMvPartitionStorage(1);
-
- storageUpdateHandler = new StorageUpdateHandler(
- 1,
- new TestPartitionDataStorage(storage),
-
DummyInternalTableImpl.createTableIndexStoragesSupplier(Map.of()),
- dsCfg
- );
- }
-
- @ParameterizedTest
- @EnumSource(AddWriteWithGc.class)
- void testNullLwm(AddWriteWithGc writer) {
- RowId rowId = fillWithDataForGc();
-
- writeWithGc(writer, null);
-
- assertEquals(WRITES_COUNT, getRowVersions(rowId).size());
- }
-
- @ParameterizedTest
- @EnumSource(AddWriteWithGc.class)
- void testOlderLwm(AddWriteWithGc writer) {
- HybridTimestamp older = clock.now();
-
- HybridTimestamp newer = clock.now();
-
- writeWithGc(writer, newer);
-
- RowId rowId = fillWithDataForGc();
-
- writeWithGc(writer, older);
-
- assertEquals(WRITES_COUNT, getRowVersions(rowId).size());
- }
-
- @ParameterizedTest
- @EnumSource(AddWriteWithGc.class)
- void testNewerLwm(AddWriteWithGc writer) {
- RowId rowId = fillWithDataForGc();
-
- writeWithGc(writer, clock.now());
-
- assertEquals(WRITES_COUNT - GC_BATCH_SIZE,
getRowVersions(rowId).size());
- }
-
- private List<ReadResult> getRowVersions(RowId rowId) {
- try (Cursor<ReadResult> readResults = storage.scanVersions(rowId)) {
- return readResults.stream().collect(toList());
- }
- }
-
- private void writeWithGc(AddWriteWithGc writer, @Nullable HybridTimestamp
lwm) {
- UUID rowUuid = UUID.randomUUID();
-
- TestKey key = new TestKey(1337, "leet");
- BinaryRow row = binaryRow(key, new TestValue(999, "bar"));
-
- writer.addWrite(storageUpdateHandler, rowUuid, row, lwm);
- }
-
- private RowId fillWithDataForGc() {
- UUID rowUuid = UUID.randomUUID();
- var rowId = new RowId(PARTITION_ID, rowUuid);
-
- TestKey key = new TestKey(1, "foo");
-
- for (int i = 0; i < WRITES_COUNT; i++) {
- BinaryRow row = binaryRow(key, new TestValue(i, "bar" + i));
-
- addWrite(storageUpdateHandler, rowUuid, row);
-
- commitWrite(rowId);
- }
-
- return rowId;
- }
-
- private static void addWrite(StorageUpdateHandler handler, UUID rowUuid,
@Nullable BinaryRow row) {
- handler.handleUpdate(
- TX_ID,
- rowUuid,
- TABLE_PARTITION_ID,
- row == null ? null : row.byteBuffer(),
- (unused) -> {}
- );
- }
-
- private void commitWrite(RowId rowId) {
- storage.runConsistently(() -> {
- storage.commitWrite(rowId, clock.now());
-
- return null;
- });
- }
-
- /** Enum that encapsulates update API. */
- enum AddWriteWithGc {
- /** Uses update api. */
- USE_UPDATE {
- @Override
- void addWrite(StorageUpdateHandler handler, TablePartitionId
partitionId, UUID rowUuid, @Nullable BinaryRow row,
- @Nullable HybridTimestamp lwm) {
- handler.handleUpdate(
- TX_ID,
- rowUuid,
- partitionId,
- row == null ? null : row.byteBuffer(),
- (unused) -> {},
- lwm
- );
- }
- },
- /** Uses updateAll api. */
- USE_UPDATE_ALL {
- @Override
- void addWrite(StorageUpdateHandler handler, TablePartitionId
partitionId, UUID rowUuid, @Nullable BinaryRow row,
- @Nullable HybridTimestamp lwm) {
- handler.handleUpdateAll(
- TX_ID,
- singletonMap(rowUuid, row == null ? null :
row.byteBuffer()),
- partitionId,
- (unused) -> {},
- lwm
- );
- }
- };
-
- void addWrite(StorageUpdateHandler handler, UUID rowUuid, @Nullable
BinaryRow row, @Nullable HybridTimestamp lwm) {
- TablePartitionId tablePartitionId = new
TablePartitionId(UUID.randomUUID(), 1);
-
- addWrite(handler, tablePartitionId, rowUuid, row, lwm);
- }
-
- abstract void addWrite(StorageUpdateHandler handler, TablePartitionId
partitionId, UUID rowUuid, @Nullable BinaryRow row,
- @Nullable HybridTimestamp lwm);
- }
-}
diff --git
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/StorageUpdateHandlerTest.java
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/StorageUpdateHandlerTest.java
index 69590f7603..59d43128cf 100644
---
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/StorageUpdateHandlerTest.java
+++
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/StorageUpdateHandlerTest.java
@@ -20,8 +20,13 @@ package org.apache.ignite.internal.table.distributed;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -30,17 +35,25 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.ignite.distributed.TestPartitionDataStorage;
import
org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
import
org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
import org.apache.ignite.internal.hlc.HybridClock;
import org.apache.ignite.internal.hlc.HybridClockImpl;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
import org.apache.ignite.internal.schema.BinaryRow;
import
org.apache.ignite.internal.schema.configuration.storage.DataStorageConfiguration;
+import org.apache.ignite.internal.storage.BinaryRowAndRowId;
import org.apache.ignite.internal.storage.ReadResult;
import org.apache.ignite.internal.storage.RowId;
+import org.apache.ignite.internal.storage.impl.TestMvPartitionStorage;
import org.apache.ignite.internal.storage.index.IndexStorage;
import org.apache.ignite.internal.table.distributed.raft.PartitionDataStorage;
+import
org.apache.ignite.internal.table.distributed.replicator.TablePartitionId;
import org.apache.ignite.internal.util.Cursor;
+import org.apache.ignite.internal.util.CursorUtils;
+import org.apache.ignite.internal.util.PendingComparableValuesTracker;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -56,6 +69,12 @@ public class StorageUpdateHandlerTest {
private final HybridClock clock = new HybridClockImpl();
+ private final PendingComparableValuesTracker<HybridTimestamp>
safeTimeTracker = spy(new PendingComparableValuesTracker<>(
+ new HybridTimestamp(1, 0)
+ ));
+
+ private final LowWatermark lowWatermark = mock(LowWatermark.class);
+
@Test
void testBuildIndex() {
PartitionDataStorage partitionStorage =
mock(PartitionDataStorage.class);
@@ -104,6 +123,107 @@ public class StorageUpdateHandlerTest {
verify(indexes, times(2)).addIndexToWaitIfAbsent(indexId);
}
+ @Test
+ void testVacuum() {
+ PartitionDataStorage partitionStorage = createPartitionDataStorage();
+
+ StorageUpdateHandler storageUpdateHandler =
createStorageUpdateHandler(partitionStorage,
mock(TableIndexStoragesSupplier.class));
+
+ HybridTimestamp lowWatermark = new HybridTimestamp(100, 100);
+
+ assertFalse(storageUpdateHandler.vacuum(lowWatermark));
+ verify(partitionStorage).pollForVacuum(lowWatermark);
+ // Let's check that StorageUpdateHandler#vacuumBatch returns true.
+ clearInvocations(partitionStorage);
+
+ BinaryRowAndRowId binaryRowAndRowId = new
BinaryRowAndRowId(mock(BinaryRow.class), new RowId(PARTITION_ID));
+
+
when(partitionStorage.scanVersions(any(RowId.class))).thenReturn(CursorUtils.emptyCursor());
+
when(partitionStorage.pollForVacuum(lowWatermark)).thenReturn(binaryRowAndRowId);
+
+ assertTrue(storageUpdateHandler.vacuum(lowWatermark));
+ verify(partitionStorage).pollForVacuum(lowWatermark);
+ }
+
+ @Test
+ void testExecuteBatchGc() {
+ PartitionDataStorage partitionStorage = createPartitionDataStorage();
+
+ StorageUpdateHandler storageUpdateHandler =
createStorageUpdateHandler(partitionStorage,
mock(TableIndexStoragesSupplier.class));
+
+ AtomicReference<HybridTimestamp> lowWatermarkReference = new
AtomicReference<>();
+
+ when(lowWatermark.getLowWatermark()).then(invocation ->
lowWatermarkReference.get());
+
+ // Let's check that if lwm is {@code null} then nothing will happen.
+ storageUpdateHandler.executeBatchGc();
+
+ verify(partitionStorage,
never()).pollForVacuum(any(HybridTimestamp.class));
+
+ // Let's check that if lvm is greater than the safe time, then nothing
will happen.
+ safeTimeTracker.update(new HybridTimestamp(10, 10));
+
+ lowWatermarkReference.set(new HybridTimestamp(11, 1));
+
+ storageUpdateHandler.executeBatchGc();
+
+ verify(partitionStorage,
never()).pollForVacuum(any(HybridTimestamp.class));
+
+ // Let's check that if lvm is equal to or less than the safe time,
then garbage collection will be executed.
+ lowWatermarkReference.set(new HybridTimestamp(10, 10));
+
+ storageUpdateHandler.executeBatchGc();
+
+ verify(partitionStorage,
times(1)).pollForVacuum(any(HybridTimestamp.class));
+
+ lowWatermarkReference.set(new HybridTimestamp(9, 9));
+
+ storageUpdateHandler.executeBatchGc();
+
+ verify(partitionStorage,
times(2)).pollForVacuum(any(HybridTimestamp.class));
+ }
+
+ @Test
+ void testInvokeGcOnHandleUpdate() {
+ PartitionDataStorage partitionStorage = createPartitionDataStorage();
+
+ StorageUpdateHandler storageUpdateHandler =
createStorageUpdateHandler(partitionStorage,
mock(TableIndexStoragesSupplier.class));
+
+ HybridTimestamp lwm = safeTimeTracker.current();
+
+ when(lowWatermark.getLowWatermark()).thenReturn(lwm);
+
+ storageUpdateHandler.handleUpdate(
+ UUID.randomUUID(),
+ UUID.randomUUID(),
+ new TablePartitionId(UUID.randomUUID(), PARTITION_ID),
+ null,
+ null
+ );
+
+ verify(partitionStorage).pollForVacuum(lwm);
+ }
+
+ @Test
+ void testInvokeGcOnHandleUpdateAll() {
+ PartitionDataStorage partitionStorage = createPartitionDataStorage();
+
+ StorageUpdateHandler storageUpdateHandler =
createStorageUpdateHandler(partitionStorage,
mock(TableIndexStoragesSupplier.class));
+
+ HybridTimestamp lwm = safeTimeTracker.current();
+
+ when(lowWatermark.getLowWatermark()).thenReturn(lwm);
+
+ storageUpdateHandler.handleUpdateAll(
+ UUID.randomUUID(),
+ Map.of(),
+ new TablePartitionId(UUID.randomUUID(), PARTITION_ID),
+ null
+ );
+
+ verify(partitionStorage).pollForVacuum(lwm);
+ }
+
private static TableSchemaAwareIndexStorage createIndexStorage() {
TableSchemaAwareIndexStorage indexStorage =
mock(TableSchemaAwareIndexStorage.class);
@@ -115,7 +235,7 @@ public class StorageUpdateHandlerTest {
}
private StorageUpdateHandler
createStorageUpdateHandler(PartitionDataStorage partitionStorage,
TableIndexStoragesSupplier indexes) {
- return new StorageUpdateHandler(PARTITION_ID, partitionStorage,
indexes, dataStorageConfig);
+ return new StorageUpdateHandler(PARTITION_ID, partitionStorage,
indexes, dataStorageConfig, safeTimeTracker, lowWatermark);
}
private void setRowVersions(PartitionDataStorage partitionStorage,
Map<UUID, List<BinaryRow>> rowVersions) {
@@ -129,4 +249,10 @@ public class StorageUpdateHandlerTest {
when(partitionStorage.scanVersions(rowId)).thenReturn(Cursor.fromIterable(readResults));
}
}
+
+ private static PartitionDataStorage createPartitionDataStorage() {
+ PartitionDataStorage partitionStorage = spy(new
TestPartitionDataStorage(new TestMvPartitionStorage(PARTITION_ID)));
+
+ return partitionStorage;
+ }
}
diff --git
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/gc/MvGcTest.java
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/gc/MvGcTest.java
index 2d7b56a2da..56caf6c180 100644
---
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/gc/MvGcTest.java
+++
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/gc/MvGcTest.java
@@ -21,6 +21,7 @@ import static
org.apache.ignite.internal.testframework.IgniteTestUtils.runRace;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureExceptionMatcher.willThrowFast;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureExceptionMatcher.willTimeoutFast;
import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
+import static
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willSucceedFast;
import static org.apache.ignite.internal.util.IgniteUtils.closeAllManually;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
@@ -29,6 +30,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.UUID;
@@ -41,6 +44,7 @@ import org.apache.ignite.internal.hlc.HybridTimestamp;
import org.apache.ignite.internal.schema.configuration.TablesConfiguration;
import org.apache.ignite.internal.table.distributed.StorageUpdateHandler;
import
org.apache.ignite.internal.table.distributed.replicator.TablePartitionId;
+import org.apache.ignite.internal.util.PendingComparableValuesTracker;
import org.apache.ignite.lang.ErrorGroups.GarbageCollector;
import org.apache.ignite.lang.IgniteInternalException;
import org.jetbrains.annotations.Nullable;
@@ -291,7 +295,7 @@ public class MvGcTest {
void testClose() throws Exception {
gc.close();
- assertThrowsClosed(() -> gc.addStorage(createTablePartitionId(),
mock(StorageUpdateHandler.class)));
+ assertThrowsClosed(() -> gc.addStorage(createTablePartitionId(),
createStorageUpdateHandler()));
assertThrowsClosed(() -> gc.removeStorage(createTablePartitionId()));
assertThrowsClosed(() -> gc.updateLowWatermark(new HybridTimestamp(1,
1)));
@@ -335,12 +339,40 @@ public class MvGcTest {
}
}
+ @Test
+ void testInvokeVacuumOnlyAfterReachSafeTime() {
+ CompletableFuture<Void> invokeVacuumMethodFuture = new
CompletableFuture<>();
+
+ HybridTimestamp lvm = new HybridTimestamp(10, 10);
+
+ StorageUpdateHandler storageUpdateHandler =
createWithCompleteFutureOnVacuum(invokeVacuumMethodFuture, lvm);
+
+ PendingComparableValuesTracker<HybridTimestamp> safeTimeTracker =
spy(new PendingComparableValuesTracker<>(
+ HybridTimestamp.MIN_VALUE
+ ));
+
+
when(storageUpdateHandler.getSafeTimeTracker()).thenReturn(safeTimeTracker);
+
+ gc.addStorage(createTablePartitionId(), storageUpdateHandler);
+
+ // Let's update the low watermark and see that we didn't start the
garbage collection because we didn't reach the safe time.
+ gc.updateLowWatermark(lvm);
+
+ assertThat(invokeVacuumMethodFuture, willTimeoutFast());
+ verify(safeTimeTracker).waitFor(lvm);
+
+ // Update the safe time to be equal to the low watermark and make sure
the garbage collection starts.
+ safeTimeTracker.update(lvm);
+
+ assertThat(invokeVacuumMethodFuture, willSucceedFast());
+ }
+
private TablePartitionId createTablePartitionId() {
return new TablePartitionId(UUID.randomUUID(), PARTITION_ID);
}
private StorageUpdateHandler
createWithCompleteFutureOnVacuum(CompletableFuture<Void> future, @Nullable
HybridTimestamp exp) {
- StorageUpdateHandler storageUpdateHandler =
mock(StorageUpdateHandler.class);
+ StorageUpdateHandler storageUpdateHandler =
createStorageUpdateHandler();
completeFutureOnVacuum(storageUpdateHandler, future, exp);
@@ -370,7 +402,7 @@ public class MvGcTest {
}
private StorageUpdateHandler createWithCountDownOnVacuum(CountDownLatch
latch) {
- StorageUpdateHandler storageUpdateHandler =
mock(StorageUpdateHandler.class);
+ StorageUpdateHandler storageUpdateHandler =
createStorageUpdateHandler();
when(storageUpdateHandler.vacuum(any(HybridTimestamp.class))).then(invocation
-> {
latch.countDown();
@@ -382,7 +414,7 @@ public class MvGcTest {
}
private StorageUpdateHandler
createWithWaitFinishVacuum(CompletableFuture<Void> startFuture,
CompletableFuture<Void> finishFuture) {
- StorageUpdateHandler storageUpdateHandler =
mock(StorageUpdateHandler.class);
+ StorageUpdateHandler storageUpdateHandler =
createStorageUpdateHandler();
when(storageUpdateHandler.vacuum(any(HybridTimestamp.class))).then(invocation
-> {
startFuture.complete(null);
@@ -402,7 +434,7 @@ public class MvGcTest {
}
private StorageUpdateHandler
createWithCountDownOnVacuumWithoutNextBatch(CountDownLatch latch) {
- StorageUpdateHandler storageUpdateHandler =
mock(StorageUpdateHandler.class);
+ StorageUpdateHandler storageUpdateHandler =
createStorageUpdateHandler();
when(storageUpdateHandler.vacuum(any(HybridTimestamp.class))).then(invocation
-> {
latch.countDown();
@@ -413,4 +445,12 @@ public class MvGcTest {
return storageUpdateHandler;
}
+
+ private static StorageUpdateHandler createStorageUpdateHandler() {
+ StorageUpdateHandler storageUpdateHandler =
mock(StorageUpdateHandler.class);
+
+ when(storageUpdateHandler.getSafeTimeTracker()).thenReturn(new
PendingComparableValuesTracker<>(HybridTimestamp.MAX_VALUE));
+
+ return storageUpdateHandler;
+ }
}
diff --git
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/raft/PartitionCommandListenerTest.java
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/raft/PartitionCommandListenerTest.java
index 00df7072a6..d99ca36a01 100644
---
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/raft/PartitionCommandListenerTest.java
+++
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/raft/PartitionCommandListenerTest.java
@@ -80,6 +80,7 @@ import org.apache.ignite.internal.storage.ReadResult;
import org.apache.ignite.internal.storage.RowId;
import org.apache.ignite.internal.storage.impl.TestMvPartitionStorage;
import org.apache.ignite.internal.storage.index.impl.TestHashIndexStorage;
+import org.apache.ignite.internal.table.distributed.LowWatermark;
import org.apache.ignite.internal.table.distributed.StorageUpdateHandler;
import org.apache.ignite.internal.table.distributed.TableMessagesFactory;
import
org.apache.ignite.internal.table.distributed.TableSchemaAwareIndexStorage;
@@ -189,7 +190,9 @@ public class PartitionCommandListenerTest {
PARTITION_ID,
partitionDataStorage,
DummyInternalTableImpl.createTableIndexStoragesSupplier(Map.of(pkStorage.id(),
pkStorage)),
- dsCfg
+ dsCfg,
+ safeTimeTracker,
+ mock(LowWatermark.class)
));
commandListener = new PartitionListener(
@@ -283,14 +286,16 @@ public class PartitionCommandListenerTest {
PARTITION_ID,
partitionDataStorage,
DummyInternalTableImpl.createTableIndexStoragesSupplier(Map.of(pkStorage.id(),
pkStorage)),
- dsCfg
+ dsCfg,
+ safeTimeTracker,
+ mock(LowWatermark.class)
);
PartitionListener testCommandListener = new PartitionListener(
partitionDataStorage,
storageUpdateHandler,
txStateStorage,
- new PendingComparableValuesTracker<>(new HybridTimestamp(1,
0)),
+ safeTimeTracker,
new PendingComparableValuesTracker<>(0L)
);
diff --git
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerIndexLockingTest.java
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerIndexLockingTest.java
index 9b53b6a251..3e39392828 100644
---
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerIndexLockingTest.java
+++
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerIndexLockingTest.java
@@ -61,6 +61,7 @@ import
org.apache.ignite.internal.storage.index.impl.TestHashIndexStorage;
import org.apache.ignite.internal.storage.index.impl.TestSortedIndexStorage;
import org.apache.ignite.internal.table.distributed.HashIndexLocker;
import org.apache.ignite.internal.table.distributed.IndexLocker;
+import org.apache.ignite.internal.table.distributed.LowWatermark;
import org.apache.ignite.internal.table.distributed.SortedIndexLocker;
import org.apache.ignite.internal.table.distributed.StorageUpdateHandler;
import org.apache.ignite.internal.table.distributed.TableMessagesFactory;
@@ -165,6 +166,8 @@ public class PartitionReplicaListenerIndexLockingTest
extends IgniteAbstractTest
);
DummySchemaManagerImpl schemaManager = new
DummySchemaManagerImpl(schemaDescriptor);
+ PendingComparableValuesTracker<HybridTimestamp> safeTime = new
PendingComparableValuesTracker<>(CLOCK.now());
+
partitionReplicaListener = new PartitionReplicaListener(
TEST_MV_PARTITION_STORAGE,
mockRaftClient,
@@ -184,14 +187,16 @@ public class PartitionReplicaListenerIndexLockingTest
extends IgniteAbstractTest
hashIndexLocker.id(), hashIndexStorage
),
CLOCK,
- new PendingComparableValuesTracker<>(CLOCK.now()),
+ safeTime,
new TestTxStateStorage(),
mock(PlacementDriver.class),
new StorageUpdateHandler(
PART_ID,
new
TestPartitionDataStorage(TEST_MV_PARTITION_STORAGE),
DummyInternalTableImpl.createTableIndexStoragesSupplier(Map.of(pkStorage.get().id(),
pkStorage.get())),
- dsCfg
+ dsCfg,
+ safeTime,
+ mock(LowWatermark.class)
),
peer -> true,
CompletableFuture.completedFuture(schemaManager)
diff --git
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerTest.java
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerTest.java
index 2c64dcd142..ffb75897bb 100644
---
a/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerTest.java
+++
b/modules/table/src/test/java/org/apache/ignite/internal/table/distributed/replication/PartitionReplicaListenerTest.java
@@ -89,6 +89,7 @@ import
org.apache.ignite.internal.storage.index.impl.TestHashIndexStorage;
import org.apache.ignite.internal.storage.index.impl.TestSortedIndexStorage;
import org.apache.ignite.internal.table.distributed.HashIndexLocker;
import org.apache.ignite.internal.table.distributed.IndexLocker;
+import org.apache.ignite.internal.table.distributed.LowWatermark;
import org.apache.ignite.internal.table.distributed.SortedIndexLocker;
import org.apache.ignite.internal.table.distributed.StorageUpdateHandler;
import org.apache.ignite.internal.table.distributed.TableMessagesFactory;
@@ -338,7 +339,9 @@ public class PartitionReplicaListenerTest extends
IgniteAbstractTest {
partId,
partitionDataStorage,
DummyInternalTableImpl.createTableIndexStoragesSupplier(Map.of(pkStorage.get().id(),
pkStorage.get())),
- dsCfg
+ dsCfg,
+ safeTimeClock,
+ mock(LowWatermark.class)
),
peer -> localNode.name().equals(peer.consistentId()),
completedFuture(schemaManager)
diff --git
a/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/impl/DummyInternalTableImpl.java
b/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/impl/DummyInternalTableImpl.java
index b05940b7a4..e4da2afd8a 100644
---
a/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/impl/DummyInternalTableImpl.java
+++
b/modules/table/src/testFixtures/java/org/apache/ignite/internal/table/impl/DummyInternalTableImpl.java
@@ -61,6 +61,7 @@ import
org.apache.ignite.internal.storage.impl.TestMvPartitionStorage;
import org.apache.ignite.internal.storage.index.impl.TestHashIndexStorage;
import org.apache.ignite.internal.table.distributed.HashIndexLocker;
import org.apache.ignite.internal.table.distributed.IndexLocker;
+import org.apache.ignite.internal.table.distributed.LowWatermark;
import org.apache.ignite.internal.table.distributed.StorageUpdateHandler;
import org.apache.ignite.internal.table.distributed.TableIndexStoragesSupplier;
import
org.apache.ignite.internal.table.distributed.TableSchemaAwareIndexStorage;
@@ -274,7 +275,14 @@ public class DummyInternalTableImpl extends
InternalTableImpl {
lenient().when(gcBatchSizeValue.value()).thenReturn(5);
lenient().when(dsCfg.gcOnUpdateBatchSize()).thenReturn(gcBatchSizeValue);
- StorageUpdateHandler storageUpdateHandler = new
StorageUpdateHandler(PART_ID, partitionDataStorage, indexes, dsCfg);
+ StorageUpdateHandler storageUpdateHandler = new StorageUpdateHandler(
+ PART_ID,
+ partitionDataStorage,
+ indexes,
+ dsCfg,
+ safeTime,
+ mock(LowWatermark.class)
+ );
DummySchemaManagerImpl schemaManager = new
DummySchemaManagerImpl(schema);