This is an automated email from the ASF dual-hosted git repository.
sumitagrawl pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new d2ae71ea087 HDDS-15065. Replace Ratis Snapshot Trigger with DB Flush
for Periodic Flush Operations (#10100)
d2ae71ea087 is described below
commit d2ae71ea087c29d86724e8487e0a55137048b0fe
Author: Priyesh Karatha <[email protected]>
AuthorDate: Mon Jun 1 10:02:33 2026 +0530
HDDS-15065. Replace Ratis Snapshot Trigger with DB Flush for Periodic Flush
Operations (#10100)
---
.../org/apache/hadoop/hdds/scm/ScmConfigKeys.java | 9 -
.../common/src/main/resources/ozone-default.xml | 6 -
.../org/apache/hadoop/hdds/scm/ha/RatisUtil.java | 3 -
.../hdds/scm/ha/SCMHADBTransactionBuffer.java | 7 +
.../hdds/scm/ha/SCMHADBTransactionBufferImpl.java | 72 ++++--
.../hdds/scm/ha/SCMHADBTransactionBufferStub.java | 13 +
.../hadoop/hdds/scm/ha/SCMHAManagerImpl.java | 3 +-
.../scm/ha/SCMHATransactionBufferMonitorTask.java | 26 +-
.../apache/hadoop/hdds/scm/ha/SCMStateMachine.java | 41 +--
.../ha/TestSCMHATransactionBufferMonitorTask.java | 275 +++++++++++++++++++++
.../AbstractTestStorageDistributionEndpoint.java | 2 -
.../scm/TestDatanodeSCMNodesReconfiguration.java | 1 -
.../hdds/scm/TestStorageContainerManagerHA.java | 1 -
13 files changed, 382 insertions(+), 77 deletions(-)
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ScmConfigKeys.java
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ScmConfigKeys.java
index e0b28548e8e..2ff192d4663 100644
---
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ScmConfigKeys.java
+++
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/ScmConfigKeys.java
@@ -584,15 +584,6 @@ public final class ScmConfigKeys {
public static final long OZONE_SCM_HA_RATIS_SNAPSHOT_THRESHOLD_DEFAULT =
1000L;
- /**
- * the config will transfer value to ratis config
- * raft.server.snapshot.creation.gap, used by ratis to take snapshot
- * when manual trigger using api.
- */
- public static final String OZONE_SCM_HA_RATIS_SNAPSHOT_GAP
- = "ozone.scm.ha.ratis.server.snapshot.creation.gap";
- public static final long OZONE_SCM_HA_RATIS_SNAPSHOT_GAP_DEFAULT =
- 1024L;
public static final String OZONE_SCM_HA_RATIS_SNAPSHOT_DIR =
"ozone.scm.ha.ratis.snapshot.dir";
diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml
b/hadoop-hdds/common/src/main/resources/ozone-default.xml
index 3089132ca0b..3d1d94735e1 100644
--- a/hadoop-hdds/common/src/main/resources/ozone-default.xml
+++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml
@@ -4201,12 +4201,6 @@
topology cluster tree from SCM.
</description>
</property>
- <property>
- <name>ozone.scm.ha.ratis.server.snapshot.creation.gap</name>
- <value>1024</value>
- <tag>SCM, OZONE</tag>
- <description>Raft snapshot gap index after which snapshot can be
taken.</description>
- </property>
<property>
<name>ozone.scm.ha.dbtransactionbuffer.flush.interval</name>
<value>60s</value>
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/RatisUtil.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/RatisUtil.java
index f2900e38f40..eb5c429861b 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/RatisUtil.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/RatisUtil.java
@@ -231,9 +231,6 @@ private static void setRaftSnapshotProperties(
Snapshot.setAutoTriggerThreshold(properties,
ozoneConf.getLong(ScmConfigKeys.OZONE_SCM_HA_RATIS_SNAPSHOT_THRESHOLD,
ScmConfigKeys.OZONE_SCM_HA_RATIS_SNAPSHOT_THRESHOLD_DEFAULT));
- Snapshot.setCreationGap(properties,
- ozoneConf.getLong(ScmConfigKeys.OZONE_SCM_HA_RATIS_SNAPSHOT_GAP,
- ScmConfigKeys.OZONE_SCM_HA_RATIS_SNAPSHOT_GAP_DEFAULT));
}
public static void checkRatisException(IOException e, String port,
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBuffer.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBuffer.java
index 7acf03424d8..f1d376d9de7 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBuffer.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBuffer.java
@@ -44,7 +44,14 @@ public interface SCMHADBTransactionBuffer
void flush() throws RocksDatabaseException, CodecException;
+ void flushIfNeeded(long snapshotWaitTime)
+ throws RocksDatabaseException, CodecException;
+
boolean shouldFlush(long snapshotWaitTime);
void init() throws RocksDatabaseException, CodecException;
+
+ void beginApplyingTransaction();
+
+ void endApplyingTransaction();
}
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBufferImpl.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBufferImpl.java
index 4b1243fd53d..4baf97a56eb 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBufferImpl.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBufferImpl.java
@@ -20,6 +20,7 @@
import static org.apache.hadoop.ozone.OzoneConsts.TRANSACTION_INFO_KEY;
import com.google.common.base.Preconditions;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -53,6 +54,7 @@ public class SCMHADBTransactionBufferImpl implements
SCMHADBTransactionBuffer {
private final AtomicReference<SnapshotInfo> latestSnapshot
= new AtomicReference<>();
private final AtomicLong txFlushPending = new AtomicLong(0);
+ private final AtomicInteger applyingTransactions = new AtomicInteger(0);
private long lastSnapshotTimeMs = 0;
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
@@ -124,30 +126,64 @@ public AtomicReference<SnapshotInfo>
getLatestSnapshotRef() {
public void flush() throws RocksDatabaseException, CodecException {
rwLock.writeLock().lock();
try {
- // write latest trx info into trx table in the same batch
- Table<String, TransactionInfo> transactionInfoTable
- = metadataStore.getTransactionInfoTable();
- transactionInfoTable.putWithBatch(currentBatchOperation,
- TRANSACTION_INFO_KEY, latestTrxInfo);
+ flushUnderWriteLock();
+ } finally {
+ rwLock.writeLock().unlock();
+ }
+ }
- metadataStore.getStore().commitBatchOperation(currentBatchOperation);
- currentBatchOperation.close();
- this.latestSnapshot.set(latestTrxInfo.toSnapshotInfo());
- // reset batch operation
- currentBatchOperation = metadataStore.getStore().initBatchOperation();
-
- DeletedBlockLog deletedBlockLog = scm.getScmBlockManager()
- .getDeletedBlockLog();
- Preconditions.checkArgument(
- deletedBlockLog instanceof DeletedBlockLogImpl);
- ((DeletedBlockLogImpl) deletedBlockLog).onFlush();
+ @Override
+ public void flushIfNeeded(long snapshotWaitTime)
+ throws RocksDatabaseException, CodecException {
+ rwLock.writeLock().lock();
+ try {
+ if (applyingTransactions.get() > 0) {
+ return;
+ }
+ long timeDiff = scm.getSystemClock().millis() - lastSnapshotTimeMs;
+ if (txFlushPending.get() > 0 && timeDiff > snapshotWaitTime) {
+ LOG.debug("Running TransactionFlushTask");
+ flushUnderWriteLock();
+ }
} finally {
- txFlushPending.set(0);
- lastSnapshotTimeMs = scm.getSystemClock().millis();
rwLock.writeLock().unlock();
}
}
+ private void flushUnderWriteLock()
+ throws RocksDatabaseException, CodecException {
+ // write latest trx info into trx table in the same batch
+ Table<String, TransactionInfo> transactionInfoTable
+ = metadataStore.getTransactionInfoTable();
+ transactionInfoTable.putWithBatch(currentBatchOperation,
+ TRANSACTION_INFO_KEY, latestTrxInfo);
+
+ metadataStore.getStore().commitBatchOperation(currentBatchOperation);
+ currentBatchOperation.close();
+ this.latestSnapshot.set(latestTrxInfo.toSnapshotInfo());
+ // reset batch operation
+ currentBatchOperation = metadataStore.getStore().initBatchOperation();
+
+ DeletedBlockLog deletedBlockLog = scm.getScmBlockManager()
+ .getDeletedBlockLog();
+ Preconditions.checkArgument(
+ deletedBlockLog instanceof DeletedBlockLogImpl);
+ ((DeletedBlockLogImpl) deletedBlockLog).onFlush();
+
+ txFlushPending.set(0);
+ lastSnapshotTimeMs = scm.getSystemClock().millis();
+ }
+
+ @Override
+ public void beginApplyingTransaction() {
+ applyingTransactions.incrementAndGet();
+ }
+
+ @Override
+ public void endApplyingTransaction() {
+ applyingTransactions.decrementAndGet();
+ }
+
@Override
public void init() throws RocksDatabaseException, CodecException {
metadataStore = scm.getScmMetadataStore();
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBufferStub.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBufferStub.java
index 2e7b3fdb0dd..c8347dc7738 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBufferStub.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHADBTransactionBufferStub.java
@@ -101,6 +101,19 @@ public AtomicReference<SnapshotInfo>
getLatestSnapshotRef() {
return null;
}
+ @Override
+ public void flushIfNeeded(long snapshotWaitTime) throws
RocksDatabaseException {
+ flush();
+ }
+
+ @Override
+ public void beginApplyingTransaction() {
+ }
+
+ @Override
+ public void endApplyingTransaction() {
+ }
+
@Override
public void flush() throws RocksDatabaseException {
rwLock.writeLock().lock();
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHAManagerImpl.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHAManagerImpl.java
index 0cc600160e1..01f1fee0d36 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHAManagerImpl.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHAManagerImpl.java
@@ -142,8 +142,7 @@ private void createStartTransactionBufferMonitor() {
OZONE_SCM_HA_DBTRANSACTIONBUFFER_FLUSH_INTERVAL_DEFAULT,
TimeUnit.MILLISECONDS);
SCMHATransactionBufferMonitorTask monitorTask
- = new SCMHATransactionBufferMonitorTask(
- transactionBuffer, ratisServer, interval);
+ = new SCMHATransactionBufferMonitorTask(transactionBuffer, interval);
trxBufferMonitorService =
new BackgroundSCMService.Builder().setClock(scm.getSystemClock())
.setScmContext(scm.getScmContext())
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHATransactionBufferMonitorTask.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHATransactionBufferMonitorTask.java
index 85faedae1c3..74b8a059ae3 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHATransactionBufferMonitorTask.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMHATransactionBufferMonitorTask.java
@@ -18,7 +18,6 @@
package org.apache.hadoop.hdds.scm.ha;
import java.io.IOException;
-import org.apache.ratis.statemachine.SnapshotInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,7 +28,6 @@
public class SCMHATransactionBufferMonitorTask implements Runnable {
private static final Logger LOG =
LoggerFactory.getLogger(SCMHATransactionBufferMonitorTask.class);
- private final SCMRatisServer server;
private final SCMHADBTransactionBuffer transactionBuffer;
private final long flushInterval;
@@ -37,31 +35,17 @@ public class SCMHATransactionBufferMonitorTask implements
Runnable {
* SCMService related variables.
*/
public SCMHATransactionBufferMonitorTask(
- SCMHADBTransactionBuffer transactionBuffer,
- SCMRatisServer server, long flushInterval) {
+ SCMHADBTransactionBuffer transactionBuffer, long flushInterval) {
this.flushInterval = flushInterval;
this.transactionBuffer = transactionBuffer;
- this.server = server;
}
@Override
public void run() {
- if (transactionBuffer.shouldFlush(flushInterval)) {
- LOG.debug("Running TransactionFlushTask");
- // set latest snapshot to null for force snapshot
- // the value will be reset again when snapshot is taken
- final SnapshotInfo lastSnapshot = transactionBuffer
- .getLatestSnapshotRef().getAndSet(null);
- try {
- server.triggerSnapshot();
- } catch (IOException e) {
- LOG.error("Snapshot request is failed", e);
- } finally {
- // under failure case, if unable to take snapshot, its value
- // is reset to previous known value
- transactionBuffer.getLatestSnapshotRef().compareAndSet(
- null, lastSnapshot);
- }
+ try {
+ transactionBuffer.flushIfNeeded(flushInterval);
+ } catch (IOException e) {
+ LOG.error("TransactionFlushTask is failed", e);
}
}
}
diff --git
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMStateMachine.java
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMStateMachine.java
index f43ce2c3dc0..a8a7d6068f0 100644
---
a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMStateMachine.java
+++
b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/SCMStateMachine.java
@@ -160,6 +160,7 @@ public CompletableFuture<Message> applyTransaction(
final TransactionContext trx) {
final CompletableFuture<Message> applyTransactionFuture =
new CompletableFuture<>();
+ transactionBuffer.beginApplyingTransaction();
try {
final SCMRatisRequest request = SCMRatisRequest.decode(
Message.valueOf(trx.getStateMachineLogEntry().getLogData()));
@@ -192,6 +193,8 @@ public CompletableFuture<Message> applyTransaction(
} catch (Exception ex) {
applyTransactionFuture.completeExceptionally(ex);
ExitUtils.terminate(1, ex.getMessage(), ex, StateMachine.LOG);
+ } finally {
+ transactionBuffer.endApplyingTransaction();
}
return applyTransactionFuture;
}
@@ -363,23 +366,28 @@ public long takeSnapshot() throws IOException {
return lastAppliedIndex;
}
- long startTime = Time.monotonicNow();
+ transactionBuffer.beginApplyingTransaction();
+ try {
+ long startTime = Time.monotonicNow();
- TransactionInfo latestTrxInfo = transactionBuffer.getLatestTrxInfo();
- final TransactionInfo lastAppliedTrxInfo =
TransactionInfo.valueOf(lastTermIndex);
+ TransactionInfo latestTrxInfo = transactionBuffer.getLatestTrxInfo();
+ final TransactionInfo lastAppliedTrxInfo =
TransactionInfo.valueOf(lastTermIndex);
- if (latestTrxInfo.compareTo(lastAppliedTrxInfo) < 0) {
- transactionBuffer.updateLatestTrxInfo(lastAppliedTrxInfo);
- transactionBuffer.setLatestSnapshot(lastAppliedTrxInfo.toSnapshotInfo());
- } else {
- lastAppliedIndex = latestTrxInfo.getTransactionIndex();
- }
+ if (latestTrxInfo.compareTo(lastAppliedTrxInfo) < 0) {
+ transactionBuffer.updateLatestTrxInfo(lastAppliedTrxInfo);
+
transactionBuffer.setLatestSnapshot(lastAppliedTrxInfo.toSnapshotInfo());
+ } else {
+ lastAppliedIndex = latestTrxInfo.getTransactionIndex();
+ }
- transactionBuffer.flush();
+ transactionBuffer.flush();
- LOG.info("Current Snapshot Index {}, takeSnapshot took {} ms",
- lastAppliedIndex, Time.monotonicNow() - startTime);
- return lastAppliedIndex;
+ LOG.info("Current Snapshot Index {}, takeSnapshot took {} ms",
+ lastAppliedIndex, Time.monotonicNow() - startTime);
+ return lastAppliedIndex;
+ } finally {
+ transactionBuffer.endApplyingTransaction();
+ }
}
@Override
@@ -399,7 +407,12 @@ public void notifyTermIndexUpdated(long term, long index) {
}
if (transactionBuffer != null) {
- transactionBuffer.updateLatestTrxInfo(TransactionInfo.valueOf(term,
index));
+ transactionBuffer.beginApplyingTransaction();
+ try {
+ transactionBuffer.updateLatestTrxInfo(TransactionInfo.valueOf(term,
index));
+ } finally {
+ transactionBuffer.endApplyingTransaction();
+ }
}
if (currentLeaderTerm.get() == term) {
diff --git
a/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMHATransactionBufferMonitorTask.java
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMHATransactionBufferMonitorTask.java
new file mode 100644
index 00000000000..5f312c6c4dd
--- /dev/null
+++
b/hadoop-hdds/server-scm/src/test/java/org/apache/hadoop/hdds/scm/ha/TestSCMHATransactionBufferMonitorTask.java
@@ -0,0 +1,275 @@
+/*
+ * 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.hadoop.hdds.scm.ha;
+
+import static org.apache.hadoop.ozone.OzoneConsts.TRANSACTION_INFO_KEY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.protobuf.ByteString;
+import java.io.File;
+import java.time.Clock;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.hdds.scm.block.BlockManager;
+import org.apache.hadoop.hdds.scm.block.DeletedBlockLogImpl;
+import org.apache.hadoop.hdds.scm.metadata.SCMMetadataStore;
+import org.apache.hadoop.hdds.scm.metadata.SCMMetadataStoreImpl;
+import org.apache.hadoop.hdds.scm.server.StorageContainerManager;
+import org.apache.hadoop.hdds.utils.TransactionInfo;
+import org.apache.hadoop.hdds.utils.db.Table;
+import org.apache.hadoop.ozone.container.common.SCMTestUtils;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+/**
+ * Tests for {@link SCMHATransactionBufferMonitorTask} and the flush race
+ * conditions it can trigger against {@link SCMHADBTransactionBufferImpl}.
+ */
+public class TestSCMHATransactionBufferMonitorTask {
+
+ private static final long FLUSH_INTERVAL_MS = 1000L;
+ private static final TransactionInfo TRX_INFO_T4 =
+ TransactionInfo.valueOf(1, 4);
+ private static final TransactionInfo TRX_INFO_T5 =
+ TransactionInfo.valueOf(1, 5);
+
+ @TempDir
+ private File testDir;
+
+ private final AtomicLong clockMillis = new AtomicLong(0);
+ private SCMMetadataStore metadataStore;
+ private SCMHADBTransactionBufferImpl transactionBuffer;
+ private Table<String, ByteString> statefulServiceConfigTable;
+ private Table<String, TransactionInfo> transactionInfoTable;
+
+ @BeforeEach
+ public void setup() throws Exception {
+ OzoneConfiguration conf = SCMTestUtils.getConf(testDir);
+ metadataStore = new SCMMetadataStoreImpl(conf);
+ statefulServiceConfigTable = metadataStore.getStatefulServiceConfigTable();
+ transactionInfoTable = metadataStore.getTransactionInfoTable();
+
+ StorageContainerManager scm = mock(StorageContainerManager.class);
+ BlockManager blockManager = mock(BlockManager.class);
+ DeletedBlockLogImpl deletedBlockLog = mock(DeletedBlockLogImpl.class);
+ Clock clock = mock(Clock.class);
+ when(clock.millis()).thenAnswer(invocation -> clockMillis.get());
+ when(scm.getScmMetadataStore()).thenReturn(metadataStore);
+ when(scm.getSystemClock()).thenReturn(clock);
+ when(scm.getScmBlockManager()).thenReturn(blockManager);
+ when(blockManager.getDeletedBlockLog()).thenReturn(deletedBlockLog);
+
+ transactionBuffer = new SCMHADBTransactionBufferImpl(scm);
+ clockMillis.set(FLUSH_INTERVAL_MS + 1);
+ }
+
+ @AfterEach
+ public void cleanup() throws Exception {
+ if (transactionBuffer != null) {
+ transactionBuffer.close();
+ }
+ if (metadataStore != null) {
+ metadataStore.stop();
+ }
+ }
+
+ private void advanceClockPastFlushInterval() {
+ clockMillis.addAndGet(FLUSH_INTERVAL_MS + 1);
+ }
+
+ /**
+ * Demonstrates the partial flush race when shouldFlush and flush are called
+ * separately: buffered data can be committed with a stale transaction index.
+ */
+ @Test
+ public void testPartialFlushWithSeparateShouldFlushAndFlush() throws
Exception {
+ transactionBuffer.updateLatestTrxInfo(TRX_INFO_T4);
+ transactionBuffer.flush();
+
+ transactionBuffer.addToBuffer(statefulServiceConfigTable, "key",
+ ByteString.copyFromUtf8("value"));
+
+ advanceClockPastFlushInterval();
+ if (transactionBuffer.shouldFlush(FLUSH_INTERVAL_MS)) {
+ transactionBuffer.flush();
+ }
+
+ assertEquals(TRX_INFO_T4, transactionInfoTable.get(TRANSACTION_INFO_KEY));
+ assertEquals(ByteString.copyFromUtf8("value"),
+ statefulServiceConfigTable.get("key"));
+ }
+
+ @Test
+ public void testFlushIfNeededDoesNotFlushDuringTransactionApply()
+ throws Exception {
+ transactionBuffer.updateLatestTrxInfo(TRX_INFO_T4);
+ transactionBuffer.flush();
+
+ transactionBuffer.beginApplyingTransaction();
+ try {
+ transactionBuffer.addToBuffer(statefulServiceConfigTable, "key",
+ ByteString.copyFromUtf8("value"));
+ transactionBuffer.flushIfNeeded(FLUSH_INTERVAL_MS);
+ assertNull(statefulServiceConfigTable.get("key"));
+ } finally {
+ transactionBuffer.endApplyingTransaction();
+ }
+
+ transactionBuffer.updateLatestTrxInfo(TRX_INFO_T5);
+ advanceClockPastFlushInterval();
+ transactionBuffer.flushIfNeeded(FLUSH_INTERVAL_MS);
+
+ assertEquals(TRX_INFO_T5, transactionInfoTable.get(TRANSACTION_INFO_KEY));
+ assertEquals(ByteString.copyFromUtf8("value"),
+ statefulServiceConfigTable.get("key"));
+ }
+
+ /**
+ * Demonstrates that calling flush() directly inside an applyTransaction
+ * window (the old behaviour of StatefulServiceStateManagerImpl) persists
+ * the batch with the stale transaction index that was current before the
+ * apply updated it.
+ */
+ @Test
+ public void testDirectFlushDuringApplyWritesStaleTransactionInfo()
+ throws Exception {
+ transactionBuffer.updateLatestTrxInfo(TRX_INFO_T4);
+ transactionBuffer.flush();
+
+ transactionBuffer.beginApplyingTransaction();
+ try {
+ transactionBuffer.addToBuffer(statefulServiceConfigTable, "key",
+ ByteString.copyFromUtf8("value"));
+ // Old saveConfiguration behaviour: flush() before updateLatestTrxInfo.
+ transactionBuffer.flush();
+ // Data is on disk, but the transaction index is still T4 — stale.
+ assertEquals(TRX_INFO_T4,
transactionInfoTable.get(TRANSACTION_INFO_KEY));
+ assertEquals(ByteString.copyFromUtf8("value"),
+ statefulServiceConfigTable.get("key"));
+ } finally {
+ transactionBuffer.updateLatestTrxInfo(TRX_INFO_T5);
+ transactionBuffer.endApplyingTransaction();
+ }
+ }
+
+ /**
+ * Verifies that using flushIfNeeded(0) instead of flush() inside an apply
+ * window defers the write until after updateLatestTrxInfo(), keeping the
+ * on-disk transaction index consistent with the buffered data.
+ */
+ @Test
+ public void testFlushIfNeededZeroWaitDefersDuringApply() throws Exception {
+ transactionBuffer.updateLatestTrxInfo(TRX_INFO_T4);
+ transactionBuffer.flush();
+
+ transactionBuffer.beginApplyingTransaction();
+ try {
+ transactionBuffer.addToBuffer(statefulServiceConfigTable, "key",
+ ByteString.copyFromUtf8("value"));
+ // New saveConfiguration behaviour: skipped because apply is in progress.
+ transactionBuffer.flushIfNeeded(0);
+ assertNull(statefulServiceConfigTable.get("key"),
+ "flushIfNeeded must not flush while a transaction is being applied");
+ } finally {
+ transactionBuffer.updateLatestTrxInfo(TRX_INFO_T5);
+ transactionBuffer.endApplyingTransaction();
+ }
+
+ // After the apply window closes, the monitor flushes both data and the
+ // correct transaction index atomically.
+ advanceClockPastFlushInterval();
+ transactionBuffer.flushIfNeeded(FLUSH_INTERVAL_MS);
+
+ assertEquals(TRX_INFO_T5, transactionInfoTable.get(TRANSACTION_INFO_KEY));
+ assertEquals(ByteString.copyFromUtf8("value"),
+ statefulServiceConfigTable.get("key"));
+ }
+
+ @Test
+ public void testMonitorTaskDoesNotPartialFlushDuringTransactionApply()
+ throws Exception {
+ transactionBuffer.updateLatestTrxInfo(TRX_INFO_T4);
+ transactionBuffer.flush();
+
+ CountDownLatch addedToBuffer = new CountDownLatch(1);
+ CountDownLatch allowFinishApply = new CountDownLatch(1);
+ CountDownLatch applyFinished = new CountDownLatch(1);
+ SCMHATransactionBufferMonitorTask monitorTask =
+ new SCMHATransactionBufferMonitorTask(transactionBuffer,
FLUSH_INTERVAL_MS);
+
+ Thread applyThread = new Thread(() -> {
+ transactionBuffer.beginApplyingTransaction();
+ try {
+ try {
+ transactionBuffer.addToBuffer(statefulServiceConfigTable, "key",
+ ByteString.copyFromUtf8("value"));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ addedToBuffer.countDown();
+ try {
+ allowFinishApply.await(10, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException(e);
+ }
+ transactionBuffer.updateLatestTrxInfo(TRX_INFO_T5);
+ } finally {
+ transactionBuffer.endApplyingTransaction();
+ applyFinished.countDown();
+ }
+ });
+
+ Thread monitorThread = new Thread(() -> {
+ try {
+ while (!applyFinished.await(10, TimeUnit.MILLISECONDS)) {
+ monitorTask.run();
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ });
+
+ applyThread.start();
+ monitorThread.start();
+
+ assertTrue(addedToBuffer.await(10, TimeUnit.SECONDS),
+ "Timed out waiting for applyThread to add data to buffer");
+ monitorTask.run();
+ assertNull(statefulServiceConfigTable.get("key"),
+ "Monitor must not flush before transaction info is updated");
+
+ allowFinishApply.countDown();
+ applyThread.join(10_000);
+ monitorThread.join(10_000);
+
+ advanceClockPastFlushInterval();
+ transactionBuffer.flushIfNeeded(FLUSH_INTERVAL_MS);
+ assertEquals(TRX_INFO_T5, transactionInfoTable.get(TRANSACTION_INFO_KEY));
+ assertEquals(ByteString.copyFromUtf8("value"),
+ statefulServiceConfigTable.get("key"));
+ }
+}
diff --git
a/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/AbstractTestStorageDistributionEndpoint.java
b/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/AbstractTestStorageDistributionEndpoint.java
index 47f4eeb1270..70aaa01a666 100644
---
a/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/AbstractTestStorageDistributionEndpoint.java
+++
b/hadoop-ozone/integration-test-recon/src/test/java/org/apache/hadoop/ozone/recon/AbstractTestStorageDistributionEndpoint.java
@@ -21,7 +21,6 @@
import static org.apache.hadoop.hdds.HddsConfigKeys.HDDS_HEARTBEAT_INTERVAL;
import static
org.apache.hadoop.hdds.HddsConfigKeys.HDDS_SCM_WAIT_TIME_AFTER_SAFE_MODE_EXIT;
import static
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_HA_DBTRANSACTIONBUFFER_FLUSH_INTERVAL;
-import static
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_HA_RATIS_SNAPSHOT_GAP;
import static
org.apache.hadoop.hdds.scm.ScmConfigKeys.OZONE_SCM_HEARTBEAT_PROCESS_INTERVAL;
import static
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_BLOCK_DELETING_SERVICE_INTERVAL;
import static
org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_DIR_DELETING_SERVICE_INTERVAL;
@@ -146,7 +145,6 @@ protected static void initializeCluster(int numDatanodes)
throws Exception {
conf.setTimeDuration(OZONE_DIR_DELETING_SERVICE_INTERVAL, 100,
TimeUnit.MILLISECONDS);
conf.setTimeDuration(OZONE_BLOCK_DELETING_SERVICE_INTERVAL, 100,
TimeUnit.MILLISECONDS);
conf.setTimeDuration(OZONE_SCM_HEARTBEAT_PROCESS_INTERVAL, 100,
TimeUnit.MILLISECONDS);
- conf.setLong(OZONE_SCM_HA_RATIS_SNAPSHOT_GAP, 1L);
conf.setTimeDuration(HDDS_HEARTBEAT_INTERVAL, 50, TimeUnit.MILLISECONDS);
conf.setTimeDuration(HDDS_CONTAINER_REPORT_INTERVAL, 200,
TimeUnit.MILLISECONDS);
conf.setTimeDuration(OZONE_SCM_HA_DBTRANSACTIONBUFFER_FLUSH_INTERVAL, 500,
TimeUnit.MILLISECONDS);
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestDatanodeSCMNodesReconfiguration.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestDatanodeSCMNodesReconfiguration.java
index 6f38d89fb2b..eb30586ec1e 100644
---
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestDatanodeSCMNodesReconfiguration.java
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestDatanodeSCMNodesReconfiguration.java
@@ -74,7 +74,6 @@ public void init() throws Exception {
conf.set(ScmConfigKeys.OZONE_SCM_PIPELINE_CREATION_INTERVAL, "10s");
conf.set(ScmConfigKeys.OZONE_SCM_HA_DBTRANSACTIONBUFFER_FLUSH_INTERVAL,
"5s");
- conf.set(ScmConfigKeys.OZONE_SCM_HA_RATIS_SNAPSHOT_GAP, "1");
conf.setTimeDuration(OZONE_SCM_HEARTBEAT_PROCESS_INTERVAL, 100,
MILLISECONDS);
conf.setTimeDuration(HDDS_HEARTBEAT_INTERVAL, 1, SECONDS);
diff --git
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestStorageContainerManagerHA.java
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestStorageContainerManagerHA.java
index 2825683f1ac..ae10947d52c 100644
---
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestStorageContainerManagerHA.java
+++
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/hdds/scm/TestStorageContainerManagerHA.java
@@ -53,7 +53,6 @@ public void init() throws Exception {
conf.set(ScmConfigKeys.OZONE_SCM_PIPELINE_CREATION_INTERVAL, "10s");
conf.set(ScmConfigKeys.OZONE_SCM_HA_DBTRANSACTIONBUFFER_FLUSH_INTERVAL,
"5s");
- conf.set(ScmConfigKeys.OZONE_SCM_HA_RATIS_SNAPSHOT_GAP, "1");
cluster = MiniOzoneCluster.newHABuilder(conf)
.setOMServiceId("om-service-test1")
.setSCMServiceId("scm-service-test1")
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]