This is an automated email from the ASF dual-hosted git repository.

jt2594838 pushed a commit to branch dev/1.3
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/dev/1.3 by this push:
     new 251cf340b71 Optimize aligned row and tablet memory estimation (#18426)
251cf340b71 is described below

commit 251cf340b71a7f6d45b41ad7727bf5cb90f9bffb
Author: Jiang Tian <[email protected]>
AuthorDate: Mon Aug 10 16:00:29 2026 +0800

    Optimize aligned row and tablet memory estimation (#18426)
---
 .../memtable/AlignedWritableMemChunk.java          | 136 +++++++
 .../dataregion/memtable/TsFileProcessor.java       | 323 +++++++++-------
 .../db/utils/datastructure/AlignedTVList.java      | 421 ++++++++++++++++-----
 .../AlignedRowsBranchComparisonBenchmarkTest.java  | 286 ++++++++++++++
 .../dataregion/memtable/TsFileProcessorTest.java   | 118 +++++-
 .../db/utils/datastructure/AlignedTVListTest.java  |  79 +++-
 6 files changed, 1129 insertions(+), 234 deletions(-)

diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedWritableMemChunk.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedWritableMemChunk.java
index 922153b8d68..4a0043c1559 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedWritableMemChunk.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedWritableMemChunk.java
@@ -21,8 +21,10 @@ package 
org.apache.iotdb.db.storageengine.dataregion.memtable;
 
 import org.apache.iotdb.db.conf.IoTDBConfig;
 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.buffer.IWALByteBufferView;
 import org.apache.iotdb.db.storageengine.dataregion.wal.utils.WALWriteUtils;
+import org.apache.iotdb.db.utils.MemUtils;
 import org.apache.iotdb.db.utils.datastructure.AlignedTVList;
 import org.apache.iotdb.db.utils.datastructure.BatchEncodeInfo;
 import org.apache.iotdb.db.utils.datastructure.MemPointIterator;
@@ -46,6 +48,7 @@ import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -58,6 +61,15 @@ import java.util.concurrent.atomic.AtomicInteger;
 import static 
org.apache.iotdb.db.storageengine.rescon.memory.PrimitiveArrayManager.ARRAY_SIZE;
 import static org.apache.iotdb.db.utils.ModificationUtils.isPointDeleted;
 
+interface AlignedRowsMemCostEstimator {
+
+  void addRow(InsertRowNode row);
+
+  long getTVListMemoryCost();
+
+  long getTextDataMemoryCost();
+}
+
 public class AlignedWritableMemChunk extends AbstractWritableMemChunk {
 
   private final Map<String, Integer> measurementIndexMap;
@@ -104,6 +116,130 @@ public class AlignedWritableMemChunk extends 
AbstractWritableMemChunk {
     return measurementIndexMap.containsKey(measurementId);
   }
 
+  public long alignedWriteRowMemCost(
+      String[] measurements, TSDataType[] incomingDataTypes, Object[] 
rowValues, int rowOffset) {
+    return list.alignedWriteRowMemCost(
+        mapMeasurementsToTVListColumns(measurements), incomingDataTypes, 
rowValues, rowOffset);
+  }
+
+  public long alignedWriteArrayMemCost(
+      String[] measurements, TSDataType[] incomingDataTypes, Object[] columns, 
int start, int end) {
+    return list.alignedWriteArrayMemCost(
+        mapMeasurementsToTVListColumns(measurements), incomingDataTypes, 
columns, start, end);
+  }
+
+  public long alignedWriteRowsMemCost(List<InsertRowNode> rows) {
+    AlignedTVList workingList = list;
+    synchronized (workingList) {
+      AlignedRowsMemCostEstimator estimator =
+          new ExistingAlignedRowsMemCostEstimator(workingList, false);
+      for (InsertRowNode row : rows) {
+        estimator.addRow(row);
+      }
+      return estimator.getTVListMemoryCost();
+    }
+  }
+
+  // TsFileProcessor serializes writes for a DataRegion, so the incremental 
estimator can retain a
+  // stable view of this TVList while rows from multiple devices are processed 
in input order.
+  AlignedRowsMemCostEstimator newAlignedWriteRowsMemCostEstimator() {
+    return new ExistingAlignedRowsMemCostEstimator(list, true);
+  }
+
+  private final class ExistingAlignedRowsMemCostEstimator implements 
AlignedRowsMemCostEstimator {
+    private final AlignedTVList.AlignedWriteRowsMemCostEstimator 
tvListEstimator;
+    private final Map<String, Integer> newMeasurementIndexMap = new 
HashMap<>();
+    private final boolean trackTextData;
+
+    private int nextColumnIndex = dataTypes.size();
+    private String[] previousMeasurements;
+    private int[] previousColumnIndexes = new int[0];
+    private long textDataMemoryCost;
+
+    private ExistingAlignedRowsMemCostEstimator(AlignedTVList workingList, 
boolean trackTextData) {
+      tvListEstimator = workingList.newAlignedWriteRowsMemCostEstimator();
+      this.trackTextData = trackTextData;
+    }
+
+    @Override
+    public void addRow(InsertRowNode row) {
+      tvListEstimator.startRow();
+      String[] measurements = row.getMeasurements();
+      TSDataType[] incomingDataTypes = row.getDataTypes();
+      Object[] rowValues = row.getValues();
+      int[] tvListColumnIndexes = mapMeasurements(measurements);
+      int columnCount =
+          Math.min(measurements.length, Math.min(incomingDataTypes.length, 
rowValues.length));
+      for (int column = 0; column < columnCount; column++) {
+        String measurement = measurements[column];
+        TSDataType dataType = incomingDataTypes[column];
+        Object value = rowValues[column];
+        tvListEstimator.addValue(tvListColumnIndexes[column], dataType, value);
+        if (trackTextData
+            && measurement != null
+            && dataType != null
+            && dataType.isBinary()
+            && value != null) {
+          textDataMemoryCost += MemUtils.getBinarySize((Binary) value);
+        }
+      }
+    }
+
+    private int[] mapMeasurements(String[] measurements) {
+      if (previousMeasurements != null
+          && (previousMeasurements == measurements
+              || Arrays.equals(previousMeasurements, measurements))) {
+        previousMeasurements = measurements;
+        return previousColumnIndexes;
+      }
+      if (previousColumnIndexes.length < measurements.length) {
+        previousColumnIndexes = new int[measurements.length];
+      }
+      Arrays.fill(previousColumnIndexes, 0, measurements.length, -1);
+      for (int i = 0; i < measurements.length; i++) {
+        String measurement = measurements[i];
+        if (measurement == null) {
+          continue;
+        }
+        Integer columnIndex = measurementIndexMap.get(measurement);
+        if (columnIndex == null) {
+          columnIndex = newMeasurementIndexMap.get(measurement);
+          if (columnIndex == null) {
+            columnIndex = nextColumnIndex++;
+            newMeasurementIndexMap.put(measurement, columnIndex);
+          }
+        }
+        previousColumnIndexes[i] = columnIndex;
+      }
+      previousMeasurements = measurements;
+      return previousColumnIndexes;
+    }
+
+    @Override
+    public long getTVListMemoryCost() {
+      return tvListEstimator.getMemoryCost();
+    }
+
+    @Override
+    public long getTextDataMemoryCost() {
+      return textDataMemoryCost;
+    }
+  }
+
+  private int[] mapMeasurementsToTVListColumns(String[] measurements) {
+    int[] columnIndexes = new int[measurements.length];
+    Arrays.fill(columnIndexes, -1);
+    for (int i = 0; i < measurements.length; i++) {
+      if (measurements[i] != null) {
+        Integer columnIndex = measurementIndexMap.get(measurements[i]);
+        if (columnIndex != null) {
+          columnIndexes[i] = columnIndex;
+        }
+      }
+    }
+    return columnIndexes;
+  }
+
   @Override
   public void putLong(long t, long v) {
     throw new UnSupportedDataTypeException(UNSUPPORTED_TYPE + 
TSDataType.VECTOR);
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 fff95a33d86..49e531e5915 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
@@ -97,6 +97,7 @@ import org.slf4j.LoggerFactory;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -118,6 +119,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
 import static 
org.apache.iotdb.db.queryengine.metric.QueryExecutionMetricSet.GET_QUERY_RESOURCE_FROM_MEM;
 import static 
org.apache.iotdb.db.queryengine.metric.QueryResourceMetricSet.FLUSHING_MEMTABLE;
 import static 
org.apache.iotdb.db.queryengine.metric.QueryResourceMetricSet.WORKING_MEMTABLE;
+import static org.apache.tsfile.utils.RamUsageEstimator.NUM_BYTES_OBJECT_REF;
 
 @SuppressWarnings("java:S1135") // ignore todos
 public class TsFileProcessor {
@@ -374,15 +376,7 @@ public class TsFileProcessor {
     long[] memIncrements;
     long alignedMemTableIncrement = 0;
     Set<IDeviceID> alignedDeviceIds = new HashSet<>();
-    for (InsertRowNode insertRowNode : insertRowsNode.getInsertRowNodeList()) {
-      if (insertRowNode.isAligned()) {
-        alignedDeviceIds.add(insertRowNode.getDeviceID());
-      }
-    }
-    AlignedTVListRamCostSnapshot alignedRamCostSnapshot =
-        alignedDeviceIds.isEmpty()
-            ? null
-            : new AlignedTVListRamCostSnapshot(workMemTable, alignedDeviceIds);
+    AlignedTVListRamCostSnapshot alignedRamCostSnapshot;
 
     long memControlStartTime = System.nanoTime();
     if (insertRowsNode.isMixingAlignment()) {
@@ -395,7 +389,8 @@ public class TsFileProcessor {
           nonAlignedList.add(insertRowNode);
         }
       }
-      long[] alignedMemIncrements = 
checkAlignedMemCostAndAddToTspInfoForRows(alignedList);
+      long[] alignedMemIncrements =
+          checkAlignedMemCostAndAddToTspInfoForRows(alignedList, 
alignedDeviceIds);
       alignedMemTableIncrement = alignedMemIncrements[0];
       final long[] nonAlignedMemIncrements;
       try {
@@ -411,12 +406,19 @@ public class TsFileProcessor {
     } else {
       if (insertRowsNode.isAligned()) {
         memIncrements =
-            
checkAlignedMemCostAndAddToTspInfoForRows(insertRowsNode.getInsertRowNodeList());
+            checkAlignedMemCostAndAddToTspInfoForRows(
+                insertRowsNode.getInsertRowNodeList(), alignedDeviceIds);
         alignedMemTableIncrement = memIncrements[0];
       } else {
         memIncrements = 
checkMemCostAndAddToTspInfoForRows(insertRowsNode.getInsertRowNodeList());
       }
     }
+    // Memory estimation only reserves counters. Taking the snapshot 
afterwards avoids a separate
+    // row scan while still capturing TVList state before WAL and memtable 
writes.
+    alignedRamCostSnapshot =
+        alignedDeviceIds.isEmpty()
+            ? null
+            : new AlignedTVListRamCostSnapshot(workMemTable, alignedDeviceIds);
     // recordScheduleMemoryBlockCost
     costsForMetrics[1] += System.nanoTime() - memControlStartTime;
 
@@ -722,7 +724,7 @@ public class TsFileProcessor {
       chunkMetadataIncrement +=
           ChunkMetadata.calculateRamSize(AlignedPath.VECTOR_PLACEHOLDER, 
TSDataType.VECTOR)
               * dataTypes.length;
-      memTableIncrement += AlignedTVList.alignedTvListArrayMemCost(dataTypes);
+      memTableIncrement += estimateNewAlignedRowMemCost(measurements, 
dataTypes, values);
       for (int i = 0; i < dataTypes.length; i++) {
         // Skip failed Measurements
         if (dataTypes[i] == null || measurements[i] == null) {
@@ -736,129 +738,178 @@ public class TsFileProcessor {
     } else {
       // For existed device of this mem table
       AlignedWritableMemChunk alignedMemChunk = (AlignedWritableMemChunk) 
memChunk;
-      List<TSDataType> dataTypesInTVList = new ArrayList<>();
+      memTableIncrement +=
+          alignedMemChunk.alignedWriteRowMemCost(measurements, dataTypes, 
values, 0);
       for (int i = 0; i < dataTypes.length; i++) {
         // Skip failed Measurements
         if (dataTypes[i] == null || measurements[i] == null) {
           continue;
         }
 
-        // Extending the column of aligned mem chunk
-        if (!alignedMemChunk.containsMeasurement(measurements[i])) {
-          memTableIncrement +=
-              (alignedMemChunk.alignedListSize() / 
PrimitiveArrayManager.ARRAY_SIZE + 1)
-                  * AlignedTVList.valueListArrayMemCost(dataTypes[i]);
-          dataTypesInTVList.add(dataTypes[i]);
-        }
         // TEXT data mem size
         if (dataTypes[i].isBinary() && values[i] != null) {
           textDataIncrement += MemUtils.getBinarySize((Binary) values[i]);
         }
       }
-      // Here currentChunkPointNum >= 1
-      if ((alignedMemChunk.alignedListSize() % 
PrimitiveArrayManager.ARRAY_SIZE) == 0) {
-        memTableIncrement += 
alignedMemChunk.getWorkingTVList().alignedTvListArrayMemCost();
-        for (TSDataType dataType : dataTypesInTVList) {
-          memTableIncrement += AlignedTVList.valueListArrayMemCost(dataType);
-        }
-      }
     }
     updateMemoryInfo(memTableIncrement, chunkMetadataIncrement, 
textDataIncrement);
     return new long[] {memTableIncrement, textDataIncrement, 
chunkMetadataIncrement};
   }
 
   @SuppressWarnings("squid:S3776") // high Cognitive Complexity
-  private long[] checkAlignedMemCostAndAddToTspInfoForRows(List<InsertRowNode> 
insertRowNodeList)
+  private long[] checkAlignedMemCostAndAddToTspInfoForRows(
+      List<InsertRowNode> insertRowNodeList, Set<IDeviceID> alignedDeviceIds)
       throws WriteProcessException {
     // Memory of increased PrimitiveArray and TEXT values, e.g., add a 
long[128], add 128*8
     long memTableIncrement = 0L;
     long textDataIncrement = 0L;
     long chunkMetadataIncrement = 0L;
-    // device -> (measurements -> datatype, adding aligned TVList size)
-    Map<IDeviceID, Pair<Map<String, TSDataType>, Integer>> 
increasingMemTableInfo = new HashMap<>();
+    Map<IDeviceID, AlignedRowsMemCostEstimator> estimators = new HashMap<>();
     for (InsertRowNode insertRowNode : insertRowNodeList) {
       IDeviceID deviceId = insertRowNode.getDeviceID();
-      TSDataType[] dataTypes = insertRowNode.getDataTypes();
-      Object[] values = insertRowNode.getValues();
-      String[] measurements = insertRowNode.getMeasurements();
-
-      IWritableMemChunk memChunk =
-          workMemTable.getWritableMemChunk(deviceId, 
AlignedPath.VECTOR_PLACEHOLDER);
-      if (memChunk == null && !increasingMemTableInfo.containsKey(deviceId)) {
-        // For new device of this mem table
-        // ChunkMetadataIncrement
-        chunkMetadataIncrement +=
-            ChunkMetadata.calculateRamSize(AlignedPath.VECTOR_PLACEHOLDER, 
TSDataType.VECTOR)
-                * dataTypes.length;
-        memTableIncrement += 
AlignedTVList.alignedTvListArrayMemCost(dataTypes);
-        for (int i = 0; i < dataTypes.length; i++) {
-          // Skip failed Measurements
-          if (dataTypes[i] == null || measurements[i] == null) {
-            continue;
-          }
-          increasingMemTableInfo
-              .computeIfAbsent(deviceId, k -> new Pair<>(new HashMap<>(), 1))
-              .left
-              .put(measurements[i], dataTypes[i]);
-          // TEXT data mem size
-          if (dataTypes[i].isBinary() && values[i] != null) {
-            textDataIncrement += MemUtils.getBinarySize((Binary) values[i]);
-          }
+      AlignedRowsMemCostEstimator estimator = estimators.get(deviceId);
+      if (estimator == null) {
+        IWritableMemChunk memChunk =
+            workMemTable.getWritableMemChunk(deviceId, 
AlignedPath.VECTOR_PLACEHOLDER);
+        if (memChunk == null) {
+          estimator = new NewAlignedRowsMemCostEstimator();
+          chunkMetadataIncrement +=
+              ChunkMetadata.calculateRamSize(AlignedPath.VECTOR_PLACEHOLDER, 
TSDataType.VECTOR)
+                  * insertRowNode.getDataTypes().length;
+        } else {
+          estimator = ((AlignedWritableMemChunk) 
memChunk).newAlignedWriteRowsMemCostEstimator();
         }
+        estimators.put(deviceId, estimator);
+        alignedDeviceIds.add(deviceId);
+      }
+      estimator.addRow(insertRowNode);
+    }
 
-      } else {
-        // For existed device of this mem table
-        AlignedWritableMemChunk alignedMemChunk = (AlignedWritableMemChunk) 
memChunk;
-        int currentChunkPointNum = alignedMemChunk == null ? 0 : 
alignedMemChunk.alignedListSize();
-        List<TSDataType> dataTypesInTVList = new ArrayList<>();
-        Pair<Map<String, TSDataType>, Integer> addingPointNumInfo =
-            increasingMemTableInfo.computeIfAbsent(deviceId, k -> new 
Pair<>(new HashMap<>(), 0));
-        for (int i = 0; i < dataTypes.length; i++) {
-          // Skip failed Measurements
-          if (dataTypes[i] == null || measurements[i] == null) {
-            continue;
-          }
+    for (AlignedRowsMemCostEstimator estimator : estimators.values()) {
+      memTableIncrement += estimator.getTVListMemoryCost();
+      textDataIncrement += estimator.getTextDataMemoryCost();
+    }
+    updateMemoryInfo(memTableIncrement, chunkMetadataIncrement, 
textDataIncrement);
+    return new long[] {memTableIncrement, textDataIncrement, 
chunkMetadataIncrement};
+  }
 
-          int addingPointNum = addingPointNumInfo.getRight();
-          // Extending the column of aligned mem chunk
-          boolean currentMemChunkContainsMeasurement =
-              alignedMemChunk != null && 
alignedMemChunk.containsMeasurement(measurements[i]);
-          if (!currentMemChunkContainsMeasurement
-              && !addingPointNumInfo.left.containsKey(measurements[i])) {
-            addingPointNumInfo.left.put(measurements[i], dataTypes[i]);
-            int currentArrayNum =
-                (currentChunkPointNum + addingPointNum) / 
PrimitiveArrayManager.ARRAY_SIZE
-                    + ((currentChunkPointNum + addingPointNum) % 
PrimitiveArrayManager.ARRAY_SIZE
-                            > 0
-                        ? 1
-                        : 0);
-            memTableIncrement +=
-                currentArrayNum * 
AlignedTVList.valueListArrayMemCost(dataTypes[i]);
-            addingPointNumInfo.left.put(measurements[i], dataTypes[i]);
-          }
-          // TEXT data mem size
-          if (dataTypes[i].isBinary() && values[i] != null) {
-            textDataIncrement += MemUtils.getBinarySize((Binary) values[i]);
+  @TestOnly private AlignedTVListRamCostSnapshot benchmarkAlignedRowsSnapshot;
+
+  @TestOnly
+  long[] benchmarkCheckAlignedMemCostAndAddToTspInfoForRows(
+      List<InsertRowNode> rows, Set<IDeviceID> alignedDeviceIds) throws 
WriteProcessException {
+    long[] increments = checkAlignedMemCostAndAddToTspInfoForRows(rows, 
alignedDeviceIds);
+    benchmarkAlignedRowsSnapshot =
+        alignedDeviceIds.isEmpty()
+            ? null
+            : new AlignedTVListRamCostSnapshot(workMemTable, alignedDeviceIds);
+    return increments;
+  }
+
+  /** Estimates a new aligned TVList without allocating it during the 
memory-control phase. */
+  private static final class NewAlignedRowsMemCostEstimator implements 
AlignedRowsMemCostEstimator {
+    private final Map<String, Integer> measurementIndexMap = new HashMap<>();
+
+    private String[] previousMeasurements;
+    private int[] previousMeasurementIndexes = new int[0];
+    private long[] primitiveArrayMemCosts = new long[0];
+    private int[] materializedMeasurementBlocks = new int[0];
+    private int rowCount;
+    private long materializedArrayMemCost;
+    private long textDataMemoryCost;
+
+    @Override
+    public void addRow(InsertRowNode row) {
+      String[] measurements = row.getMeasurements();
+      TSDataType[] dataTypes = row.getDataTypes();
+      Object[] values = row.getValues();
+      int[] measurementIndexes = mapKnownMeasurements(measurements);
+      int columnCount = Math.min(measurements.length, 
Math.min(dataTypes.length, values.length));
+      int currentBlock = rowCount / PrimitiveArrayManager.ARRAY_SIZE;
+      rowCount++;
+      for (int column = 0; column < columnCount; column++) {
+        String measurement = measurements[column];
+        TSDataType dataType = dataTypes[column];
+        if (measurement == null || dataType == null) {
+          continue;
+        }
+        int measurementIndex = measurementIndexes[column];
+        if (measurementIndex < 0) {
+          Integer knownMeasurementIndex = measurementIndexMap.get(measurement);
+          if (knownMeasurementIndex == null) {
+            measurementIndex = measurementIndexMap.size();
+            measurementIndexMap.put(measurement, measurementIndex);
+            ensureMeasurementCapacity(measurementIndex + 1);
+            primitiveArrayMemCosts[measurementIndex] =
+                AlignedTVList.valueListArrayMemCost(dataType) - 
NUM_BYTES_OBJECT_REF;
+          } else {
+            measurementIndex = knownMeasurementIndex;
           }
+          measurementIndexes[column] = measurementIndex;
+        }
+        Object value = values[column];
+        if (value != null && materializedMeasurementBlocks[measurementIndex] 
!= currentBlock) {
+          materializedMeasurementBlocks[measurementIndex] = currentBlock;
+          materializedArrayMemCost += primitiveArrayMemCosts[measurementIndex];
         }
-        int addingPointNum = addingPointNumInfo.right;
-        // Here currentChunkPointNum + addingPointNum >= 1
-        if (((currentChunkPointNum + addingPointNum) % 
PrimitiveArrayManager.ARRAY_SIZE) == 0) {
-          dataTypesInTVList.addAll(addingPointNumInfo.left.values());
-          memTableIncrement +=
-              alignedMemChunk != null
-                  ? 
alignedMemChunk.getWorkingTVList().alignedTvListArrayMemCost()
-                      + dataTypesInTVList.stream()
-                          .mapToLong(AlignedTVList::valueListArrayMemCost)
-                          .sum()
-                  : AlignedTVList.alignedTvListArrayMemCost(
-                      dataTypesInTVList.toArray(new TSDataType[0]));
+        if (dataType.isBinary() && value != null) {
+          textDataMemoryCost += MemUtils.getBinarySize((Binary) value);
         }
-        addingPointNumInfo.setRight(addingPointNum + 1);
       }
     }
-    updateMemoryInfo(memTableIncrement, chunkMetadataIncrement, 
textDataIncrement);
-    return new long[] {memTableIncrement, textDataIncrement, 
chunkMetadataIncrement};
+
+    private int[] mapKnownMeasurements(String[] measurements) {
+      if (previousMeasurements != null
+          && (previousMeasurements == measurements
+              || Arrays.equals(previousMeasurements, measurements))) {
+        previousMeasurements = measurements;
+        return previousMeasurementIndexes;
+      }
+      if (previousMeasurementIndexes.length < measurements.length) {
+        previousMeasurementIndexes = new int[measurements.length];
+      }
+      Arrays.fill(previousMeasurementIndexes, 0, measurements.length, -1);
+      for (int i = 0; i < measurements.length; i++) {
+        Integer measurementIndex = measurementIndexMap.get(measurements[i]);
+        if (measurementIndex != null) {
+          previousMeasurementIndexes[i] = measurementIndex;
+        }
+      }
+      previousMeasurements = measurements;
+      return previousMeasurementIndexes;
+    }
+
+    private void ensureMeasurementCapacity(int requiredCapacity) {
+      if (requiredCapacity <= primitiveArrayMemCosts.length) {
+        return;
+      }
+      int oldCapacity = primitiveArrayMemCosts.length;
+      int newCapacity = Math.max(requiredCapacity, Math.max(4, oldCapacity << 
1));
+      primitiveArrayMemCosts = Arrays.copyOf(primitiveArrayMemCosts, 
newCapacity);
+      materializedMeasurementBlocks = 
Arrays.copyOf(materializedMeasurementBlocks, newCapacity);
+      Arrays.fill(materializedMeasurementBlocks, oldCapacity, newCapacity, -1);
+    }
+
+    @Override
+    public long getTVListMemoryCost() {
+      if (rowCount == 0) {
+        return 0;
+      }
+      int measurementColumnCount = measurementIndexMap.size();
+      long blockCount =
+          (rowCount + (long) PrimitiveArrayManager.ARRAY_SIZE - 1)
+              / PrimitiveArrayManager.ARRAY_SIZE;
+      return AlignedTVList.alignedTvListInitialMemCost(measurementColumnCount)
+          + blockCount
+              * AlignedTVList.alignedTvListArrayMemCostWithoutPrimitiveArrays(
+                  measurementColumnCount)
+          + materializedArrayMemCost;
+    }
+
+    @Override
+    public long getTextDataMemoryCost() {
+      return textDataMemoryCost;
+    }
   }
 
   private long[] checkMemCostAndAddToTspInfoForTablet(
@@ -965,8 +1016,7 @@ public class TsFileProcessor {
           dataTypes.length
               * ChunkMetadata.calculateRamSize(AlignedPath.VECTOR_PLACEHOLDER, 
TSDataType.VECTOR);
       memIncrements[0] +=
-          ((end - start) / PrimitiveArrayManager.ARRAY_SIZE + 1)
-              * AlignedTVList.alignedTvListArrayMemCost(dataTypes);
+          estimateNewAlignedArrayMemCost(measurementIds, dataTypes, columns, 
start, end);
       for (int i = 0; i < dataTypes.length; i++) {
         TSDataType dataType = dataTypes[i];
         String measurement = measurementIds[i];
@@ -983,7 +1033,6 @@ public class TsFileProcessor {
 
     } else {
       AlignedWritableMemChunk alignedMemChunk = (AlignedWritableMemChunk) 
memChunk;
-      List<TSDataType> dataTypesInTVList = new ArrayList<>();
       for (int i = 0; i < dataTypes.length; i++) {
         TSDataType dataType = dataTypes[i];
         String measurement = measurementIds[i];
@@ -991,39 +1040,53 @@ public class TsFileProcessor {
         if (dataType == null || column == null || measurement == null) {
           continue;
         }
-        // Extending the column of aligned mem chunk
-        if (!alignedMemChunk.containsMeasurement(measurementIds[i])) {
-          memIncrements[0] +=
-              (alignedMemChunk.alignedListSize() / 
PrimitiveArrayManager.ARRAY_SIZE + 1)
-                  * AlignedTVList.valueListArrayMemCost(dataType);
-          dataTypesInTVList.add(dataType);
-        }
         // TEXT data size
         if (dataType.isBinary()) {
           Binary[] binColumn = (Binary[]) columns[i];
           memIncrements[1] += MemUtils.getBinaryColumnSize(binColumn, start, 
end);
         }
       }
-      long acquireArray;
-      if (alignedMemChunk.alignedListSize() % PrimitiveArrayManager.ARRAY_SIZE 
== 0) {
-        acquireArray = (end - start) / PrimitiveArrayManager.ARRAY_SIZE + 1L;
-      } else {
-        acquireArray =
-            (end
-                    - start
-                    - 1
-                    + (alignedMemChunk.alignedListSize() % 
PrimitiveArrayManager.ARRAY_SIZE))
-                / PrimitiveArrayManager.ARRAY_SIZE;
+      memIncrements[0] +=
+          alignedMemChunk.alignedWriteArrayMemCost(measurementIds, dataTypes, 
columns, start, end);
+    }
+  }
+
+  private long estimateNewAlignedRowMemCost(
+      String[] measurements, TSDataType[] dataTypes, Object[] values) {
+    List<TSDataType> validDataTypes = new ArrayList<>();
+    for (int i = 0; i < dataTypes.length; i++) {
+      if (measurements[i] != null && dataTypes[i] != null && values[i] != 
null) {
+        validDataTypes.add(dataTypes[i]);
       }
-      if (acquireArray != 0) {
-        // memory of extending the TVList
-        memIncrements[0] +=
-            acquireArray * 
alignedMemChunk.getWorkingTVList().alignedTvListArrayMemCost();
-        for (TSDataType dataType : dataTypesInTVList) {
-          memIncrements[0] += acquireArray * 
AlignedTVList.valueListArrayMemCost(dataType);
-        }
+    }
+    return estimateNewDenseAlignedMemCost(validDataTypes, 1);
+  }
+
+  private long estimateNewAlignedArrayMemCost(
+      String[] measurements, TSDataType[] dataTypes, Object[] columns, int 
start, int end) {
+    List<TSDataType> validDataTypes = new ArrayList<>();
+    for (int i = 0; i < dataTypes.length; i++) {
+      if (measurements[i] != null && dataTypes[i] != null && columns[i] != 
null) {
+        validDataTypes.add(dataTypes[i]);
       }
     }
+    return estimateNewDenseAlignedMemCost(validDataTypes, end - start);
+  }
+
+  private long estimateNewDenseAlignedMemCost(List<TSDataType> dataTypes, int 
rowCount) {
+    if (rowCount <= 0) {
+      return 0;
+    }
+    int measurementColumnCount = dataTypes.size();
+    long blockMemCost =
+        
AlignedTVList.alignedTvListArrayMemCostWithoutPrimitiveArrays(measurementColumnCount);
+    for (TSDataType dataType : dataTypes) {
+      blockMemCost += AlignedTVList.valueListArrayMemCost(dataType) - 
NUM_BYTES_OBJECT_REF;
+    }
+    long blockCount =
+        (rowCount + (long) PrimitiveArrayManager.ARRAY_SIZE - 1) / 
PrimitiveArrayManager.ARRAY_SIZE;
+    return AlignedTVList.alignedTvListInitialMemCost(measurementColumnCount)
+        + blockCount * blockMemCost;
   }
 
   private void reconcileAlignedTVListRamCost(
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/AlignedTVList.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/AlignedTVList.java
index d319b9c9a0d..d8b16b3eb84 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/AlignedTVList.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/AlignedTVList.java
@@ -81,6 +81,8 @@ public abstract class AlignedTVList extends TVList {
 
   private long materializedBitmapMemoryCost;
   private long arrayMemCostWithoutIndex;
+  private int[] materializedValueArrayCounts;
+  private long materializedValueArrayMemCost;
 
   /**
    * A fully prepared partial clone. All allocations and validations are 
completed before this plan
@@ -150,6 +152,7 @@ public abstract class AlignedTVList extends TVList {
     super();
     dataTypes = types;
     memoryBinaryChunkSize = new long[dataTypes.size()];
+    materializedValueArrayCounts = new int[dataTypes.size()];
     values = new ArrayList<>(types.size());
     for (int i = 0; i < types.size(); i++) {
       values.add(new ArrayList<>());
@@ -199,6 +202,8 @@ public abstract class AlignedTVList extends TVList {
     alignedTvList.rowCount = this.rowCount;
     alignedTvList.allValueColDeletedMap = getAllValueColDeletedMap();
     alignedTvList.materializedBitmapMemoryCost = 
calculateBitmapRamCost(bitMaps, null);
+    alignedTvList.refreshArrayMemCostWithoutIndex();
+    alignedTvList.refreshMaterializedValueArrayMemoryCost();
     return alignedTvList;
   }
 
@@ -210,6 +215,9 @@ public abstract class AlignedTVList extends TVList {
     cloneList.values = this.values;
     cloneList.bitMaps = this.bitMaps;
     cloneList.materializedBitmapMemoryCost = materializedBitmapMemoryCost;
+    cloneList.materializedValueArrayCounts =
+        Arrays.copyOf(materializedValueArrayCounts, 
materializedValueArrayCounts.length);
+    cloneList.materializedValueArrayMemCost = materializedValueArrayMemCost;
     return cloneList;
   }
 
@@ -304,6 +312,8 @@ public abstract class AlignedTVList extends TVList {
     plan.cloneList.arrayMemCostWithoutIndex = 
plan.cloneArrayMemCostWithoutIndex;
     materializedBitmapMemoryCost = plan.sourceBitmapMemoryCost;
     plan.cloneList.materializedBitmapMemoryCost = plan.cloneBitmapMemoryCost;
+    refreshMaterializedValueArrayMemoryCost();
+    plan.cloneList.refreshMaterializedValueArrayMemoryCost();
   }
 
   /**
@@ -331,7 +341,9 @@ public abstract class AlignedTVList extends TVList {
 
       // Release memory for non-query columns
       for (Object dataArray : columnValues) {
-        PrimitiveArrayManager.release(dataArray);
+        if (dataArray != null) {
+          PrimitiveArrayManager.release(dataArray);
+        }
       }
       values.set(i, null);
       memoryBinaryChunkSize[i] = 0;
@@ -345,6 +357,7 @@ public abstract class AlignedTVList extends TVList {
     materializedBitmapMemoryCost = calculateBitmapRamCost(bitMaps, 
columnsToKeep);
     // Refresh per-block memory cost after releasing columns
     refreshArrayMemCostWithoutIndex();
+    refreshMaterializedValueArrayMemoryCost();
   }
 
   @SuppressWarnings("squid:S3776") // Suppress high Cognitive Complexity 
warning
@@ -360,44 +373,37 @@ public abstract class AlignedTVList extends TVList {
       Object columnValue = value[i];
       if (columnValue == null) {
         markNullValue(i, arrayIndex, elementIndex);
+        continue;
       }
       List<Object> columnValues = values.get(i);
       if (columnValues == null) {
         throw new IllegalStateException(
             String.format("Missing value arrays for aligned column index %d 
during append", i));
       }
+      Object valueArray = getOrCreateValueArray(i, arrayIndex);
       switch (dataTypes.get(i)) {
         case TEXT:
         case BLOB:
         case STRING:
-          ((Binary[]) columnValues.get(arrayIndex))[elementIndex] =
-              columnValue != null ? (Binary) columnValue : Binary.EMPTY_VALUE;
-          memoryBinaryChunkSize[i] +=
-              columnValue != null
-                  ? getBinarySize((Binary) columnValue)
-                  : getBinarySize(Binary.EMPTY_VALUE);
+          ((Binary[]) valueArray)[elementIndex] = (Binary) columnValue;
+          memoryBinaryChunkSize[i] += getBinarySize((Binary) columnValue);
           break;
         case FLOAT:
-          ((float[]) columnValues.get(arrayIndex))[elementIndex] =
-              columnValue != null ? (float) columnValue : Float.MIN_VALUE;
+          ((float[]) valueArray)[elementIndex] = (float) columnValue;
           break;
         case INT32:
         case DATE:
-          ((int[]) columnValues.get(arrayIndex))[elementIndex] =
-              columnValue != null ? (int) columnValue : Integer.MIN_VALUE;
+          ((int[]) valueArray)[elementIndex] = (int) columnValue;
           break;
         case INT64:
         case TIMESTAMP:
-          ((long[]) columnValues.get(arrayIndex))[elementIndex] =
-              columnValue != null ? (long) columnValue : Long.MIN_VALUE;
+          ((long[]) valueArray)[elementIndex] = (long) columnValue;
           break;
         case DOUBLE:
-          ((double[]) columnValues.get(arrayIndex))[elementIndex] =
-              columnValue != null ? (double) columnValue : Double.MIN_VALUE;
+          ((double[]) valueArray)[elementIndex] = (double) columnValue;
           break;
         case BOOLEAN:
-          ((boolean[]) columnValues.get(arrayIndex))[elementIndex] =
-              columnValue != null && (boolean) columnValue;
+          ((boolean[]) valueArray)[elementIndex] = (boolean) columnValue;
           break;
         default:
           break;
@@ -517,66 +523,16 @@ public abstract class AlignedTVList extends TVList {
   }
 
   public void extendColumn(TSDataType dataType) {
-    if (bitMaps == null) {
-      List<List<BitMap>> localBitMaps = new ArrayList<>(values.size());
-      for (int i = 0; i < values.size(); i++) {
-        localBitMaps.add(null);
-      }
-      bitMaps = localBitMaps;
-    }
     List<Object> columnValue = new ArrayList<>();
-    List<BitMap> columnBitMaps = new ArrayList<>();
     for (int i = 0; i < timestamps.size(); i++) {
-      switch (dataType) {
-        case TEXT:
-        case STRING:
-        case BLOB:
-          columnValue.add(getPrimitiveArraysByType(TSDataType.TEXT));
-          break;
-        case FLOAT:
-          columnValue.add(getPrimitiveArraysByType(TSDataType.FLOAT));
-          break;
-        case INT32:
-        case DATE:
-          columnValue.add(getPrimitiveArraysByType(TSDataType.INT32));
-          break;
-        case INT64:
-        case TIMESTAMP:
-          columnValue.add(getPrimitiveArraysByType(TSDataType.INT64));
-          break;
-        case DOUBLE:
-          columnValue.add(getPrimitiveArraysByType(TSDataType.DOUBLE));
-          break;
-        case BOOLEAN:
-          columnValue.add(getPrimitiveArraysByType(TSDataType.BOOLEAN));
-          break;
-        default:
-          break;
-      }
-      BitMap bitMap = new BitMap(ARRAY_SIZE);
-      // The following code is for these 2 kinds of scenarios.
-
-      // Eg1: If rowCount=5 and ARRAY_SIZE=2, we need to supply 3 bitmaps for 
the extending column.
-      // The first 2 bitmaps should mark all bits to represent 4 nulls and the 
3rd bitmap should
-      // mark
-      // the 1st bit to represent 1 null value.
-
-      // Eg2: If rowCount=4 and ARRAY_SIZE=2, we need to supply 2 bitmaps for 
the extending column.
-      // These 2 bitmaps should mark all bits to represent 4 nulls.
-      if (i == timestamps.size() - 1 && rowCount % ARRAY_SIZE != 0) {
-        for (int j = 0; j < rowCount % ARRAY_SIZE; j++) {
-          bitMap.mark(j);
-        }
-      } else {
-        bitMap.markAll();
-      }
-      columnBitMaps.add(bitMap);
+      columnValue.add(null);
+    }
+    if (bitMaps != null) {
+      bitMaps.add(null);
     }
-    this.bitMaps.add(columnBitMaps);
     this.values.add(columnValue);
     this.dataTypes.add(dataType);
-    materializedBitmapMemoryCost +=
-        (long) columnBitMaps.size() * (bitmapReferenceRamCost() + 
bitmapRamCost());
+    materializedValueArrayCounts = Arrays.copyOf(materializedValueArrayCounts, 
dataTypes.size());
     refreshArrayMemCostWithoutIndex();
 
     long[] tmpValueChunkRawSize = memoryBinaryChunkSize;
@@ -690,7 +646,7 @@ public abstract class AlignedTVList extends TVList {
     if (bitMaps == null
         || bitMaps.get(columnIndex) == null
         || bitMaps.get(columnIndex).get(unsortedRowIndex / ARRAY_SIZE) == 
null) {
-      return false;
+      return values.get(columnIndex).get(unsortedRowIndex / ARRAY_SIZE) == 
null;
     }
     int arrayIndex = unsortedRowIndex / ARRAY_SIZE;
     int elementIndex = unsortedRowIndex % ARRAY_SIZE;
@@ -816,6 +772,9 @@ public abstract class AlignedTVList extends TVList {
   }
 
   protected Object cloneValue(TSDataType type, Object value) {
+    if (value == null) {
+      return null;
+    }
     switch (type) {
       case TEXT:
       case BLOB:
@@ -910,6 +869,7 @@ public abstract class AlignedTVList extends TVList {
       }
     }
     cloneList.materializedBitmapMemoryCost = materializedBitmapMemoryCost;
+    cloneList.refreshMaterializedValueArrayMemoryCost();
 
     if (hasBitMapsToMove && cloneList.bitMaps == null) {
       cloneList.bitMaps = new ArrayList<>(dataTypes.size());
@@ -925,12 +885,36 @@ public abstract class AlignedTVList extends TVList {
       List<Object> columnValues = values.get(i);
       if (columnValues != null) {
         for (Object dataArray : columnValues) {
-          PrimitiveArrayManager.release(dataArray);
+          if (dataArray != null) {
+            PrimitiveArrayManager.release(dataArray);
+          }
         }
         columnValues.clear();
       }
       memoryBinaryChunkSize[i] = 0;
     }
+    Arrays.fill(materializedValueArrayCounts, 0);
+    materializedValueArrayMemCost = 0;
+  }
+
+  private Object getOrCreateValueArray(int columnIndex, int arrayIndex) {
+    List<Object> columnValues = values.get(columnIndex);
+    if (columnValues == null) {
+      throw new IllegalStateException(
+          String.format("Missing value arrays for aligned column index %d", 
columnIndex));
+    }
+    Object array = columnValues.get(arrayIndex);
+    if (array == null) {
+      array = getPrimitiveArraysByType(dataTypes.get(columnIndex));
+      columnValues.set(arrayIndex, array);
+      materializedValueArrayCounts[columnIndex]++;
+      materializedValueArrayMemCost += 
primitiveArrayMemCost(dataTypes.get(columnIndex));
+      int existingRows = Math.min(rowCount - arrayIndex * ARRAY_SIZE, 
ARRAY_SIZE);
+      if (existingRows > 0) {
+        getBitMap(columnIndex, arrayIndex).markRange(0, existingRows);
+      }
+    }
+    return array;
   }
 
   @Override
@@ -957,7 +941,7 @@ public abstract class AlignedTVList extends TVList {
         throw new IllegalStateException(
             String.format("Missing value arrays for aligned column index %d 
during expand", i));
       }
-      columnValues.add(getPrimitiveArraysByType(dataTypes.get(i)));
+      columnValues.add(null);
       if (bitMaps != null && bitMaps.get(i) != null) {
         bitMaps.get(i).add(null);
         materializedBitmapMemoryCost += bitmapReferenceRamCost();
@@ -1078,7 +1062,7 @@ public abstract class AlignedTVList extends TVList {
   }
 
   private void arrayCopy(Object[] value, int idx, int arrayIndex, int 
elementIndex, int remaining) {
-    for (int i = 0; i < values.size(); i++) {
+    for (int i = 0; i < Math.min(values.size(), value.length); i++) {
       if (value[i] == null) {
         continue;
       }
@@ -1087,11 +1071,12 @@ public abstract class AlignedTVList extends TVList {
         throw new IllegalStateException(
             String.format("Missing value arrays for aligned column index %d 
during arrayCopy", i));
       }
+      Object valueArray = getOrCreateValueArray(i, arrayIndex);
       switch (dataTypes.get(i)) {
         case TEXT:
         case BLOB:
         case STRING:
-          Binary[] arrayT = ((Binary[]) columnValues.get(arrayIndex));
+          Binary[] arrayT = ((Binary[]) valueArray);
           System.arraycopy(value[i], idx, arrayT, elementIndex, remaining);
 
           // update raw size of Text chunk
@@ -1101,25 +1086,25 @@ public abstract class AlignedTVList extends TVList {
           }
           break;
         case FLOAT:
-          float[] arrayF = ((float[]) columnValues.get(arrayIndex));
+          float[] arrayF = ((float[]) valueArray);
           System.arraycopy(value[i], idx, arrayF, elementIndex, remaining);
           break;
         case INT32:
         case DATE:
-          int[] arrayI = ((int[]) columnValues.get(arrayIndex));
+          int[] arrayI = ((int[]) valueArray);
           System.arraycopy(value[i], idx, arrayI, elementIndex, remaining);
           break;
         case INT64:
         case TIMESTAMP:
-          long[] arrayL = ((long[]) columnValues.get(arrayIndex));
+          long[] arrayL = ((long[]) valueArray);
           System.arraycopy(value[i], idx, arrayL, elementIndex, remaining);
           break;
         case DOUBLE:
-          double[] arrayD = ((double[]) columnValues.get(arrayIndex));
+          double[] arrayD = ((double[]) valueArray);
           System.arraycopy(value[i], idx, arrayD, elementIndex, remaining);
           break;
         case BOOLEAN:
-          boolean[] arrayB = ((boolean[]) columnValues.get(arrayIndex));
+          boolean[] arrayB = ((boolean[]) valueArray);
           System.arraycopy(value[i], idx, arrayB, elementIndex, remaining);
           break;
         default:
@@ -1198,28 +1183,64 @@ public abstract class AlignedTVList extends TVList {
     return timestamps.size()
             * (arrayMemCostWithoutIndex
                 + (indices != null ? (long) PrimitiveArrayManager.ARRAY_SIZE * 
Integer.BYTES : 0))
+        + materializedValueArrayMemCost
         + materializedBitmapMemoryCost
         + calculateContainerRamCost(null);
   }
 
   public synchronized long getRamSize(Set<Integer> columnsToClone) {
-    return timestamps.size() * alignedTvListArrayMemCost(columnsToClone)
+    return timestamps.size()
+            * (calculateArrayMemCostWithoutIndex(columnsToClone)
+                + (indices != null ? (long) PrimitiveArrayManager.ARRAY_SIZE * 
Integer.BYTES : 0))
+        + calculateMaterializedValueArrayMemCost(columnsToClone)
         + calculateBitmapRamCost(bitMaps, columnsToClone)
         + calculateContainerRamCost(columnsToClone);
   }
 
   private long calculateArrayMemCostWithoutIndex(Set<Integer> retainedColumns) 
{
-    long arrayMemCost = alignedTvListArrayMemCost(retainedColumns);
-    if (indices != null) {
-      arrayMemCost -= (long) PrimitiveArrayManager.ARRAY_SIZE * Integer.BYTES;
+    long arrayMemCost =
+        (long) PrimitiveArrayManager.ARRAY_SIZE * Long.BYTES
+            + 2L * NUM_BYTES_ARRAY_HEADER
+            + 2L * NUM_BYTES_OBJECT_REF;
+    for (int i = 0; i < dataTypes.size(); i++) {
+      if ((retainedColumns == null || retainedColumns.contains(i)) && 
values.get(i) != null) {
+        arrayMemCost += NUM_BYTES_OBJECT_REF;
+      }
     }
     return arrayMemCost;
   }
 
+  private long calculateMaterializedValueArrayMemCost(Set<Integer> 
retainedColumns) {
+    long size = 0;
+    for (int i = 0; i < dataTypes.size(); i++) {
+      if ((retainedColumns == null || retainedColumns.contains(i)) && 
values.get(i) != null) {
+        size += (long) materializedValueArrayCounts[i] * 
primitiveArrayMemCost(dataTypes.get(i));
+      }
+    }
+    return size;
+  }
+
   private void refreshArrayMemCostWithoutIndex() {
     arrayMemCostWithoutIndex = calculateArrayMemCostWithoutIndex(null);
   }
 
+  private void refreshMaterializedValueArrayMemoryCost() {
+    Arrays.fill(materializedValueArrayCounts, 0);
+    materializedValueArrayMemCost = 0;
+    for (int i = 0; i < values.size(); i++) {
+      List<Object> columnValues = values.get(i);
+      if (columnValues == null) {
+        continue;
+      }
+      for (Object valueArray : columnValues) {
+        if (valueArray != null) {
+          materializedValueArrayCounts[i]++;
+          materializedValueArrayMemCost += 
primitiveArrayMemCost(dataTypes.get(i));
+        }
+      }
+    }
+  }
+
   /**
    * Calculate the one-time container memory retained by this list. 
Primitive-array references in
    * the time/index/value lists and bitmap lists are already charged by the 
per-block accounting, so
@@ -1324,6 +1345,228 @@ public abstract class AlignedTVList extends TVList {
     return size;
   }
 
+  /** Initial list-container memory before the first aligned row is written. */
+  public static long alignedTvListInitialMemCost(int measurementColumnCount) {
+    long arrayListShallowSize = 
RamUsageEstimator.shallowSizeOfInstance(ArrayList.class);
+    long listWithReferencesSize =
+        arrayListShallowSize + 
RamUsageEstimator.sizeOfObjectArray(measurementColumnCount);
+    return 2 * listWithReferencesSize
+        + RamUsageEstimator.sizeOfLongArray(measurementColumnCount)
+        + arrayListShallowSize
+        + (long) measurementColumnCount * arrayListShallowSize;
+  }
+
+  /** Memory of one aligned block excluding all lazily materialized value 
arrays. */
+  public static long alignedTvListArrayMemCostWithoutPrimitiveArrays(int 
measurementColumnCount) {
+    return (long) ARRAY_SIZE * Long.BYTES
+        + 2L * NUM_BYTES_ARRAY_HEADER
+        + (2L + measurementColumnCount) * NUM_BYTES_OBJECT_REF;
+  }
+
+  /**
+   * Estimate memory allocated by an aligned tablet after its measurements are 
mapped to TVList
+   * columns. A negative mapped index denotes a column that will be appended 
before the write.
+   */
+  public synchronized long alignedWriteArrayMemCost(
+      int[] tvListColumnIndexes,
+      TSDataType[] incomingDataTypes,
+      Object[] columns,
+      int start,
+      int end) {
+    if (start >= end) {
+      return 0;
+    }
+    int columnCount =
+        Math.min(tvListColumnIndexes.length, Math.min(columns.length, 
incomingDataTypes.length));
+    int newColumnCount =
+        countNewColumns(tvListColumnIndexes, incomingDataTypes, columns, 
columnCount);
+    long size = 0;
+    int block = rowCount / ARRAY_SIZE;
+    int elementIndex = rowCount % ARRAY_SIZE;
+    int inputIndex = start;
+    while (inputIndex < end) {
+      if (block >= timestamps.size()) {
+        size +=
+            arrayMemCostWithoutIndex
+                + (long) newColumnCount * NUM_BYTES_OBJECT_REF
+                + (indices != null ? (long) PrimitiveArrayManager.ARRAY_SIZE * 
Integer.BYTES : 0);
+      }
+      for (int inputColumn = 0; inputColumn < columnCount; inputColumn++) {
+        TSDataType incomingDataType = incomingDataTypes[inputColumn];
+        if (incomingDataType == null || columns[inputColumn] == null) {
+          continue;
+        }
+        int tvListColumnIndex = tvListColumnIndexes[inputColumn];
+        if (tvListColumnIndex < 0 || 
!isValueArrayMaterialized(tvListColumnIndex, block)) {
+          size += primitiveArrayMemCost(incomingDataType);
+        }
+      }
+      int written = Math.min(end - inputIndex, ARRAY_SIZE - elementIndex);
+      inputIndex += written;
+      block++;
+      elementIndex = 0;
+    }
+    return size + columnExtensionMemCost(newColumnCount);
+  }
+
+  public synchronized AlignedWriteRowsMemCostEstimator 
newAlignedWriteRowsMemCostEstimator() {
+    return new AlignedWriteRowsMemCostEstimator();
+  }
+
+  /** Incrementally estimates a row batch without retaining per-row input or 
column mappings. */
+  public final class AlignedWriteRowsMemCostEstimator {
+    private final int initialRowCount = rowCount;
+    private final int existingBlockCount = timestamps.size();
+    private final int existingColumnCount = dataTypes.size();
+    private final long existingArrayMemCostWithoutIndex = 
arrayMemCostWithoutIndex;
+    private final boolean hasIndices = indices != null;
+    private boolean[] newColumns = new boolean[Math.max(1, 
existingColumnCount)];
+    private int[] materializedColumnBlocks = new int[Math.max(1, 
existingColumnCount)];
+
+    private int estimatedRowCount;
+    private int currentBlock = -1;
+    private int newColumnCount;
+    private long materializedArrayMemCost;
+
+    private AlignedWriteRowsMemCostEstimator() {
+      Arrays.fill(materializedColumnBlocks, -1);
+    }
+
+    public void startRow() {
+      currentBlock = (int) ((initialRowCount + (long) estimatedRowCount) / 
ARRAY_SIZE);
+      estimatedRowCount++;
+    }
+
+    public void addValue(int tvListColumnIndex, TSDataType incomingDataType, 
Object value) {
+      if (tvListColumnIndex < 0 || incomingDataType == null) {
+        return;
+      }
+      ensureColumnCapacity(tvListColumnIndex + 1);
+      if (tvListColumnIndex >= existingColumnCount && 
!newColumns[tvListColumnIndex]) {
+        newColumns[tvListColumnIndex] = true;
+        newColumnCount++;
+      }
+      if (value != null
+          && !isValueArrayMaterialized(tvListColumnIndex, currentBlock)
+          && materializedColumnBlocks[tvListColumnIndex] != currentBlock) {
+        materializedColumnBlocks[tvListColumnIndex] = currentBlock;
+        materializedArrayMemCost += primitiveArrayMemCost(incomingDataType);
+      }
+    }
+
+    private void ensureColumnCapacity(int requiredCapacity) {
+      if (requiredCapacity <= newColumns.length) {
+        return;
+      }
+      int oldCapacity = newColumns.length;
+      int newCapacity = Math.max(requiredCapacity, oldCapacity << 1);
+      newColumns = Arrays.copyOf(newColumns, newCapacity);
+      materializedColumnBlocks = Arrays.copyOf(materializedColumnBlocks, 
newCapacity);
+      Arrays.fill(materializedColumnBlocks, oldCapacity, newCapacity, -1);
+    }
+
+    public long getMemoryCost() {
+      if (estimatedRowCount == 0) {
+        return 0;
+      }
+      long finalBlockCount =
+          (initialRowCount + (long) estimatedRowCount + ARRAY_SIZE - 1) / 
ARRAY_SIZE;
+      long newBlockCount = Math.max(0, finalBlockCount - existingBlockCount);
+      long newBlockMemCost =
+          existingArrayMemCostWithoutIndex
+              + (long) newColumnCount * NUM_BYTES_OBJECT_REF
+              + (hasIndices ? (long) ARRAY_SIZE * Integer.BYTES : 0);
+      return materializedArrayMemCost
+          + columnExtensionMemCost(newColumnCount)
+          + newBlockCount * newBlockMemCost;
+    }
+  }
+
+  /**
+   * Estimate memory allocated by one aligned row after its measurements are 
mapped to TVList
+   * columns. {@code rowOffset} is the number of preceding rows in the same 
pending batch.
+   */
+  public synchronized long alignedWriteRowMemCost(
+      int[] tvListColumnIndexes,
+      TSDataType[] incomingDataTypes,
+      Object[] rowValues,
+      int rowOffset) {
+    int columnCount =
+        Math.min(tvListColumnIndexes.length, Math.min(rowValues.length, 
incomingDataTypes.length));
+    int newColumnCount =
+        countNewColumns(tvListColumnIndexes, incomingDataTypes, rowValues, 
columnCount);
+    long size = 0;
+    int block = (rowCount + rowOffset) / ARRAY_SIZE;
+    if (block >= timestamps.size()) {
+      size +=
+          arrayMemCostWithoutIndex
+              + (long) newColumnCount * NUM_BYTES_OBJECT_REF
+              + (indices != null ? (long) PrimitiveArrayManager.ARRAY_SIZE * 
Integer.BYTES : 0);
+    }
+    for (int inputColumn = 0; inputColumn < columnCount; inputColumn++) {
+      TSDataType incomingDataType = incomingDataTypes[inputColumn];
+      if (incomingDataType == null || rowValues[inputColumn] == null) {
+        continue;
+      }
+      int tvListColumnIndex = tvListColumnIndexes[inputColumn];
+      if (tvListColumnIndex < 0 || 
!isValueArrayMaterialized(tvListColumnIndex, block)) {
+        size += primitiveArrayMemCost(incomingDataType);
+      }
+    }
+    return size + columnExtensionMemCost(newColumnCount);
+  }
+
+  private static int countNewColumns(
+      int[] tvListColumnIndexes,
+      TSDataType[] incomingDataTypes,
+      Object[] inputValues,
+      int columnCount) {
+    int count = 0;
+    for (int i = 0; i < columnCount; i++) {
+      if (tvListColumnIndexes[i] < 0 && incomingDataTypes[i] != null && 
inputValues[i] != null) {
+        count++;
+      }
+    }
+    return count;
+  }
+
+  private boolean isValueArrayMaterialized(int columnIndex, int arrayIndex) {
+    if (columnIndex >= values.size()) {
+      return false;
+    }
+    List<Object> columnValues = values.get(columnIndex);
+    return columnValues != null
+        && arrayIndex < columnValues.size()
+        && columnValues.get(arrayIndex) != null;
+  }
+
+  private long columnExtensionMemCost(int newColumnCount) {
+    if (newColumnCount == 0) {
+      return 0;
+    }
+    int oldColumnCount = dataTypes.size();
+    int newTotalColumnCount = oldColumnCount + newColumnCount;
+    long size = (long) timestamps.size() * NUM_BYTES_OBJECT_REF * 
newColumnCount;
+    size +=
+        RamUsageEstimator.sizeOfObjectArray(newTotalColumnCount)
+            - RamUsageEstimator.sizeOfObjectArray(oldColumnCount);
+    size +=
+        RamUsageEstimator.sizeOfLongArray(newTotalColumnCount)
+            - RamUsageEstimator.sizeOfLongArray(oldColumnCount);
+    size +=
+        RamUsageEstimator.sizeOfObjectArray(newTotalColumnCount)
+            - RamUsageEstimator.sizeOfObjectArray(oldColumnCount);
+    if (bitMaps != null) {
+      size +=
+          RamUsageEstimator.sizeOfObjectArray(newTotalColumnCount)
+              - RamUsageEstimator.sizeOfObjectArray(oldColumnCount);
+    }
+    long newColumnContainerCost =
+        RamUsageEstimator.shallowSizeOf(new ArrayList<>())
+            + (timestamps.isEmpty() ? 0 : 
RamUsageEstimator.sizeOfObjectArray(0));
+    return size + newColumnCount * newColumnContainerCost;
+  }
+
   /**
    * Get the single alignedTVList array mem cost by give types.
    *
@@ -1371,6 +1614,12 @@ public abstract class AlignedTVList extends TVList {
         + NUM_BYTES_OBJECT_REF;
   }
 
+  /** Memory cost of one materialized primitive array, excluding its ArrayList 
reference. */
+  private static long primitiveArrayMemCost(TSDataType type) {
+    return (long) PrimitiveArrayManager.ARRAY_SIZE * type.getDataTypeSize()
+        + NUM_BYTES_ARRAY_HEADER;
+  }
+
   public static long bitmapRamCost() {
     return BITMAP_RAM_COST;
   }
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedRowsBranchComparisonBenchmarkTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedRowsBranchComparisonBenchmarkTest.java
new file mode 100644
index 00000000000..72d6de4990e
--- /dev/null
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedRowsBranchComparisonBenchmarkTest.java
@@ -0,0 +1,286 @@
+/*
+ * 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.MetadataException;
+import org.apache.iotdb.commons.file.SystemFileFactory;
+import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeId;
+import 
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.InsertRowNode;
+import org.apache.iotdb.db.storageengine.dataregion.DataRegionInfo;
+import org.apache.iotdb.db.storageengine.dataregion.DataRegionTest;
+import org.apache.iotdb.db.storageengine.rescon.memory.PrimitiveArrayManager;
+import org.apache.iotdb.db.storageengine.rescon.memory.SystemInfo;
+import org.apache.iotdb.db.utils.EnvironmentUtils;
+import org.apache.iotdb.db.utils.ManualPerformanceTestUtils;
+import org.apache.iotdb.db.utils.constant.TestConstant;
+
+import com.sun.management.ThreadMXBean;
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.file.metadata.IDeviceID;
+import org.apache.tsfile.file.metadata.PlainDeviceID;
+import org.apache.tsfile.write.schema.IMeasurementSchema;
+import org.apache.tsfile.write.schema.MeasurementSchema;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+public class AlignedRowsBranchComparisonBenchmarkTest {
+
+  private static final String STORAGE_GROUP = "root.branch_benchmark";
+  private static final String DEVICE_PATH = STORAGE_GROUP + ".d0";
+  private static final IDeviceID DEVICE_ID = new PlainDeviceID(DEVICE_PATH);
+  private static final int EXISTING_COLUMNS = 48;
+  private static final int EXISTING_ROWS = PrimitiveArrayManager.ARRAY_SIZE;
+  private static final int[] ROW_COUNTS = {128, 1024, 1024, 8192};
+  private static final int[] COLUMN_COUNTS = {16, 16, 64, 64};
+  private static final int[] ITERATIONS = {976, 122, 30, 3};
+  private static final int WARMUP_ROUNDS = 3;
+  private static final int MEASUREMENT_ROUNDS = 6;
+  private static final ThreadMXBean ALLOCATION_MX_BEAN =
+      (ThreadMXBean) ManagementFactory.getThreadMXBean();
+
+  private TsFileProcessor processor;
+  private DataRegionInfo dataRegionInfo;
+  private File tsFile;
+  private IMemTable memTable;
+  private long benchmarkBlackhole;
+
+  @Before
+  public void setUp() throws Exception {
+    Assert.assertTrue(ManualPerformanceTestUtils.enableThreadMetrics());
+    EnvironmentUtils.envSetUp();
+    tsFile =
+        
SystemFileFactory.INSTANCE.getFile(TestConstant.getTestTsFilePath(STORAGE_GROUP,
 0, 0, 0));
+    dataRegionInfo =
+        new DataRegionInfo(
+            new DataRegionTest.DummyDataRegion(
+                TestConstant.OUTPUT_DATA_DIR + "branch-benchmark-info", 
STORAGE_GROUP));
+    processor =
+        new TsFileProcessor(
+            STORAGE_GROUP,
+            tsFile,
+            dataRegionInfo,
+            ignored -> {},
+            (ignored, updateMap, systemFlushTime) -> {},
+            true);
+    processor.setTsFileProcessorInfo(new TsFileProcessorInfo(dataRegionInfo));
+    dataRegionInfo.initTsFileProcessorInfo(processor);
+    SystemInfo.getInstance().reportStorageGroupStatus(dataRegionInfo, 
processor);
+
+    List<IMeasurementSchema> schemas = createSchemas(EXISTING_COLUMNS);
+    AlignedWritableMemChunk memChunk = new AlignedWritableMemChunk(new 
ArrayList<>(schemas));
+    long[] times = new long[EXISTING_ROWS];
+    Object[] columns = new Object[EXISTING_COLUMNS];
+    for (int column = 0; column < EXISTING_COLUMNS; column++) {
+      int[] values = new int[EXISTING_ROWS];
+      for (int row = 0; row < EXISTING_ROWS; row++) {
+        times[row] = row;
+        values[row] = row + column;
+      }
+      columns[column] = values;
+    }
+    memChunk.putAlignedTablet(times, columns, null, 0, EXISTING_ROWS);
+    Map<IDeviceID, IWritableMemChunkGroup> map = new HashMap<>();
+    map.put(DEVICE_ID, new AlignedWritableMemChunkGroup(memChunk, new 
ArrayList<>(schemas)));
+    memTable = new PrimitiveMemTable(STORAGE_GROUP, "0", map);
+    Field workMemTable = 
TsFileProcessor.class.getDeclaredField("workMemTable");
+    workMemTable.setAccessible(true);
+    workMemTable.set(processor, memTable);
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    try {
+      if (processor != null) {
+        processor.putMemTableBackAndClose();
+      }
+    } finally {
+      EnvironmentUtils.cleanEnv();
+      EnvironmentUtils.cleanDir(TestConstant.OUTPUT_DATA_DIR);
+      if (tsFile != null) {
+        tsFile.delete();
+        new File(tsFile.getPath() + ".resource").delete();
+      }
+    }
+  }
+
+  @Test
+  public void compareAlignedRowsMemoryEstimation() throws Exception {
+    for (int scenario = 0; scenario < ROW_COUNTS.length; scenario++) {
+      List<InsertRowNode> rows = createRows(ROW_COUNTS[scenario], 
COLUMN_COUNTS[scenario]);
+      for (int round = 0; round < WARMUP_ROUNDS; round++) {
+        runUnmeasured(rows, ITERATIONS[scenario]);
+      }
+      BenchmarkResult[] results = new BenchmarkResult[MEASUREMENT_ROUNDS];
+      for (int round = 0; round < MEASUREMENT_ROUNDS; round++) {
+        results[round] = measure(rows, ITERATIONS[scenario]);
+      }
+      BenchmarkResult summary = summarize(results);
+      System.out.printf(
+          Locale.ROOT,
+          "rows=%d columns=%d iterations=%d estimate=%d time=%.1f ns/op 
allocation=%.1f B/op%n",
+          ROW_COUNTS[scenario],
+          COLUMN_COUNTS[scenario],
+          ITERATIONS[scenario],
+          summary.estimate,
+          summary.nanosPerOperation,
+          summary.allocatedBytesPerOperation);
+    }
+    Assert.assertTrue(benchmarkBlackhole > 0);
+  }
+
+  private BenchmarkResult measure(List<InsertRowNode> rows, int iterations) {
+    System.gc();
+    System.runFinalization();
+    long threadId = Thread.currentThread().getId();
+    long allocatedBytesBefore = 
ALLOCATION_MX_BEAN.getThreadAllocatedBytes(threadId);
+    long elapsedNanos = 0;
+    long previousCost = 0;
+    long estimate = 0;
+    for (int iteration = 0; iteration < iterations; iteration++) {
+      resetMemoryAccounting(previousCost);
+      long startNanos = System.nanoTime();
+      try {
+        long[] increments =
+            processor.benchmarkCheckAlignedMemCostAndAddToTspInfoForRows(rows, 
new HashSet<>());
+        estimate = increments[0];
+        benchmarkBlackhole = estimate;
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      } finally {
+        elapsedNanos += System.nanoTime() - startNanos;
+      }
+      previousCost = estimate;
+    }
+    resetMemoryAccounting(previousCost);
+    long allocatedBytes =
+        ALLOCATION_MX_BEAN.getThreadAllocatedBytes(threadId) - 
allocatedBytesBefore;
+    return new BenchmarkResult(
+        (double) elapsedNanos / iterations, (double) allocatedBytes / 
iterations, estimate);
+  }
+
+  private void runUnmeasured(List<InsertRowNode> rows, int iterations) {
+    long previousCost = 0;
+    for (int iteration = 0; iteration < iterations; iteration++) {
+      resetMemoryAccounting(previousCost);
+      try {
+        previousCost =
+            processor.benchmarkCheckAlignedMemCostAndAddToTspInfoForRows(rows, 
new HashSet<>())[0];
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    }
+    resetMemoryAccounting(previousCost);
+  }
+
+  private void resetMemoryAccounting(long memTableIncrement) {
+    if (memTableIncrement == 0) {
+      return;
+    }
+    memTable.releaseTVListRamCost(memTableIncrement);
+    dataRegionInfo.releaseStorageGroupMemCost(memTableIncrement);
+    SystemInfo.getInstance().resetStorageGroupStatus(dataRegionInfo);
+  }
+
+  private static BenchmarkResult summarize(BenchmarkResult[] results) {
+    double[] nanos = new double[results.length];
+    double[] allocatedBytes = new double[results.length];
+    for (int i = 0; i < results.length; i++) {
+      nanos[i] = results[i].nanosPerOperation;
+      allocatedBytes[i] = results[i].allocatedBytesPerOperation;
+    }
+    return new BenchmarkResult(
+        median(nanos), median(allocatedBytes), results[results.length - 
1].estimate);
+  }
+
+  private static double median(double[] values) {
+    Arrays.sort(values);
+    int middle = values.length / 2;
+    return (values.length & 1) == 1
+        ? values[middle]
+        : values[middle - 1] + (values[middle] - values[middle - 1]) / 2;
+  }
+
+  private static List<IMeasurementSchema> createSchemas(int count) {
+    List<IMeasurementSchema> schemas = new ArrayList<>(count);
+    for (int i = 0; i < count; i++) {
+      schemas.add(new MeasurementSchema("s" + i, TSDataType.INT32));
+    }
+    return schemas;
+  }
+
+  private static List<InsertRowNode> createRows(int rowCount, int columnCount)
+      throws MetadataException {
+    List<InsertRowNode> rows = new ArrayList<>(rowCount);
+    for (int row = 0; row < rowCount; row++) {
+      String[] measurements = new String[columnCount];
+      TSDataType[] dataTypes = new TSDataType[columnCount];
+      MeasurementSchema[] schemas = new MeasurementSchema[columnCount];
+      Object[] values = new Object[columnCount];
+      int offset = row % columnCount;
+      for (int column = 0; column < columnCount; column++) {
+        int sourceColumn = (column + offset) % columnCount;
+        measurements[column] = "s" + sourceColumn;
+        dataTypes[column] = TSDataType.INT32;
+        schemas[column] = new MeasurementSchema(measurements[column], 
TSDataType.INT32);
+        values[column] = ((row * columnCount + column) & 3) == 0 ? null : row 
+ sourceColumn;
+      }
+      rows.add(
+          new InsertRowNode(
+              new PlanNodeId("benchmark"),
+              new PartialPath(DEVICE_PATH),
+              true,
+              measurements,
+              dataTypes,
+              schemas,
+              row,
+              values,
+              false));
+    }
+    return rows;
+  }
+
+  private static final class BenchmarkResult {
+
+    private final double nanosPerOperation;
+    private final double allocatedBytesPerOperation;
+    private final long estimate;
+
+    private BenchmarkResult(
+        double nanosPerOperation, double allocatedBytesPerOperation, long 
estimate) {
+      this.nanosPerOperation = nanosPerOperation;
+      this.allocatedBytesPerOperation = allocatedBytesPerOperation;
+      this.estimate = estimate;
+    }
+  }
+}
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 0e2e533cc87..c878b9109d9 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
@@ -43,6 +43,7 @@ import 
org.apache.iotdb.db.storageengine.rescon.memory.PrimitiveArrayManager;
 import org.apache.iotdb.db.storageengine.rescon.memory.SystemInfo;
 import org.apache.iotdb.db.utils.EnvironmentUtils;
 import org.apache.iotdb.db.utils.constant.TestConstant;
+import org.apache.iotdb.db.utils.datastructure.AlignedTVList;
 
 import org.apache.commons.io.FileUtils;
 import org.apache.tsfile.enums.TSDataType;
@@ -74,6 +75,7 @@ import org.slf4j.LoggerFactory;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -81,6 +83,7 @@ import java.util.concurrent.ExecutionException;
 
 import static junit.framework.TestCase.assertTrue;
 import static 
org.apache.iotdb.db.storageengine.dataregion.DataRegionTest.buildInsertRowNodeByTSRecord;
+import static org.apache.tsfile.utils.RamUsageEstimator.NUM_BYTES_OBJECT_REF;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
@@ -736,7 +739,7 @@ public class TsFileProcessorTest {
     processor.insertTablet(genInsertTableNodeFors3000ToS6000(200, true), 0, 
10, new TSStatus[10]);
     Assert.assertEquals(4152728, memTable.getTVListsRamCost());
     processor.insertTablet(genInsertTableNode(300, true), 0, 10, new 
TSStatus[10]);
-    Assert.assertEquals(7537280, memTable.getTVListsRamCost());
+    Assert.assertEquals(5953280, memTable.getTVListsRamCost());
     processor.insertTablet(genInsertTableNodeFors3000ToS6000(300, true), 0, 
10, new TSStatus[10]);
     Assert.assertEquals(7705280, memTable.getTVListsRamCost());
 
@@ -760,6 +763,7 @@ public class TsFileProcessorTest {
     Assert.assertEquals(1923360, memTable.memSize());
   }
 
+  // Bitmap accounting follows the blocks that actually contain nulls, 
including a new column.
   @Test
   public void alignedBitmapMemoryAccountingMatchesActualAllocations()
       throws MetadataException, WriteProcessException, IOException, 
IllegalPathException {
@@ -803,12 +807,115 @@ public class TsFileProcessorTest {
     processor.insert(extendedColumnRow, new long[4]);
 
     int extendedColumnIndex = 
alignedMemChunk.getWorkingTVList().getBitMaps().size() - 1;
-    for (BitMap bitMap : 
alignedMemChunk.getWorkingTVList().getBitMaps().get(extendedColumnIndex)) {
-      Assert.assertNotNull(bitMap);
-    }
+    List<BitMap> extendedColumnBitMaps =
+        
alignedMemChunk.getWorkingTVList().getBitMaps().get(extendedColumnIndex);
+    Assert.assertNull(extendedColumnBitMaps.get(0));
+    Assert.assertNull(extendedColumnBitMaps.get(1));
+    Assert.assertNotNull(extendedColumnBitMaps.get(2));
     assertAlignedTvListRamCostMatchesActual(denseTablet.getDeviceID());
   }
 
+  // Measurement mapping and the target block determine which primitive arrays 
are charged.
+  @Test
+  public void alignedWriteMemCostUsesMeasurementMapping() {
+    AlignedWritableMemChunk memChunk =
+        new AlignedWritableMemChunk(
+            Arrays.asList(
+                new MeasurementSchema("s0", TSDataType.INT64),
+                new MeasurementSchema("s1", TSDataType.INT32)));
+    memChunk.putAlignedRow(0, new Object[] {1L, null});
+
+    long estimatedMemCost =
+        memChunk.alignedWriteRowMemCost(
+            new String[] {"s1", "s0"},
+            new TSDataType[] {TSDataType.INT32, TSDataType.INT64},
+            new Object[] {1, 1L},
+            0);
+
+    Assert.assertEquals(
+        AlignedTVList.valueListArrayMemCost(TSDataType.INT32) - 
NUM_BYTES_OBJECT_REF,
+        estimatedMemCost);
+
+    InsertRowNode reorderedRow = new InsertRowNode(new PlanNodeId(""));
+    reorderedRow.setMeasurements(new String[] {"s1", "s0"});
+    reorderedRow.setDataTypes(new TSDataType[] {TSDataType.INT32, 
TSDataType.INT64});
+    reorderedRow.setValues(new Object[] {1, 1L});
+    int rowsInSameBlock = 2;
+    Assert.assertEquals(
+        AlignedTVList.valueListArrayMemCost(TSDataType.INT32) - 
NUM_BYTES_OBJECT_REF,
+        memChunk.alignedWriteRowsMemCost(Collections.nCopies(rowsInSameBlock, 
reorderedRow)));
+
+    int remainingRowsInBlock = PrimitiveArrayManager.ARRAY_SIZE - 1;
+    memChunk.putAlignedTablet(
+        new long[remainingRowsInBlock],
+        new Object[] {new long[remainingRowsInBlock], null},
+        null,
+        0,
+        remainingRowsInBlock);
+    Assert.assertEquals(
+        memChunk.getWorkingTVList().alignedTvListArrayMemCost(),
+        memChunk.alignedWriteArrayMemCost(
+            new String[] {"s1", "s0"},
+            new TSDataType[] {TSDataType.INT32, TSDataType.INT64},
+            new Object[] {new int[1], new long[1]},
+            0,
+            1));
+
+    int rowsAcrossTwoBlocks = PrimitiveArrayManager.ARRAY_SIZE + 1;
+    Assert.assertEquals(
+        2 * memChunk.getWorkingTVList().alignedTvListArrayMemCost(),
+        
memChunk.alignedWriteRowsMemCost(Collections.nCopies(rowsAcrossTwoBlocks, 
reorderedRow)));
+  }
+
+  // The incremental estimator must retain new-column state when row schemas 
change at a block
+  // boundary, while an all-null occurrence must not materialize the new 
column in the old block.
+  @Test
+  public void alignedWriteRowsMemCostHandlesChangingMeasurementOrders() {
+    AlignedWritableMemChunk memChunk =
+        new AlignedWritableMemChunk(
+            Arrays.asList(
+                new MeasurementSchema("s0", TSDataType.INT64),
+                new MeasurementSchema("s1", TSDataType.INT32)));
+    int existingRows = PrimitiveArrayManager.ARRAY_SIZE - 1;
+    memChunk.putAlignedTablet(
+        new long[existingRows],
+        new Object[] {new long[existingRows], new int[existingRows]},
+        null,
+        0,
+        existingRows);
+
+    InsertRowNode firstRow = new InsertRowNode(new PlanNodeId(""));
+    firstRow.setMeasurements(new String[] {"s1", "s0", "s2"});
+    firstRow.setDataTypes(new TSDataType[] {TSDataType.INT32, 
TSDataType.INT64, TSDataType.DOUBLE});
+    firstRow.setValues(new Object[] {1, 1L, null});
+
+    InsertRowNode secondRow = new InsertRowNode(new PlanNodeId(""));
+    secondRow.setMeasurements(new String[] {"s2", "s0", "s1"});
+    secondRow.setDataTypes(
+        new TSDataType[] {TSDataType.DOUBLE, TSDataType.INT64, 
TSDataType.INT32});
+    secondRow.setValues(new Object[] {2D, 2L, 2});
+
+    InsertRowNode thirdRow = new InsertRowNode(new PlanNodeId(""));
+    thirdRow.setMeasurements(new String[] {"s2", "s0", "s1"});
+    thirdRow.setDataTypes(new TSDataType[] {TSDataType.DOUBLE, 
TSDataType.INT64, TSDataType.INT32});
+    thirdRow.setValues(new Object[] {3D, 3L, 3});
+
+    List<InsertRowNode> rows = Arrays.asList(firstRow, secondRow, thirdRow);
+    long denseArrayMemCost =
+        memChunk.alignedWriteArrayMemCost(
+            secondRow.getMeasurements(),
+            secondRow.getDataTypes(),
+            new Object[] {new double[3], new long[3], new int[3]},
+            0,
+            3);
+    long expectedMemCost =
+        denseArrayMemCost
+            - AlignedTVList.valueListArrayMemCost(TSDataType.DOUBLE)
+            + NUM_BYTES_OBJECT_REF;
+
+    Assert.assertEquals(expectedMemCost, 
memChunk.alignedWriteRowsMemCost(rows));
+  }
+
   @Test
   public void nonAlignedTvListRamCostTest()
       throws MetadataException, WriteProcessException, IOException {
@@ -1246,8 +1353,7 @@ public class TsFileProcessorTest {
   private void assertAlignedTvListRamCostMatchesActual(IDeviceID 
targetDeviceId) {
     AlignedWritableMemChunk alignedMemChunk = 
getAlignedMemChunk(targetDeviceId);
     long actualRamCost = alignedMemChunk.getWorkingTVList().getRamSize();
-    for (org.apache.iotdb.db.utils.datastructure.AlignedTVList sortedTVList :
-        alignedMemChunk.getSortedList()) {
+    for (AlignedTVList sortedTVList : alignedMemChunk.getSortedList()) {
       actualRamCost += sortedTVList.getRamSize();
     }
     Assert.assertEquals(actualRamCost, 
processor.getWorkMemTable().getTVListsRamCost());
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/datastructure/AlignedTVListTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/datastructure/AlignedTVListTest.java
index f84710bf6a0..3f8f1811997 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/datastructure/AlignedTVListTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/datastructure/AlignedTVListTest.java
@@ -32,6 +32,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 import static 
org.apache.iotdb.db.storageengine.rescon.memory.PrimitiveArrayManager.ARRAY_SIZE;
@@ -40,6 +41,36 @@ import static 
org.apache.tsfile.utils.RamUsageEstimator.NUM_BYTES_OBJECT_REF;
 
 public class AlignedTVListTest {
 
+  // A null-only column keeps a null array slot while a populated column 
materializes its array.
+  @Test
+  public void testPrimitiveArraysAreMaterializedOnlyForNonNullColumns() {
+    AlignedTVList tvList =
+        AlignedTVList.newAlignedList(Arrays.asList(TSDataType.INT64, 
TSDataType.DOUBLE));
+
+    tvList.putAlignedValue(1, new Object[] {null, 2.0D});
+    Assert.assertNull(tvList.getValues().get(0).get(0));
+    Assert.assertNotNull(tvList.getValues().get(1).get(0));
+
+    // A row containing only nulls must not allocate another value array.
+    tvList.putAlignedValue(2, new Object[] {null, null});
+    Assert.assertNull(tvList.getValues().get(0).get(0));
+    Assert.assertEquals(1, 
tvList.getValues().get(1).stream().filter(Objects::nonNull).count());
+  }
+
+  // Tablet writes allocate arrays only for input columns that carry a column 
vector.
+  @Test
+  public void testBatchWriteMaterializesOnlyColumnsWithValues() {
+    AlignedTVList tvList =
+        AlignedTVList.newAlignedList(Arrays.asList(TSDataType.INT64, 
TSDataType.INT32));
+    long[] times = {1, 2, 3};
+    Object[] columns = {null, new int[] {1, 2, 3}};
+
+    tvList.putAlignedValues(times, columns, null, 0, times.length);
+    
Assert.assertTrue(tvList.getValues().get(0).stream().allMatch(Objects::isNull));
+    Assert.assertNotNull(tvList.getValues().get(1).get(0));
+  }
+
+  // Value-array cost excludes bitmap storage because bitmaps are allocated 
independently.
   @Test
   public void testValueListArrayMemCostExcludesBitmapReservation() {
     long expected = (long) ARRAY_SIZE * Long.BYTES + NUM_BYTES_ARRAY_HEADER + 
NUM_BYTES_OBJECT_REF;
@@ -52,6 +83,23 @@ public class AlignedTVListTest {
     Assert.assertEquals(NUM_BYTES_OBJECT_REF, 
AlignedTVList.bitmapReferenceRamCost());
   }
 
+  @Test
+  public void testStaticNewAlignedListMemoryCosts() {
+    List<TSDataType> dataTypes = Arrays.asList(TSDataType.INT64, 
TSDataType.INT32);
+    AlignedTVList tvList = AlignedTVList.newAlignedList(dataTypes);
+
+    Assert.assertEquals(
+        tvList.getRamSize(), 
AlignedTVList.alignedTvListInitialMemCost(dataTypes.size()));
+    long primitiveArrayMemCost =
+        dataTypes.stream()
+            .mapToLong(
+                dataType -> AlignedTVList.valueListArrayMemCost(dataType) - 
NUM_BYTES_OBJECT_REF)
+            .sum();
+    Assert.assertEquals(
+        tvList.alignedTvListArrayMemCost() - primitiveArrayMemCost,
+        
AlignedTVList.alignedTvListArrayMemCostWithoutPrimitiveArrays(dataTypes.size()));
+  }
+
   @Test
   public void testAlignedTVList1() {
     List<TSDataType> dataTypes = new ArrayList<>();
@@ -163,6 +211,7 @@ public class AlignedTVListTest {
     }
   }
 
+  // A null first appears in the third block, so only that block receives a 
compact bitmap.
   @Test
   public void testBitmapIsAllocatedLazilyWithCompactBackingArray() {
     AlignedTVList tvList =
@@ -184,18 +233,16 @@ public class AlignedTVListTest {
         ARRAY_SIZE / Byte.SIZE + 1, 
firstColumnBitMaps.get(2).getByteArray().length);
     Assert.assertTrue(tvList.isNullValue(ARRAY_SIZE * 2 + 1, 0));
     Assert.assertFalse(tvList.isNullValue(ARRAY_SIZE * 2, 0));
-    long primitiveArrayAndBitmapCost =
-        3L * tvList.alignedTvListArrayMemCost()
-            + 3L * AlignedTVList.bitmapReferenceRamCost()
-            + AlignedTVList.bitmapRamCost();
-    Assert.assertTrue(tvList.getRamSize() > primitiveArrayAndBitmapCost);
+    Assert.assertEquals(3, 
tvList.getValues().get(0).stream().filter(Objects::nonNull).count());
+    Assert.assertEquals(3, 
tvList.getValues().get(1).stream().filter(Objects::nonNull).count());
     Assert.assertEquals(tvList.getRamSize(), 
tvList.calculateRamSize().getRamSize());
     Assert.assertEquals(tvList.getRamSize(), tvList.clone().getRamSize());
     Assert.assertEquals(tvList.getRamSize(), 
tvList.cloneForFlushSort().getRamSize());
   }
 
+  // Extending a populated TVList creates null slots but no value arrays or 
bitmap structures.
   @Test
-  public void testExtendedColumnRamCostIncludesActualBitmaps() {
+  public void testExtendColumnDoesNotMaterializeArraysOrBitmaps() {
     AlignedTVList tvList =
         AlignedTVList.newAlignedList(new 
ArrayList<>(Arrays.asList(TSDataType.INT64)));
     for (int i = 0; i <= ARRAY_SIZE; i++) {
@@ -203,18 +250,26 @@ public class AlignedTVListTest {
     }
 
     long ramSizeBeforeExtension = tvList.getRamSize();
+    int oldColumnCount = tvList.getTsDataTypes().size();
+    long expectedExtensionCost =
+        (long) tvList.getTimestamps().size() * NUM_BYTES_OBJECT_REF
+            + 2L
+                * (RamUsageEstimator.sizeOfObjectArray(oldColumnCount + 1)
+                    - RamUsageEstimator.sizeOfObjectArray(oldColumnCount))
+            + RamUsageEstimator.sizeOfLongArray(oldColumnCount + 1)
+            - RamUsageEstimator.sizeOfLongArray(oldColumnCount)
+            + RamUsageEstimator.shallowSizeOf(new ArrayList<>())
+            + RamUsageEstimator.sizeOfObjectArray(0);
     tvList.extendColumn(TSDataType.INT32);
 
-    Assert.assertTrue(
-        tvList.getRamSize() - ramSizeBeforeExtension
-            >= 2L
-                * (AlignedTVList.valueListArrayMemCost(TSDataType.INT32)
-                    + AlignedTVList.bitmapReferenceRamCost()
-                    + AlignedTVList.bitmapRamCost()));
+    Assert.assertEquals(expectedExtensionCost, tvList.getRamSize() - 
ramSizeBeforeExtension);
+    
Assert.assertTrue(tvList.getValues().get(1).stream().allMatch(Objects::isNull));
+    Assert.assertNull(tvList.getBitMaps());
     tvList.clear();
     Assert.assertTrue(tvList.getRamSize() > 0);
   }
 
+  // An input bitmap without marked values must not create a retained TVList 
bitmap.
   @Test
   public void testEmptyInputBitmapsDoNotMaterializeMemTableBitmaps() {
     AlignedTVList tvList = 
AlignedTVList.newAlignedList(Arrays.asList(TSDataType.INT64));

Reply via email to