This is an automated email from the ASF dual-hosted git repository.
jt2594838 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new f58a52fe90c Fix WAL replay stalls on oversized entries (#18728)
f58a52fe90c is described below
commit f58a52fe90c5e40a40bc52a1c3a34b469783d4e3
Author: Caideyipi <[email protected]>
AuthorDate: Tue Sep 29 12:19:41 2026 +0800
Fix WAL replay stalls on oversized entries (#18728)
---
.../apache/iotdb/db/i18n/DataNodePipeMessages.java | 12 ++-
.../iotdb/db/i18n/StorageEngineMessages.java | 5 +-
.../apache/iotdb/db/i18n/DataNodePipeMessages.java | 11 ++-
.../iotdb/db/i18n/StorageEngineMessages.java | 5 +-
.../java/org/apache/iotdb/db/conf/IoTDBConfig.java | 11 +++
.../org/apache/iotdb/db/conf/IoTDBDescriptor.java | 3 +
.../dataregion/wal/buffer/WALBuffer.java | 19 +++++
.../dataregion/wal/io/WALByteBufReader.java | 17 ++--
.../consensus/ConsensusPrefetchingQueue.java | 43 +++++++++-
.../broker/consensus/ProgressWALIterator.java | 61 +++++++++++++-
.../dataregion/wal/buffer/WALBufferCommonTest.java | 27 +++++++
.../dataregion/wal/io/WALFileTest.java | 34 ++++++++
.../consensus/ConsensusPrefetchingQueueTest.java | 63 +++++++++++++++
.../broker/consensus/ProgressWALIteratorTest.java | 92 ++++++++++++++++++++++
.../resources/conf/edge/iotdb-system.properties | 1 +
.../conf/iotdb-system.properties.template | 6 ++
16 files changed, 394 insertions(+), 16 deletions(-)
diff --git
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
index 3f7330a79ad..59f58407dc4 100644
---
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
+++
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
@@ -2220,8 +2220,16 @@ public final class DataNodePipeMessages {
"ProgressWALIterator: failed to open near-live WAL file {}, retrying
without blacklisting";
public static final String
PIPE_LOG_PROGRESSWALITERATOR_ERROR_READING_WAL_2DB46D41 =
"ProgressWALIterator: error reading WAL";
- public static final String
PIPE_LOG_PROGRESSWALITERATOR_FAILED_TO_OPEN_WAL_FILE_SKIPPING_29CA1092 =
- "ProgressWALIterator: failed to open WAL file {}, skipping";
+ public static final String
+
PIPE_LOG_PROGRESSWALITERATOR_FAILED_TO_PROCESS_WAL_FILE_SKIPPING_REMAINING_ENTRIES_093F14A9
=
+ "ProgressWALIterator: failed to process WAL file {}, skipping remaining
entries";
+ public static final String
+
PIPE_LOG_PROGRESSWALITERATOR_FAILED_TO_IDENTIFY_UNREADABLE_WAL_FILE_7BA9F422 =
+ "failed to identify unreadable WAL file during replay";
+ public static final String
+
SUBSCRIPTION_ERROR_WAL_REPLAY_SKIPPED_UNREADABLE_RETAINED_WAL_FILES_D60C5FB2 =
+ "Subscription WAL replay skipped %d unreadable retained WAL file(s).
Historical data in "
+ + "those files cannot be delivered.";
public static final String
PIPE_LOG_PROGRESSWALITERATOR_SKIPPED_UNREADABLE_RETAINED_WAL_FILES_FFC8455E =
"ProgressWALIterator: skipped {} unreadable retained WAL files in
directory {}, "
+ "firstFile={}, lastFile={}, firstError={}; historical subscription
data in these "
diff --git
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/StorageEngineMessages.java
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/StorageEngineMessages.java
index 12ef8456800..032db3e9bb1 100644
---
a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/StorageEngineMessages.java
+++
b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/StorageEngineMessages.java
@@ -349,7 +349,10 @@ public final class StorageEngineMessages {
public static final String FAILED_TO_CREATE_WAL_NODE_AFTER_RETRIES = "Failed
to create WAL node after retries for identifier: ";
public static final String FAIL_TO_CREATE_WAL_NODE = "Fail to create wal
node";
public static final String OVER_SIZED_WAL_ENTRY = "The wal entry size %d
exceeds the limit %d, which may be a result of file corruption or configuration
change."
- + "Please increase wal_buffer_size_in_byte or quarantine the file %s";
+ + "Please increase wal_entry_size_limit_in_byte or quarantine the file
%s";
+ public static final String
+
EXCEPTION_THE_WAL_ENTRY_SIZE_ARG_EXCEEDS_WAL_ENTRY_SIZE_LIMIT_IN_BYTE_ARG_691BB408
=
+ "The WAL entry size %d exceeds wal_entry_size_limit_in_byte %d.";
// ======================== Flush ========================
diff --git
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
index 9b124afaffa..48ec49342f3 100644
---
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
+++
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodePipeMessages.java
@@ -2062,8 +2062,15 @@ public final class DataNodePipeMessages {
"ProgressWALIterator:打开 near-live WAL 文件 {} 失败,不加入黑名单并重试";
public static final String
PIPE_LOG_PROGRESSWALITERATOR_ERROR_READING_WAL_2DB46D41 =
"ProgressWALIterator:读取 WAL 出错";
- public static final String
PIPE_LOG_PROGRESSWALITERATOR_FAILED_TO_OPEN_WAL_FILE_SKIPPING_29CA1092 =
- "ProgressWALIterator:打开 WAL 文件 {} 失败,跳过该文件";
+ public static final String
+
PIPE_LOG_PROGRESSWALITERATOR_FAILED_TO_PROCESS_WAL_FILE_SKIPPING_REMAINING_ENTRIES_093F14A9
=
+ "ProgressWALIterator:处理 WAL 文件 {} 失败,跳过该文件的剩余条目";
+ public static final String
+
PIPE_LOG_PROGRESSWALITERATOR_FAILED_TO_IDENTIFY_UNREADABLE_WAL_FILE_7BA9F422 =
+ "重放时无法确定不可读的 WAL 文件";
+ public static final String
+
SUBSCRIPTION_ERROR_WAL_REPLAY_SKIPPED_UNREADABLE_RETAINED_WAL_FILES_D60C5FB2 =
+ "订阅 WAL 重放跳过了 %d 个无法读取的保留 WAL 文件。这些文件中的历史数据无法投递。";
public static final String
PIPE_LOG_PROGRESSWALITERATOR_SKIPPED_UNREADABLE_RETAINED_WAL_FILES_FFC8455E =
"ProgressWALIterator:跳过了 {} 个无法读取的保留 WAL 文件,directory={},firstFile={},"
+ "lastFile={},firstError={};这些文件中的历史订阅数据无法重放";
diff --git
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/StorageEngineMessages.java
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/StorageEngineMessages.java
index a69ae2a6c8e..43ebfbd35ab 100644
---
a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/StorageEngineMessages.java
+++
b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/StorageEngineMessages.java
@@ -349,7 +349,10 @@ public final class StorageEngineMessages {
public static final String FAILED_TO_CREATE_WAL_NODE_AFTER_RETRIES =
"重试后仍无法创建 WAL 节点,标识符: ";
public static final String FAIL_TO_CREATE_WAL_NODE = "创建 WAL 节点失败";
public static final String OVER_SIZED_WAL_ENTRY = "WAL条目大小 %d 超过阈值 %d,
可能是由于文件损坏或者系统配置变更."
- + "请提高 wal_buffer_size_in_byte 或者隔离该文件 %s";
+ + "请提高 wal_entry_size_limit_in_byte 或者隔离该文件 %s";
+ public static final String
+
EXCEPTION_THE_WAL_ENTRY_SIZE_ARG_EXCEEDS_WAL_ENTRY_SIZE_LIMIT_IN_BYTE_ARG_691BB408
=
+ "WAL 条目大小 %d 超过 wal_entry_size_limit_in_byte %d。";
// ======================== Flush ========================
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java
index 82a6f397720..fb39bb57a62 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBConfig.java
@@ -186,6 +186,9 @@ public class IoTDBConfig implements DefaultEncodingProvider
{
/** Buffer size of each wal node. Unit: byte */
private int walBufferSize = 32 * 1024 * 1024;
+ /** Maximum size of one WAL entry. A non-positive value follows
walBufferSize. Unit: byte */
+ private int walEntrySizeLimitInByte = -1;
+
/** Blocking queue capacity of each delete ahead log buffer */
private int deletionAheadLogBufferQueueCapacity = 500;
@@ -2152,6 +2155,14 @@ public class IoTDBConfig implements
DefaultEncodingProvider {
this.walBufferSize = walBufferSize;
}
+ public int getWalEntrySizeLimitInByte() {
+ return walEntrySizeLimitInByte > 0 ? walEntrySizeLimitInByte :
walBufferSize;
+ }
+
+ public void setWalEntrySizeLimitInByte(int walEntrySizeLimitInByte) {
+ this.walEntrySizeLimitInByte = walEntrySizeLimitInByte;
+ }
+
public int getDeletionAheadLogBufferQueueCapacity() {
return deletionAheadLogBufferQueueCapacity;
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java
index 098940f8254..a2b8eff3610 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/conf/IoTDBDescriptor.java
@@ -1444,6 +1444,9 @@ public class IoTDBDescriptor {
conf.setWalBufferSize(walBufferSize);
}
+ conf.setWalEntrySizeLimitInByte(
+
Integer.parseInt(properties.getProperty("wal_entry_size_limit_in_byte", "-1")));
+
boolean WALInsertNodeCacheShrinkClearEnabled =
Boolean.parseBoolean(
properties.getProperty(
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/buffer/WALBuffer.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/buffer/WALBuffer.java
index 27e36f7574c..13930f936d5 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/buffer/WALBuffer.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/buffer/WALBuffer.java
@@ -22,6 +22,7 @@ package
org.apache.iotdb.db.storageengine.dataregion.wal.buffer;
import org.apache.iotdb.commons.concurrent.IoTDBThreadPoolFactory;
import org.apache.iotdb.commons.concurrent.ThreadName;
import org.apache.iotdb.commons.conf.CommonDescriptor;
+import org.apache.iotdb.commons.exception.IoTDBRuntimeException;
import org.apache.iotdb.commons.utils.TestOnly;
import org.apache.iotdb.db.conf.IoTDBConfig;
import org.apache.iotdb.db.conf.IoTDBDescriptor;
@@ -68,6 +69,7 @@ import java.util.function.BiConsumer;
import java.util.function.Predicate;
import static
org.apache.iotdb.db.storageengine.dataregion.wal.node.WALNode.DEFAULT_SEARCH_INDEX;
+import static org.apache.iotdb.rpc.TSStatusCode.WAL_ENTRY_TOO_LARGE;
/**
* This buffer guarantees the concurrent safety and uses double buffers
mechanism to accelerate
@@ -247,6 +249,23 @@ public class WALBuffer extends AbstractWALBuffer {
walEntry.getWalFlushListener().fail(new
WALNodeClosedException(identifier));
return;
}
+ if (!walEntry.isSignal() && walEntry.getType() !=
WALEntryType.MEMORY_TABLE_CHECKPOINT) {
+ final int serializedSize = walEntry.serializedSize();
+ final int entrySizeLimit = config.getWalEntrySizeLimitInByte();
+ if (serializedSize > entrySizeLimit) {
+ walEntry
+ .getWalFlushListener()
+ .fail(
+ new IoTDBRuntimeException(
+ String.format(
+ StorageEngineMessages
+
.EXCEPTION_THE_WAL_ENTRY_SIZE_ARG_EXCEEDS_WAL_ENTRY_SIZE_LIMIT_IN_BYTE_ARG_691BB408,
+ serializedSize,
+ entrySizeLimit),
+ WAL_ENTRY_TOO_LARGE.getStatusCode()));
+ return;
+ }
+ }
// just add this WALEntry to queue
try {
walEntries.put(walEntry);
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/io/WALByteBufReader.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/io/WALByteBufReader.java
index 3fa8f23a2ce..f89b1fb5393 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/io/WALByteBufReader.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/wal/io/WALByteBufReader.java
@@ -20,6 +20,7 @@
package org.apache.iotdb.db.storageengine.dataregion.wal.io;
import org.apache.iotdb.consensus.iot.log.ConsensusReqReader;
+import org.apache.iotdb.db.conf.IoTDBDescriptor;
import org.apache.iotdb.db.i18n.StorageEngineMessages;
import org.apache.iotdb.db.storageengine.dataregion.wal.buffer.WALEntry;
@@ -31,13 +32,13 @@ import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.List;
-import static
org.apache.iotdb.db.storageengine.dataregion.wal.buffer.WALBuffer.ONE_THIRD_WAL_BUFFER_SIZE;
-
/**
* This reader returns {@link WALEntry} as {@link ByteBuffer}, the usage of
WALByteBufReader is like
* {@link Iterator}.
*/
public class WALByteBufReader implements Closeable {
+ private static final int READ_CHUNK_SIZE = 1024 * 1024;
+
private WALMetaData metaData;
private WALInputStream walInputStream;
private DataInputStream logStream;
@@ -86,12 +87,14 @@ public class WALByteBufReader implements Closeable {
public ByteBuffer next() throws IOException {
currentEntryIndex++;
int size = sizeIterator.next();
- if (size > ONE_THIRD_WAL_BUFFER_SIZE) {
+ final int entrySizeLimit =
+ IoTDBDescriptor.getInstance().getConfig().getWalEntrySizeLimitInByte();
+ if (size > entrySizeLimit) {
throw new IOException(
String.format(
StorageEngineMessages.OVER_SIZED_WAL_ENTRY,
size,
- ONE_THIRD_WAL_BUFFER_SIZE,
+ entrySizeLimit,
walInputStream.logFile));
}
// TODO: Reuse this buffer
@@ -101,7 +104,11 @@ public class WALByteBufReader implements Closeable {
logStream.readFully, since this function does not change
the position of the buffer.
*/
- logStream.readFully(buffer.array(), 0, size);
+ for (int offset = 0; offset < size; ) {
+ final int chunkSize = Math.min(READ_CHUNK_SIZE, size - offset);
+ logStream.readFully(buffer.array(), offset, chunkSize);
+ offset += chunkSize;
+ }
return buffer;
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueue.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueue.java
index f68d37cc6a2..7f9ba6afd24 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueue.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueue.java
@@ -89,6 +89,7 @@ import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiConsumer;
import java.util.function.BooleanSupplier;
@@ -181,6 +182,10 @@ public class ConsensusPrefetchingQueue {
private final AtomicLong walGapSkippedEntries = new AtomicLong(0);
+ private final AtomicReference<String> pendingWalReplayError = new
AtomicReference<>();
+
+ private int reportedSkippedBrokenWalFileCount = 0;
+
/**
* Guards queue state transitions that touch replay positioning, seek state,
and writer buffers.
*/
@@ -749,6 +754,10 @@ public class ConsensusPrefetchingQueue {
if (isClosed || closeRequested || !isActive) {
return null;
}
+ final String walReplayError = pendingWalReplayError.getAndSet(null);
+ if (Objects.nonNull(walReplayError)) {
+ return generateErrorResponse(walReplayError);
+ }
if (!prefetchInitialized && !initPrefetch(regionProgress)) {
return null;
}
@@ -1927,11 +1936,13 @@ public class ConsensusPrefetchingQueue {
}
ensureSubscriptionWalReadable();
+ reportUnreadableWalReplayIfNecessary();
int entriesRead = 0;
while (entriesRead < maxWalEntries
&& subscriptionWALIterator.hasNext()
&& prefetchingQueue.size() < MAX_PREFETCHING_QUEUE_SIZE) {
+ reportUnreadableWalReplayIfNecessary();
try {
final IndexedConsensusRequest walEntry =
subscriptionWALIterator.next();
entriesRead++;
@@ -1968,6 +1979,7 @@ public class ConsensusPrefetchingQueue {
break;
}
}
+ reportUnreadableWalReplayIfNecessary();
if (entriesRead > 0) {
LOGGER.debug(
@@ -1980,6 +1992,22 @@ public class ConsensusPrefetchingQueue {
return MaterializationResult.SUCCESS;
}
+ private void reportUnreadableWalReplayIfNecessary() {
+ if (Objects.isNull(subscriptionWALIterator)) {
+ return;
+ }
+ final int skippedBrokenWalFileCount =
subscriptionWALIterator.getSkippedBrokenWalFileCount();
+ if (skippedBrokenWalFileCount <= reportedSkippedBrokenWalFileCount) {
+ return;
+ }
+ reportedSkippedBrokenWalFileCount = skippedBrokenWalFileCount;
+ pendingWalReplayError.set(
+ String.format(
+ DataNodePipeMessages
+
.SUBSCRIPTION_ERROR_WAL_REPLAY_SKIPPED_UNREADABLE_RETAINED_WAL_FILES_D60C5FB2,
+ skippedBrokenWalFileCount));
+ }
+
private void advanceWalReplayCursorIfPresent(final IndexedConsensusRequest
request) {
if (!hasLocalSearchIndex(request)) {
return;
@@ -2003,14 +2031,24 @@ public class ConsensusPrefetchingQueue {
}
private void ensureSubscriptionWalReadable() {
- if (Objects.isNull(subscriptionWALIterator) ||
subscriptionWALIterator.hasNext()) {
+ if (Objects.isNull(subscriptionWALIterator)) {
+ return;
+ }
+ if (subscriptionWALIterator.hasNext()) {
+ reportUnreadableWalReplayIfNecessary();
return;
}
+ reportUnreadableWalReplayIfNecessary();
// Listing and sorting all retained WAL files is only necessary after the
iterator is
// exhausted. While it still has a readable request, refreshing cannot
affect the next result.
subscriptionWALIterator.refresh();
- if (subscriptionWALIterator.hasNext() || !(consensusReqReader instanceof
WALNode)) {
+ if (subscriptionWALIterator.hasNext()) {
+ reportUnreadableWalReplayIfNecessary();
+ return;
+ }
+ reportUnreadableWalReplayIfNecessary();
+ if (!(consensusReqReader instanceof WALNode)) {
return;
}
@@ -2031,6 +2069,7 @@ public class ConsensusPrefetchingQueue {
private void resetSubscriptionWALPosition(final long startSearchIndex) {
closeSubscriptionWALIterator();
+ reportedSkippedBrokenWalFileCount = 0;
subscriptionWALIterator = createSubscriptionWALIterator(startSearchIndex);
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ProgressWALIterator.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ProgressWALIterator.java
index d9f8ca5e94e..ee61c105c15 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ProgressWALIterator.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/subscription/broker/consensus/ProgressWALIterator.java
@@ -431,7 +431,11 @@ public class ProgressWALIterator implements Closeable,
Iterator<IndexedConsensus
continue;
} catch (final EOFException eofException) {
if (!currentReaderUsesLiveSnapshot) {
- throw eofException;
+ final IndexedConsensusRequest flushed =
skipUnreadableCurrentWalFile(eofException);
+ if (flushed != null && !shouldSkip(flushed)) {
+ return flushed;
+ }
+ continue;
}
// Live snapshot metadata may get ahead of the bytes currently
visible in the file. Treat
// EOF as "this snapshot is exhausted for now" instead of
terminating the iterator.
@@ -444,6 +448,12 @@ public class ProgressWALIterator implements Closeable,
Iterator<IndexedConsensus
continue;
}
return null;
+ } catch (final IOException readException) {
+ final IndexedConsensusRequest flushed =
skipUnreadableCurrentWalFile(readException);
+ if (flushed != null && !shouldSkip(flushed)) {
+ return flushed;
+ }
+ continue;
}
}
@@ -605,11 +615,55 @@ public class ProgressWALIterator implements Closeable,
Iterator<IndexedConsensus
lastUnreportedSkippedBrokenWalFile = walFile.getName();
unreportedSkippedBrokenWalFileCount++;
LOGGER.debug(
-
DataNodePipeMessages.PIPE_LOG_PROGRESSWALITERATOR_FAILED_TO_OPEN_WAL_FILE_SKIPPING_29CA1092,
+ DataNodePipeMessages
+
.PIPE_LOG_PROGRESSWALITERATOR_FAILED_TO_PROCESS_WAL_FILE_SKIPPING_REMAINING_ENTRIES_093F14A9,
walFile.getName(),
error);
}
+ private IndexedConsensusRequest skipUnreadableCurrentWalFile(final
IOException error) {
+ final int failedFileIndex = currentFileIndex;
+ final File walFile =
+ failedFileIndex >= 0 && failedFileIndex < walFiles.length
+ ? walFiles[failedFileIndex]
+ : null;
+ final long versionId =
+ failedFileIndex >= 0 && failedFileIndex < walFileVersionIds.length
+ ? walFileVersionIds[failedFileIndex]
+ : currentReaderVersionId;
+ final IndexedConsensusRequest flushed =
+ isFailedEntryPartOfPendingRequest() ? null : flushPending();
+
+ try {
+ closeCurrentReader();
+ } catch (final IOException closeException) {
+ error.addSuppressed(closeException);
+ }
+ pendingRequests.clear();
+ pendingSearchIndex = Long.MIN_VALUE;
+ pendingLocalSeq = Long.MIN_VALUE;
+ resetCurrentFileTracking();
+
+ if (walFile == null || versionId < 0) {
+ markIncompleteScan(
+ DataNodePipeMessages
+
.PIPE_LOG_PROGRESSWALITERATOR_FAILED_TO_IDENTIFY_UNREADABLE_WAL_FILE_7BA9F422,
+ error);
+ return flushed;
+ }
+ recordSkippedBrokenWalFile(versionId, walFile, error);
+ return flushed;
+ }
+
+ private boolean isFailedEntryPartOfPendingRequest() {
+ return currentReader != null
+ && !pendingRequests.isEmpty()
+ && isSamePendingRequest(
+ currentReader.getCurrentEntryPhysicalTime(),
+ currentReader.getCurrentEntryNodeId(),
+ currentReader.getCurrentEntryLocalSeq());
+ }
+
private void logSkippedBrokenWalFilesIfNecessary() {
if (unreportedSkippedBrokenWalFileCount == 0) {
return;
@@ -717,8 +771,9 @@ public class ProgressWALIterator implements Closeable,
Iterator<IndexedConsensus
private void closeCurrentReader() throws IOException {
if (currentReader != null) {
- currentReader.close();
+ final ProgressWALReader reader = currentReader;
currentReader = null;
+ reader.close();
}
}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/wal/buffer/WALBufferCommonTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/wal/buffer/WALBufferCommonTest.java
index 253e71e2a02..1d4dcbc8c0e 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/wal/buffer/WALBufferCommonTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/wal/buffer/WALBufferCommonTest.java
@@ -20,6 +20,7 @@ package
org.apache.iotdb.db.storageengine.dataregion.wal.buffer;
import org.apache.iotdb.calc.exception.QueryProcessException;
import org.apache.iotdb.commons.exception.IllegalPathException;
+import org.apache.iotdb.commons.exception.IoTDBRuntimeException;
import org.apache.iotdb.commons.path.PartialPath;
import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
import org.apache.iotdb.db.conf.IoTDBConfig;
@@ -27,6 +28,7 @@ import org.apache.iotdb.db.conf.IoTDBDescriptor;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowNode;
import org.apache.iotdb.db.storageengine.dataregion.wal.io.WALReader;
import org.apache.iotdb.db.storageengine.dataregion.wal.utils.WALFileUtils;
+import
org.apache.iotdb.db.storageengine.dataregion.wal.utils.listener.WALFlushListener;
import org.apache.iotdb.db.utils.constant.TestConstant;
import org.apache.tsfile.common.conf.TSFileConfig;
@@ -48,7 +50,9 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
+import static org.apache.iotdb.rpc.TSStatusCode.WAL_ENTRY_TOO_LARGE;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public abstract class WALBufferCommonTest {
@@ -167,11 +171,34 @@ public abstract class WALBufferCommonTest {
public void testHugeWrite() throws Exception {
// use small buffer (only 32 bytes) to simulate huge write request
int prevWalBufferSize = config.getWalBufferSize();
+ int prevWalEntrySizeLimit = config.getWalEntrySizeLimitInByte();
config.setWalBufferSize(32);
+ config.setWalEntrySizeLimitInByte(Integer.MAX_VALUE);
try {
testConcurrentWrite();
} finally {
config.setWalBufferSize(prevWalBufferSize);
+ config.setWalEntrySizeLimitInByte(prevWalEntrySizeLimit);
+ }
+ }
+
+ @Test
+ public void testRejectEntryExceedingConfiguredSizeLimit() throws Exception {
+ final InsertRowNode insertRowNode = getInsertRowNode(devicePath, 1L);
+ final WALEntry walEntry = new WALInfoEntry(1L, insertRowNode, true);
+ final int previousEntrySizeLimit = config.getWalEntrySizeLimitInByte();
+ try {
+ config.setWalEntrySizeLimitInByte(walEntry.serializedSize() - 1);
+ walBuffer.write(walEntry);
+
+ assertEquals(WALFlushListener.Status.FAILURE,
walEntry.getWalFlushListener().waitForResult());
+ assertTrue(walEntry.getWalFlushListener().getCause() instanceof
IoTDBRuntimeException);
+ assertEquals(
+ WAL_ENTRY_TOO_LARGE.getStatusCode(),
+ ((IoTDBRuntimeException)
walEntry.getWalFlushListener().getCause()).getErrorCode());
+ assertTrue(walBuffer.isAllWALEntriesConsumed());
+ } finally {
+ config.setWalEntrySizeLimitInByte(previousEntrySizeLimit);
}
}
}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/wal/io/WALFileTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/wal/io/WALFileTest.java
index ba34aef1a9e..5c8880686b3 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/wal/io/WALFileTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/wal/io/WALFileTest.java
@@ -95,6 +95,40 @@ public class WALFileTest {
Files.deleteIfExists(new File(walFile + ".broken.1").toPath());
}
+ @Test
+ public void testReadEntryLargerThanOneThirdWalBufferWithinIndependentLimit()
throws Exception {
+ final int originalWalBufferSize =
IoTDBDescriptor.getInstance().getConfig().getWalBufferSize();
+ final int originalEntrySizeLimit =
+ IoTDBDescriptor.getInstance().getConfig().getWalEntrySizeLimitInByte();
+ final int entrySize = originalWalBufferSize / 3 + 1;
+ try {
+
IoTDBDescriptor.getInstance().getConfig().setWalEntrySizeLimitInByte(originalWalBufferSize);
+
+ final ByteBuffer entry = ByteBuffer.allocate(entrySize);
+ entry.put(WALEntryType.INSERT_ROW_NODE.getCode());
+ entry.position(entrySize);
+ final WALMetaData metadata = new WALMetaData();
+ metadata.add(entrySize, -1, 42);
+ try (WALWriter writer = new WALWriter(walFile, WALFileVersion.V3)) {
+ writer.write(entry, metadata);
+ }
+
+ try (WALByteBufReader reader = new WALByteBufReader(walFile)) {
+ assertTrue(reader.hasNext());
+ assertArrayEquals(entry.array(), reader.next().array());
+ assertFalse(reader.hasNext());
+ }
+
+
IoTDBDescriptor.getInstance().getConfig().setWalEntrySizeLimitInByte(entrySize
- 1);
+ try (WALByteBufReader reader = new WALByteBufReader(walFile)) {
+ final IOException exception = assertThrows(IOException.class,
reader::next);
+
assertTrue(exception.getMessage().contains("wal_entry_size_limit_in_byte"));
+ }
+ } finally {
+
IoTDBDescriptor.getInstance().getConfig().setWalEntrySizeLimitInByte(originalEntrySizeLimit);
+ }
+ }
+
/** Unexpected channel closure must propagate to the buffer instead of
acknowledging a write. */
@Test
public void testClosedChannelWriteAndForceFail() throws IOException {
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueueTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueueTest.java
index b2127159672..6dabc8e2a19 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueueTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/broker/consensus/ConsensusPrefetchingQueueTest.java
@@ -44,7 +44,9 @@ import
org.apache.iotdb.db.subscription.agent.SubscriptionConsumerAgent;
import org.apache.iotdb.db.subscription.event.SubscriptionEvent;
import org.apache.iotdb.db.subscription.resource.SubscriptionMemoryManager;
import org.apache.iotdb.rpc.subscription.config.TopicConstant;
+import org.apache.iotdb.rpc.subscription.payload.poll.ErrorPayload;
import org.apache.iotdb.rpc.subscription.payload.poll.RegionProgress;
+import
org.apache.iotdb.rpc.subscription.payload.poll.SubscriptionPollResponseType;
import org.apache.iotdb.rpc.subscription.payload.poll.WriterId;
import org.apache.iotdb.rpc.subscription.payload.poll.WriterProgress;
@@ -112,6 +114,59 @@ public class ConsensusPrefetchingQueueTest {
.getModifiers()));
}
+ @Test
+ public void testUnreadableWalReplayIsVisibleAsPollError() throws Exception {
+ final String originalSystemDir =
IoTDBDescriptor.getInstance().getConfig().getSystemDir();
+ final File systemDir =
temporaryFolder.newFolder("system-unreadable-wal-poll-error");
+ ConsensusPrefetchingQueue queue = null;
+ try {
+ final FakeConsensusReqReader reader = new FakeConsensusReqReader();
+ final IoTConsensusServerImpl serverImpl =
mock(IoTConsensusServerImpl.class);
+ when(serverImpl.getConsensusReqReader()).thenReturn(reader);
+ when(serverImpl.getWriterSafeFrontierTracker()).thenReturn(new
WriterSafeFrontierTracker());
+
+ final ConsensusLogToTabletConverter converter =
mock(ConsensusLogToTabletConverter.class);
+ when(converter.getDatabaseName()).thenReturn("db");
+ when(converter.isTableModel()).thenReturn(true);
+
+ queue =
+ new ConsensusPrefetchingQueue(
+ "consumerGroup",
+ "topic",
+ TopicConstant.ORDER_MODE_LEADER_ONLY_VALUE,
+ new DataRegionId(1),
+ serverImpl,
+ new SubscriptionWalRetentionPolicy(
+ "topic",
+ SubscriptionWalRetentionPolicy.UNBOUNDED,
+ SubscriptionWalRetentionPolicy.UNBOUNDED),
+ converter,
+ newCommitManager(systemDir),
+ new RegionProgress(Collections.emptyMap()),
+ 1L,
+ 1L,
+ true);
+
+ final ProgressWALIterator iterator = mock(ProgressWALIterator.class);
+ when(iterator.getSkippedBrokenWalFileCount()).thenReturn(1);
+ setSubscriptionWalIterator(queue, iterator);
+ invokeReportUnreadableWalReplayIfNecessary(queue);
+
+ final SubscriptionEvent event = queue.poll("consumer");
+ assertNotNull(event);
+ assertEquals(
+ SubscriptionPollResponseType.ERROR.getType(),
+ event.getCurrentResponse().getResponseType());
+ assertTrue(event.getCurrentResponse().getPayload() instanceof
ErrorPayload);
+ assertNull(queue.poll("consumer"));
+ } finally {
+ if (queue != null) {
+ queue.close();
+ }
+
IoTDBDescriptor.getInstance().getConfig().setSystemDir(originalSystemDir);
+ }
+ }
+
@Test
public void testPrefetchDoesNotDependOnConsumerMetaReadLock() throws
Exception {
final String originalSystemDir =
IoTDBDescriptor.getInstance().getConfig().getSystemDir();
@@ -2272,6 +2327,14 @@ public class ConsensusPrefetchingQueueTest {
method.invoke(queue);
}
+ private static void invokeReportUnreadableWalReplayIfNecessary(
+ final ConsensusPrefetchingQueue queue) throws Exception {
+ final Method method =
+
ConsensusPrefetchingQueue.class.getDeclaredMethod("reportUnreadableWalReplayIfNecessary");
+ method.setAccessible(true);
+ method.invoke(queue);
+ }
+
private static Tablet createTablet() {
final List<String> columnNames = Arrays.asList("device", "temperature");
final List<TSDataType> dataTypes = Arrays.asList(TSDataType.STRING,
TSDataType.DOUBLE);
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/broker/consensus/ProgressWALIteratorTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/broker/consensus/ProgressWALIteratorTest.java
index 7f816937091..45bff39d6ba 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/broker/consensus/ProgressWALIteratorTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/subscription/broker/consensus/ProgressWALIteratorTest.java
@@ -21,6 +21,7 @@ package org.apache.iotdb.db.subscription.broker.consensus;
import
org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeType;
import org.apache.iotdb.consensus.common.request.IndexedConsensusRequest;
+import org.apache.iotdb.db.conf.IoTDBDescriptor;
import org.apache.iotdb.db.storageengine.dataregion.wal.buffer.WALEntryType;
import org.apache.iotdb.db.storageengine.dataregion.wal.buffer.WALInfoEntry;
import org.apache.iotdb.db.storageengine.dataregion.wal.io.WALFileVersion;
@@ -311,11 +312,92 @@ public class ProgressWALIteratorTest {
} finally {
Files.deleteIfExists(firstBrokenWal.toPath());
Files.deleteIfExists(secondBrokenWal.toPath());
+ Files.deleteIfExists(
+ firstBrokenWal.toPath().resolveSibling(firstBrokenWal.getName() +
".broken"));
+ Files.deleteIfExists(
+ secondBrokenWal.toPath().resolveSibling(secondBrokenWal.getName() +
".broken"));
Files.deleteIfExists(lastWal.toPath());
Files.deleteIfExists(dir);
}
}
+ @Test
+ public void
testIteratorSkipsUnreadableEntryAndPreservesCompletedPendingRequest()
+ throws Exception {
+ final Path dir =
Files.createTempDirectory("progress-wal-iterator-unreadable-entry");
+ final File dataWal =
+ dir.resolve(WALFileUtils.getLogFileName(0, 0,
WALFileStatus.CONTAINS_SEARCH_INDEX))
+ .toFile();
+ final File successorWal =
+ dir.resolve(WALFileUtils.getLogFileName(1, 3,
WALFileStatus.CONTAINS_SEARCH_INDEX))
+ .toFile();
+ final int originalEntrySizeLimit =
+ IoTDBDescriptor.getInstance().getConfig().getWalEntrySizeLimitInByte();
+
+ try {
+ IoTDBDescriptor.getInstance().getConfig().setWalEntrySizeLimitInByte(64);
+ try (WALWriter writer = new WALWriter(dataWal, WALFileVersion.V3)) {
+ writer.write(searchableEntry(1L), singleEntryMeta(19, 1L, 1L, 100L, 7,
1L));
+ writer.write(oversizedSearchableEntry(65, 2L), singleEntryMeta(65, 2L,
1L, 200L, 7, 2L));
+ }
+ try (WALWriter writer = new WALWriter(successorWal, WALFileVersion.V3)) {
+ writer.write(searchableEntry(3L), singleEntryMeta(19, 3L, 1L, 300L, 7,
3L));
+ }
+
+ try (ProgressWALIterator iterator = new
ProgressWALIterator(dir.toFile(), Long.MIN_VALUE)) {
+ assertTrue(iterator.hasNext());
+ assertEquals(1L, iterator.next().getSearchIndex());
+ assertTrue(iterator.hasNext());
+ assertEquals(3L, iterator.next().getSearchIndex());
+ assertFalse(iterator.hasNext());
+ assertEquals(1, iterator.getSkippedBrokenWalFileCount());
+ assertTrue(iterator.hasIncompleteScan());
+ }
+ } finally {
+
IoTDBDescriptor.getInstance().getConfig().setWalEntrySizeLimitInByte(originalEntrySizeLimit);
+ Files.deleteIfExists(dataWal.toPath());
+ Files.deleteIfExists(successorWal.toPath());
+ Files.deleteIfExists(dir);
+ }
+ }
+
+ @Test
+ public void
testIteratorDropsPendingRequestWhenUnreadableEntryContinuesSameWriterProgress()
+ throws Exception {
+ final Path dir =
Files.createTempDirectory("progress-wal-iterator-incomplete-request");
+ final File dataWal =
+ dir.resolve(WALFileUtils.getLogFileName(0, 0,
WALFileStatus.CONTAINS_SEARCH_INDEX))
+ .toFile();
+ final File successorWal =
+ dir.resolve(WALFileUtils.getLogFileName(1, 3,
WALFileStatus.CONTAINS_SEARCH_INDEX))
+ .toFile();
+ final int originalEntrySizeLimit =
+ IoTDBDescriptor.getInstance().getConfig().getWalEntrySizeLimitInByte();
+
+ try {
+ IoTDBDescriptor.getInstance().getConfig().setWalEntrySizeLimitInByte(64);
+ try (WALWriter writer = new WALWriter(dataWal, WALFileVersion.V3)) {
+ writer.write(searchableEntry(1L), singleEntryMeta(19, 1L, 1L, 100L, 7,
1L));
+ writer.write(oversizedSearchableEntry(65, 2L), singleEntryMeta(65, 2L,
1L, 100L, 7, 1L));
+ }
+ try (WALWriter writer = new WALWriter(successorWal, WALFileVersion.V3)) {
+ writer.write(searchableEntry(3L), singleEntryMeta(19, 3L, 1L, 300L, 7,
3L));
+ }
+
+ try (ProgressWALIterator iterator = new
ProgressWALIterator(dir.toFile(), Long.MIN_VALUE)) {
+ assertTrue(iterator.hasNext());
+ assertEquals(3L, iterator.next().getSearchIndex());
+ assertFalse(iterator.hasNext());
+ assertEquals(1, iterator.getSkippedBrokenWalFileCount());
+ }
+ } finally {
+
IoTDBDescriptor.getInstance().getConfig().setWalEntrySizeLimitInByte(originalEntrySizeLimit);
+ Files.deleteIfExists(dataWal.toPath());
+ Files.deleteIfExists(successorWal.toPath());
+ Files.deleteIfExists(dir);
+ }
+ }
+
@Test
public void testLiveWalReopenReusesMetadataSnapshot() throws Exception {
final Path dir =
Files.createTempDirectory("progress-wal-iterator-live-snapshot");
@@ -635,6 +717,16 @@ public class ProgressWALIteratorTest {
return buffer;
}
+ private static ByteBuffer oversizedSearchableEntry(final int size, final
long bodySearchIndex) {
+ final ByteBuffer buffer = ByteBuffer.allocate(size);
+ buffer.put(WALEntryType.INSERT_ROW_NODE.getCode());
+ buffer.putLong(1L);
+ buffer.putShort(PlanNodeType.INSERT_ROW.getNodeType());
+ buffer.putLong(bodySearchIndex);
+ buffer.position(size);
+ return buffer;
+ }
+
private static WALMetaData singleEntryMeta(
final int size,
final long searchIndex,
diff --git
a/iotdb-core/node-commons/src/assembly/resources/conf/edge/iotdb-system.properties
b/iotdb-core/node-commons/src/assembly/resources/conf/edge/iotdb-system.properties
index 82679f99e6a..1ec4ad2fe30 100644
---
a/iotdb-core/node-commons/src/assembly/resources/conf/edge/iotdb-system.properties
+++
b/iotdb-core/node-commons/src/assembly/resources/conf/edge/iotdb-system.properties
@@ -108,6 +108,7 @@ max_allowed_concurrent_queries=100
# ---- memory / file buffers ----
wal_buffer_size_in_byte=1048576
+wal_entry_size_limit_in_byte=-1
group_size_in_byte=4194304
target_chunk_point_num=10000
target_chunk_size=262144
diff --git
a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
index ce806f57541..158a079be6b 100644
---
a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
+++
b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
@@ -1787,6 +1787,12 @@ wal_sync_mode_fsync_delay_in_ms=3
# Datatype: int
wal_buffer_size_in_byte=33554432
+# Maximum size of one WAL entry
+# If it is non-positive, follow wal_buffer_size_in_byte.
+# effectiveMode: restart
+# Datatype: int
+wal_entry_size_limit_in_byte=-1
+
# Whether to cache the sorted WAL file list in each WAL node.
# Enabling this avoids scanning and sorting the WAL directory whenever the
file list is accessed,
# including when a consensus request reader updates its files to search.