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 9e40726175d Reuse tablet device ranges during writes (#18734)
9e40726175d is described below

commit 9e40726175d4305bdaf5829f6b9725778a07d422
Author: Caideyipi <[email protected]>
AuthorDate: Tue Sep 29 14:44:04 2026 +0800

    Reuse tablet device ranges during writes (#18734)
---
 .../dataregion/memtable/AbstractMemTable.java      |  32 ++-
 .../dataregion/memtable/IMemTable.java             |  10 +
 .../dataregion/memtable/TsFileProcessor.java       |  80 +++++-
 .../TabletDeviceRangeReusePerformanceTest.java     | 283 +++++++++++++++++++++
 .../dataregion/memtable/TsFileProcessorTest.java   |  57 +++++
 5 files changed, 448 insertions(+), 14 deletions(-)

diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AbstractMemTable.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AbstractMemTable.java
index e24308cf505..35a3696630e 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AbstractMemTable.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AbstractMemTable.java
@@ -315,10 +315,26 @@ public abstract class AbstractMemTable implements 
IMemTable {
   public int insertAlignedTablet(
       InsertTabletNode insertTabletNode, int start, int end, TSStatus[] 
results)
       throws WriteProcessException {
+    try {
+      return insertAlignedTablet(
+          insertTabletNode, start, end, results, 
insertTabletNode.splitByDevice(start, end));
+    } catch (RuntimeException e) {
+      throw new WriteProcessException(e);
+    }
+  }
+
+  @Override
+  public int insertAlignedTablet(
+      InsertTabletNode insertTabletNode,
+      int start,
+      int end,
+      TSStatus[] results,
+      List<Pair<IDeviceID, Integer>> deviceEndOffsetPairs)
+      throws WriteProcessException {
     try {
       int nullPointsNumber =
           computeTabletNullPointsNumber(insertTabletNode, start, end, true, 
results);
-      writeAlignedTablet(insertTabletNode, start, end, results);
+      writeAlignedTablet(insertTabletNode, start, end, results, 
deviceEndOffsetPairs);
       // TODO-Table: what is the relation between this and 
TsFileProcessor.checkMemCost
       memSize += MemUtils.getAlignedTabletSize(insertTabletNode, start, end, 
results);
       int validValueCount =
@@ -449,6 +465,16 @@ public abstract class AbstractMemTable implements 
IMemTable {
 
   public void writeAlignedTablet(
       InsertTabletNode insertTabletNode, int start, int end, TSStatus[] 
results) {
+    writeAlignedTablet(
+        insertTabletNode, start, end, results, 
insertTabletNode.splitByDevice(start, end));
+  }
+
+  public void writeAlignedTablet(
+      InsertTabletNode insertTabletNode,
+      int start,
+      int end,
+      TSStatus[] results,
+      List<Pair<IDeviceID, Integer>> deviceEndOffsetPairs) {
     List<IMeasurementSchema> schemaList = new ArrayList<>();
     final int measurementSize =
         insertTabletNode.getMeasurements() == null ? 0 : 
insertTabletNode.getMeasurements().length;
@@ -474,10 +500,8 @@ public abstract class AbstractMemTable implements 
IMemTable {
     if (schemaList.isEmpty()) {
       return;
     }
-    final List<Pair<IDeviceID, Integer>> deviceEndOffsetPair =
-        insertTabletNode.splitByDevice(start, end);
     int splitStart = start;
-    for (Pair<IDeviceID, Integer> pair : deviceEndOffsetPair) {
+    for (Pair<IDeviceID, Integer> pair : deviceEndOffsetPairs) {
       final IDeviceID deviceID = pair.left;
       int splitEnd = pair.right;
       IWritableMemChunkGroup memChunkGroup =
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/IMemTable.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/IMemTable.java
index 4d38fea2c11..afa24083ff6 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/IMemTable.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/IMemTable.java
@@ -123,6 +123,16 @@ public interface IMemTable extends WALEntryValue {
   int insertAlignedTablet(InsertTabletNode insertTabletNode, int start, int 
end, TSStatus[] results)
       throws WriteProcessException;
 
+  default int insertAlignedTablet(
+      InsertTabletNode insertTabletNode,
+      int start,
+      int end,
+      TSStatus[] results,
+      List<Pair<IDeviceID, Integer>> deviceEndOffsetPairs)
+      throws WriteProcessException {
+    return insertAlignedTablet(insertTabletNode, start, end, results);
+  }
+
   ReadOnlyMemChunk query(
       QueryContext context,
       IFullPath fullPath,
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
index de1a5a09b7d..6724d196443 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessor.java
@@ -544,19 +544,33 @@ public class TsFileProcessor {
     walNode.onMemTableCreated(workMemTable, tsFileResource.getTsFilePath());
   }
 
+  private static List<List<Pair<IDeviceID, Integer>>> splitTabletByDevice(
+      InsertTabletNode insertTabletNode, List<int[]> rangeList) {
+    List<List<Pair<IDeviceID, Integer>>> deviceEndOffsetPairsList =
+        new ArrayList<>(rangeList.size());
+    for (int[] range : rangeList) {
+      deviceEndOffsetPairsList.add(insertTabletNode.splitByDevice(range[0], 
range[1]));
+    }
+    return deviceEndOffsetPairsList;
+  }
+
   private long[] scheduleMemoryBlock(
       InsertTabletNode insertTabletNode,
       List<int[]> rangeList,
+      List<List<Pair<IDeviceID, Integer>>> deviceEndOffsetPairsList,
       TSStatus[] results,
       long[] infoForMetrics)
       throws WriteProcessException {
     long memControlStartTime = System.nanoTime();
     long[] totalMemIncrements = new long[NUM_MEM_TO_ESTIMATE];
     try {
-      for (int[] range : rangeList) {
+      for (int rangeIndex = 0; rangeIndex < rangeList.size(); ++rangeIndex) {
+        int[] range = rangeList.get(rangeIndex);
         int start = range[0];
         int end = range[1];
-        long[] memIncrements = checkMemCost(insertTabletNode, start, end, 
results);
+        long[] memIncrements =
+            checkMemCost(
+                insertTabletNode, start, end, 
deviceEndOffsetPairsList.get(rangeIndex), results);
         for (int i = 0; i < memIncrements.length; i++) {
           totalMemIncrements[i] += memIncrements[i];
         }
@@ -578,11 +592,15 @@ public class TsFileProcessor {
   }
 
   private long[] checkMemCost(
-      InsertTabletNode insertTabletNode, int start, int end, TSStatus[] 
results)
+      InsertTabletNode insertTabletNode,
+      int start,
+      int end,
+      List<Pair<IDeviceID, Integer>> deviceEndOffsetPairs,
+      TSStatus[] results)
       throws WriteProcessException {
     long[] memIncrements;
     if (insertTabletNode.isAligned()) {
-      memIncrements = checkAlignedMemCost(insertTabletNode, start, end, 
results);
+      memIncrements = checkAlignedMemCost(insertTabletNode, start, 
deviceEndOffsetPairs, results);
     } else {
       memIncrements =
           checkMemCostAndAddToTspInfoForTablet(
@@ -598,9 +616,11 @@ public class TsFileProcessor {
   }
 
   private long[] checkAlignedMemCost(
-      InsertTabletNode insertTabletNode, int start, int end, TSStatus[] 
results)
+      InsertTabletNode insertTabletNode,
+      int start,
+      List<Pair<IDeviceID, Integer>> deviceEndPosList,
+      TSStatus[] results)
       throws WriteProcessException {
-    List<Pair<IDeviceID, Integer>> deviceEndPosList = 
insertTabletNode.splitByDevice(start, end);
     long[] memIncrements = new long[NUM_MEM_TO_ESTIMATE];
     int splitStart = start;
     for (Pair<IDeviceID, Integer> iDeviceIDIntegerPair : deviceEndPosList) {
@@ -650,11 +670,15 @@ public class TsFileProcessor {
 
     ensureMemTable(infoForMetrics);
     workMemTable.checkDataType(insertTabletNode);
+    List<List<Pair<IDeviceID, Integer>>> deviceEndOffsetPairsList =
+        splitTabletByDevice(insertTabletNode, rangeList);
     AlignedTVListRamCostSnapshot alignedRamCostSnapshot =
-        takeAlignedTVListRamCostSnapshot(workMemTable, insertTabletNode, 
rangeList);
+        takeAlignedTVListRamCostSnapshotFromDeviceRanges(
+            workMemTable, insertTabletNode, deviceEndOffsetPairsList);
 
     long[] memIncrements =
-        scheduleMemoryBlock(insertTabletNode, rangeList, results, 
infoForMetrics);
+        scheduleMemoryBlock(
+            insertTabletNode, rangeList, deviceEndOffsetPairsList, results, 
infoForMetrics);
 
     clearDataRegionReplicaSet(insertTabletNode);
     long startTime = System.nanoTime();
@@ -708,7 +732,11 @@ public class TsFileProcessor {
           if (insertTabletNode.isAligned()) {
             pointInserted +=
                 workMemTable.insertAlignedTablet(
-                    insertTabletNode, start, end, noFailure ? null : results);
+                    insertTabletNode,
+                    start,
+                    end,
+                    noFailure ? null : results,
+                    deviceEndOffsetPairsList.get(rangeIndex));
           } else {
             pointInserted += workMemTable.insertTablet(insertTabletNode, 
start, end);
           }
@@ -732,7 +760,7 @@ public class TsFileProcessor {
         }
 
         final List<Pair<IDeviceID, Integer>> deviceEndOffsetPairs =
-            insertTabletNode.splitByDevice(start, end);
+            deviceEndOffsetPairsList.get(rangeIndex);
         tsFileResource.updateStartTime(
             deviceEndOffsetPairs.get(0).left, 
insertTabletNode.getTimes()[start]);
         if (!sequence) {
@@ -1409,6 +1437,38 @@ public class TsFileProcessor {
         : new AlignedTVListRamCostSnapshot(memTable, alignedDeviceIds);
   }
 
+  private static AlignedTVListRamCostSnapshot 
takeAlignedTVListRamCostSnapshotFromDeviceRanges(
+      IMemTable memTable,
+      InsertTabletNode insertTabletNode,
+      List<List<Pair<IDeviceID, Integer>>> deviceEndOffsetPairsList) {
+    if (!insertTabletNode.isAligned() || deviceEndOffsetPairsList.isEmpty()) {
+      return null;
+    }
+
+    IDeviceID singleDeviceId = null;
+    Set<IDeviceID> alignedDeviceIds = null;
+    for (List<Pair<IDeviceID, Integer>> deviceEndOffsetPairs : 
deviceEndOffsetPairsList) {
+      for (Pair<IDeviceID, Integer> deviceEndOffsetPair : 
deviceEndOffsetPairs) {
+        IDeviceID deviceId = deviceEndOffsetPair.getLeft();
+        if (singleDeviceId == null) {
+          singleDeviceId = deviceId;
+        } else if (!singleDeviceId.equals(deviceId)) {
+          if (alignedDeviceIds == null) {
+            alignedDeviceIds = new HashSet<>();
+            alignedDeviceIds.add(singleDeviceId);
+          }
+          alignedDeviceIds.add(deviceId);
+        }
+      }
+    }
+    if (alignedDeviceIds != null) {
+      return new AlignedTVListRamCostSnapshot(memTable, alignedDeviceIds);
+    }
+    return singleDeviceId == null
+        ? null
+        : new AlignedTVListRamCostSnapshot(memTable, singleDeviceId);
+  }
+
   static final class AlignedTVListRamCostSnapshot {
 
     private final IMemTable memTable;
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TabletDeviceRangeReusePerformanceTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TabletDeviceRangeReusePerformanceTest.java
new file mode 100644
index 00000000000..ebc004d9719
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TabletDeviceRangeReusePerformanceTest.java
@@ -0,0 +1,283 @@
+/*
+ * 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.iotdb.db.storageengine.dataregion.memtable;
+
+import org.apache.iotdb.commons.exception.IllegalPathException;
+import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
+import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.RelationalInsertTabletNode;
+import org.apache.iotdb.db.utils.ManualPerformanceTestUtils;
+import org.apache.iotdb.db.utils.ManualPerformanceTestUtils.Measurement;
+import org.apache.iotdb.db.utils.ManualPerformanceTestUtils.Summary;
+
+import org.apache.tsfile.file.metadata.IDeviceID;
+import org.apache.tsfile.utils.Pair;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class TabletDeviceRangeReusePerformanceTest {
+
+  private static final String ENABLED_PROPERTY = 
"iotdb.tablet.device.ranges.perf.enabled";
+  private static final String ROWS_PROPERTY = 
"iotdb.tablet.device.ranges.perf.rows";
+  private static final String DEVICES_PROPERTY = 
"iotdb.tablet.device.ranges.perf.devices";
+  private static final String WARMUP_ITERATIONS_PROPERTY =
+      "iotdb.tablet.device.ranges.perf.warmup.iterations";
+  private static final String ITERATIONS_PROPERTY = 
"iotdb.tablet.device.ranges.perf.iterations";
+  private static final String ROUNDS_PROPERTY = 
"iotdb.tablet.device.ranges.perf.rounds";
+  private static volatile int benchmarkBlackhole;
+
+  @Test
+  public void tabletDeviceRangeReuseBenchmark() throws IllegalPathException {
+    Assume.assumeTrue(
+        String.format(
+            "Manual performance UT. Enable with -D%s=true, optionally tune 
-D%s, -D%s, -D%s, -D%s and -D%s.",
+            ENABLED_PROPERTY,
+            ROWS_PROPERTY,
+            DEVICES_PROPERTY,
+            WARMUP_ITERATIONS_PROPERTY,
+            ITERATIONS_PROPERTY,
+            ROUNDS_PROPERTY),
+        Boolean.getBoolean(ENABLED_PROPERTY));
+    Assume.assumeTrue(
+        "Current-thread CPU time and allocation metrics are required.",
+        ManualPerformanceTestUtils.enableThreadMetrics());
+
+    final int rowCount = Integer.getInteger(ROWS_PROPERTY, 4096);
+    final int deviceCount = Integer.getInteger(DEVICES_PROPERTY, 64);
+    final int warmupIterations = 
Integer.getInteger(WARMUP_ITERATIONS_PROPERTY, 1000);
+    final int iterations = Integer.getInteger(ITERATIONS_PROPERTY, 10_000);
+    final int rounds = Integer.getInteger(ROUNDS_PROPERTY, 7);
+    Assert.assertTrue(rowCount > 0);
+    Assert.assertTrue(deviceCount > 0);
+    Assert.assertTrue(rowCount >= deviceCount);
+    Assert.assertTrue(warmupIterations > 0);
+    Assert.assertTrue(iterations > 0);
+    Assert.assertTrue(rounds > 0);
+
+    final BenchmarkNode node = new BenchmarkNode(rowCount, deviceCount);
+    final List<int[]> ranges = Collections.singletonList(new int[] {0, 
rowCount});
+    runLegacy(node, ranges, warmupIterations);
+    runCached(node, ranges, warmupIterations);
+    node.resetCalls();
+
+    final Measurement[] legacyMeasurements = new Measurement[rounds];
+    final Measurement[] cachedMeasurements = new Measurement[rounds];
+    for (int round = 0; round < rounds; ++round) {
+      if ((round & 1) == 0) {
+        legacyMeasurements[round] = measureLegacy(node, ranges, iterations);
+        cachedMeasurements[round] = measureCached(node, ranges, iterations);
+      } else {
+        cachedMeasurements[round] = measureCached(node, ranges, iterations);
+        legacyMeasurements[round] = measureLegacy(node, ranges, iterations);
+      }
+    }
+
+    final Summary legacySummary =
+        ManualPerformanceTestUtils.summarize(legacyMeasurements, iterations);
+    final Summary cachedSummary =
+        ManualPerformanceTestUtils.summarize(cachedMeasurements, iterations);
+    final long expectedLegacySplits = (long) iterations * rounds * 4;
+    final long expectedCachedSplits = iterations * rounds;
+    Assert.assertEquals(expectedLegacySplits, node.legacySplitCalls.get());
+    Assert.assertEquals(expectedCachedSplits, node.cachedSplitCalls.get());
+    Assert.assertEquals(node.cachedDeviceIdCalls.get() * 4, 
node.legacyDeviceIdCalls.get());
+    printResult(
+        rowCount,
+        deviceCount,
+        warmupIterations,
+        iterations,
+        rounds,
+        legacySummary,
+        cachedSummary,
+        node.legacySplitCalls.get(),
+        node.cachedSplitCalls.get(),
+        node.legacyDeviceIdCalls.get(),
+        node.cachedDeviceIdCalls.get());
+  }
+
+  private static Measurement measureLegacy(BenchmarkNode node, List<int[]> 
ranges, int iterations) {
+    return ManualPerformanceTestUtils.measure(iterations, () -> 
runLegacyOnce(node, ranges));
+  }
+
+  private static Measurement measureCached(BenchmarkNode node, List<int[]> 
ranges, int iterations) {
+    return ManualPerformanceTestUtils.measure(iterations, () -> 
runCachedOnce(node, ranges));
+  }
+
+  private static void runLegacy(BenchmarkNode node, List<int[]> ranges, int 
iterations) {
+    for (int i = 0; i < iterations; ++i) {
+      runLegacyOnce(node, ranges);
+    }
+  }
+
+  private static void runCached(BenchmarkNode node, List<int[]> ranges, int 
iterations) {
+    for (int i = 0; i < iterations; ++i) {
+      runCachedOnce(node, ranges);
+    }
+  }
+
+  private static void runLegacyOnce(BenchmarkNode node, List<int[]> ranges) {
+    long checksum = 0;
+    for (int[] range : ranges) {
+      List<Pair<IDeviceID, Integer>> ramSnapshotRanges =
+          node.legacySplitByDevice(range[0], range[1]);
+      checksum += ramSnapshotRanges.size();
+    }
+    for (int[] range : ranges) {
+      List<Pair<IDeviceID, Integer>> memoryCheckRanges =
+          node.legacySplitByDevice(range[0], range[1]);
+      checksum += memoryCheckRanges.size();
+    }
+    for (int[] range : ranges) {
+      List<Pair<IDeviceID, Integer>> memTableWriteRanges =
+          node.legacySplitByDevice(range[0], range[1]);
+      checksum += memTableWriteRanges.size();
+    }
+    for (int[] range : ranges) {
+      List<Pair<IDeviceID, Integer>> resourceUpdateRanges =
+          node.legacySplitByDevice(range[0], range[1]);
+      checksum += resourceUpdateRanges.size();
+    }
+    benchmarkBlackhole = (int) checksum;
+  }
+
+  private static void runCachedOnce(BenchmarkNode node, List<int[]> ranges) {
+    List<List<Pair<IDeviceID, Integer>>> deviceRanges = new 
ArrayList<>(ranges.size());
+    for (int[] range : ranges) {
+      deviceRanges.add(node.cachedSplitByDevice(range[0], range[1]));
+    }
+    long checksum = 0;
+    for (List<Pair<IDeviceID, Integer>> deviceEndOffsetPairs : deviceRanges) {
+      checksum += deviceEndOffsetPairs.size() * 4L;
+    }
+    benchmarkBlackhole = (int) checksum;
+  }
+
+  private static void printResult(
+      int rowCount,
+      int deviceCount,
+      int warmupIterations,
+      int iterations,
+      int rounds,
+      Summary legacySummary,
+      Summary cachedSummary,
+      long legacySplitCalls,
+      long cachedSplitCalls,
+      long legacyDeviceIdCalls,
+      long cachedDeviceIdCalls) {
+    System.out.printf(
+        Locale.ROOT,
+        "Tablet device range reuse benchmark: rows=%d, devices=%d, warmup=%d, 
iterations=%d, rounds=%d%n"
+            + "legacy repeated split: cpu=%.2f ns/op, allocation=%.2f 
bytes/op, splitCalls=%d, getDeviceIDCalls=%d%n"
+            + "cached ranges: cpu=%.2f ns/op, allocation=%.2f bytes/op, 
splitCalls=%d, getDeviceIDCalls=%d%n"
+            + "reduction: cpu=%.2f%%, allocation=%.2f%%%n",
+        rowCount,
+        deviceCount,
+        warmupIterations,
+        iterations,
+        rounds,
+        legacySummary.getCpuNanosPerOperation(),
+        legacySummary.getAllocatedBytesPerOperation(),
+        legacySplitCalls,
+        legacyDeviceIdCalls,
+        cachedSummary.getCpuNanosPerOperation(),
+        cachedSummary.getAllocatedBytesPerOperation(),
+        cachedSplitCalls,
+        cachedDeviceIdCalls,
+        reduction(legacySummary.getCpuNanosPerOperation(), 
cachedSummary.getCpuNanosPerOperation()),
+        reduction(
+            legacySummary.getAllocatedBytesPerOperation(),
+            cachedSummary.getAllocatedBytesPerOperation()));
+  }
+
+  private static double reduction(double before, double after) {
+    return before == 0 ? 0 : (before - after) * 100 / before;
+  }
+
+  private static final class BenchmarkNode extends RelationalInsertTabletNode {
+
+    private final IDeviceID[] deviceIds;
+    private final int rowsPerDevice;
+    private final AtomicLong legacySplitCalls = new AtomicLong();
+    private final AtomicLong cachedSplitCalls = new AtomicLong();
+    private final AtomicLong legacyDeviceIdCalls = new AtomicLong();
+    private final AtomicLong cachedDeviceIdCalls = new AtomicLong();
+
+    private BenchmarkNode(int rowCount, int deviceCount) throws 
IllegalPathException {
+      super(new PlanNodeId("tablet-device-range-benchmark"));
+      deviceIds = new IDeviceID[deviceCount];
+      for (int i = 0; i < deviceCount; ++i) {
+        deviceIds[i] = 
IDeviceID.Factory.DEFAULT_FACTORY.create("root.benchmark.d" + i);
+      }
+      rowsPerDevice = Math.max(1, rowCount / deviceCount);
+      setAligned(true);
+    }
+
+    @Override
+    public IDeviceID getDeviceID(int rowIdx) {
+      return deviceIds[Math.min(rowIdx / rowsPerDevice, deviceIds.length - 1)];
+    }
+
+    @Override
+    public List<Pair<IDeviceID, Integer>> splitByDevice(int start, int end) {
+      return split(start, end, legacySplitCalls, legacyDeviceIdCalls);
+    }
+
+    private List<Pair<IDeviceID, Integer>> legacySplitByDevice(int start, int 
end) {
+      return split(start, end, legacySplitCalls, legacyDeviceIdCalls);
+    }
+
+    private List<Pair<IDeviceID, Integer>> cachedSplitByDevice(int start, int 
end) {
+      return split(start, end, cachedSplitCalls, cachedDeviceIdCalls);
+    }
+
+    private List<Pair<IDeviceID, Integer>> split(
+        int start, int end, AtomicLong splitCounter, AtomicLong 
deviceIdCounter) {
+      splitCounter.incrementAndGet();
+      final List<Pair<IDeviceID, Integer>> result = new ArrayList<>();
+      IDeviceID previous = getDeviceID(start, deviceIdCounter);
+      for (int i = start + 1; i < end; ++i) {
+        IDeviceID current = getDeviceID(i, deviceIdCounter);
+        if (!current.equals(previous)) {
+          result.add(new Pair<>(previous, i));
+          previous = getDeviceID(i, deviceIdCounter);
+        }
+      }
+      result.add(new Pair<>(previous, end));
+      return result;
+    }
+
+    private IDeviceID getDeviceID(int rowIdx, AtomicLong counter) {
+      counter.incrementAndGet();
+      return deviceIds[Math.min(rowIdx / rowsPerDevice, deviceIds.length - 1)];
+    }
+
+    private void resetCalls() {
+      legacySplitCalls.set(0);
+      cachedSplitCalls.set(0);
+      legacyDeviceIdCalls.set(0);
+      cachedDeviceIdCalls.set(0);
+    }
+  }
+}
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorTest.java
index e1b402a64dd..f1ca1d597c3 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/TsFileProcessorTest.java
@@ -86,6 +86,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import static junit.framework.TestCase.assertTrue;
 import static 
org.apache.iotdb.db.storageengine.dataregion.DataRegionTest.buildInsertRowNodeByTSRecord;
@@ -762,6 +763,25 @@ public class TsFileProcessorTest {
     Assert.assertEquals(1, relationalSplitCalls[0]);
   }
 
+  @Test
+  public void alignedTabletReusesDeviceRangesAcrossWriteStages()
+      throws IOException, WriteProcessException, IllegalPathException {
+    processor = newTestProcessor(filePath + ".device-range-reuse");
+    final AtomicInteger splitCalls = new AtomicInteger();
+    final int rowCount = 4;
+    final List<int[]> rangeList =
+        Arrays.asList(new int[] {0, rowCount / 2}, new int[] {rowCount / 2, 
rowCount});
+
+    processor.insertTablet(
+        genCountingAlignedTablet(new String[] {"s0"}, rowCount, 0, splitCalls),
+        rangeList,
+        new TSStatus[rowCount],
+        true,
+        new long[5]);
+
+    Assert.assertEquals(rangeList.size(), splitCalls.get());
+  }
+
   @Test
   public void alignedTvListRamCostTest2()
       throws MetadataException, WriteProcessException, IOException {
@@ -1596,6 +1616,43 @@ public class TsFileProcessorTest {
         rowCount);
   }
 
+  private InsertTabletNode genCountingAlignedTablet(
+      String[] measurements, int rowCount, long startTime, AtomicInteger 
splitCalls)
+      throws IllegalPathException {
+    TSDataType[] dataTypes = new TSDataType[measurements.length];
+    MeasurementSchema[] schemas = new MeasurementSchema[measurements.length];
+    Object[] columns = new Object[measurements.length];
+    for (int i = 0; i < measurements.length; i++) {
+      dataTypes[i] = TSDataType.INT32;
+      schemas[i] = new MeasurementSchema(measurements[i], TSDataType.INT32, 
encoding);
+      columns[i] = new int[rowCount];
+    }
+    long[] times = new long[rowCount];
+    for (int row = 0; row < rowCount; row++) {
+      times[row] = startTime + row;
+      for (Object column : columns) {
+        ((int[]) column)[row] = row;
+      }
+    }
+    return new InsertTabletNode(
+        new QueryId("test_write").genPlanNodeId(),
+        new PartialPath(deviceId),
+        true,
+        measurements,
+        dataTypes,
+        schemas,
+        times,
+        null,
+        columns,
+        rowCount) {
+      @Override
+      public List<Pair<IDeviceID, Integer>> splitByDevice(int start, int end) {
+        splitCalls.incrementAndGet();
+        return super.splitByDevice(start, end);
+      }
+    };
+  }
+
   private InsertTabletNode genInsertTableNode(long startTime, boolean 
isAligned)
       throws IllegalPathException {
     String deviceId = "root.sg.device5";

Reply via email to