This is an automated email from the ASF dual-hosted git repository.
JackieTien97 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 9e2074d26bd [to dev/1.3] Optimize aligned TVList partial-column clone
memory usage (#18454)
9e2074d26bd is described below
commit 9e2074d26bd67c7afe7de7e810e6ed7f2cf62cc2
Author: shuwenwei <[email protected]>
AuthorDate: Thu Aug 13 18:14:16 2026 +0800
[to dev/1.3] Optimize aligned TVList partial-column clone memory usage
(#18454)
---
.../execution/MemoryEstimationHelper.java | 2 +-
.../fragment/FragmentInstanceContext.java | 4 +-
.../db/utils/datastructure/AlignedTVList.java | 124 ++++++++----------
.../db/utils/datastructure/BackAlignedTVList.java | 6 +-
.../db/utils/datastructure/QuickAlignedTVList.java | 6 +-
.../iotdb/db/utils/datastructure/TVList.java | 5 -
.../db/utils/datastructure/TimAlignedTVList.java | 6 +-
.../fragment/FragmentInstanceExecutionTest.java | 145 +++++++++++++++++++++
.../dataregion/memtable/TsFileProcessorTest.java | 28 ++--
.../db/utils/datastructure/AlignedTVListTest.java | 43 ++++++
10 files changed, 272 insertions(+), 97 deletions(-)
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/MemoryEstimationHelper.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/MemoryEstimationHelper.java
index ba6660f3d6a..1737b48cf18 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/MemoryEstimationHelper.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/MemoryEstimationHelper.java
@@ -44,7 +44,7 @@ public class MemoryEstimationHelper {
private static final long MEASUREMENT_PATH_INSTANCE_SIZE =
RamUsageEstimator.shallowSizeOfInstance(AlignedPath.class);
- private static final long ARRAY_LIST_INSTANCE_SIZE =
+ public static final long ARRAY_LIST_INSTANCE_SIZE =
RamUsageEstimator.shallowSizeOfInstance(ArrayList.class);
private static final long INTEGER_INSTANCE_SIZE =
RamUsageEstimator.shallowSizeOfInstance(Integer.class);
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceContext.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceContext.java
index 24b233efcdf..14a90159dac 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceContext.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceContext.java
@@ -165,7 +165,7 @@ public class FragmentInstanceContext extends QueryContext {
private boolean highestPriority = false;
// accessed value columns on each referenced AlignedTVList.
- private final Map<TVList, Set<Integer>> alignedTVListColumnAccessMap = new
ConcurrentHashMap<>();
+ private Map<TVList, Set<Integer>> alignedTVListColumnAccessMap = new
ConcurrentHashMap<>();
public static FragmentInstanceContext createFragmentInstanceContext(
FragmentInstanceId id,
@@ -1027,7 +1027,7 @@ public class FragmentInstanceContext extends QueryContext
{
// release TVList/AlignedTVList owned by current query
releaseTVListOwnedByQuery();
- alignedTVListColumnAccessMap.clear();
+ alignedTVListColumnAccessMap = null;
fileModCache = null;
nonExistentModFiles = null;
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 d8b16b3eb84..a3cb492c9a1 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
@@ -19,6 +19,7 @@
package org.apache.iotdb.db.utils.datastructure;
+import org.apache.iotdb.db.queryengine.execution.MemoryEstimationHelper;
import
org.apache.iotdb.db.queryengine.execution.fragment.FragmentInstanceContext;
import org.apache.iotdb.db.queryengine.execution.fragment.QueryContext;
import org.apache.iotdb.db.queryengine.plan.statement.component.Ordering;
@@ -92,10 +93,9 @@ public abstract class AlignedTVList extends TVList {
public static final class PartialClonePlan {
private final AlignedTVList sourceList;
private final AlignedTVList cloneList;
- private final List<Object>[] valueColumnsToMove;
- private final List<BitMap>[] bitmapColumnsToMove;
- private final long sourceArrayMemCostWithoutIndex;
- private final long cloneArrayMemCostWithoutIndex;
+ // The columns retained by the source. commit() derives the moved columns
from this set, so
+ // no O(N) move-plan arrays need to be allocated during preparation.
+ private final Set<Integer> retainedColumns;
private final long sourceBitmapMemoryCost;
private final long cloneBitmapMemoryCost;
@@ -104,18 +104,12 @@ public abstract class AlignedTVList extends TVList {
private PartialClonePlan(
AlignedTVList sourceList,
AlignedTVList cloneList,
- List<Object>[] valueColumnsToMove,
- List<BitMap>[] bitmapColumnsToMove,
- long sourceArrayMemCostWithoutIndex,
- long cloneArrayMemCostWithoutIndex,
+ Set<Integer> retainedColumns,
long sourceBitmapMemoryCost,
long cloneBitmapMemoryCost) {
this.sourceList = sourceList;
this.cloneList = cloneList;
- this.valueColumnsToMove = valueColumnsToMove;
- this.bitmapColumnsToMove = bitmapColumnsToMove;
- this.sourceArrayMemCostWithoutIndex = sourceArrayMemCostWithoutIndex;
- this.cloneArrayMemCostWithoutIndex = cloneArrayMemCostWithoutIndex;
+ this.retainedColumns = retainedColumns;
this.sourceBitmapMemoryCost = sourceBitmapMemoryCost;
this.cloneBitmapMemoryCost = cloneBitmapMemoryCost;
}
@@ -149,13 +143,17 @@ public abstract class AlignedTVList extends TVList {
private final AlignedTVList outer = this;
AlignedTVList(List<TSDataType> types) {
+ this(types, true);
+ }
+
+ AlignedTVList(List<TSDataType> types, boolean initializeValueColumns) {
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<>());
+ values.add(initializeValueColumns ? new ArrayList<>() : null);
}
// arrayMemCostWithoutIndex depends on per-column value arrays, so values
must be
// initialized before computing it
@@ -163,48 +161,19 @@ public abstract class AlignedTVList extends TVList {
}
public static AlignedTVList newAlignedList(List<TSDataType> dataTypes) {
+ return newAlignedList(dataTypes, true);
+ }
+
+ public static AlignedTVList newAlignedList(
+ List<TSDataType> dataTypes, boolean initializeValueColumns) {
switch (TVLIST_SORT_ALGORITHM) {
case QUICK:
- return new QuickAlignedTVList(dataTypes);
+ return new QuickAlignedTVList(dataTypes, initializeValueColumns);
case BACKWARD:
- return new BackAlignedTVList(dataTypes);
+ return new BackAlignedTVList(dataTypes, initializeValueColumns);
default:
- return new TimAlignedTVList(dataTypes);
- }
- }
-
- @Override
- public TVList getTvListByColumnIndex(List<Integer> columnIndex,
List<TSDataType> dataTypeList) {
- List<List<Object>> values = new ArrayList<>();
- List<List<BitMap>> bitMaps = null;
- for (int i = 0; i < columnIndex.size(); i++) {
- // columnIndex == -1 means querying a non-exist column, add null column
here
- if (columnIndex.get(i) == -1) {
- values.add(null);
- } else {
- values.add(this.values.get(columnIndex.get(i)));
- if (this.bitMaps != null && this.bitMaps.get(columnIndex.get(i)) !=
null) {
- if (bitMaps == null) {
- bitMaps = new ArrayList<>(columnIndex.size());
- for (int j = 0; j < columnIndex.size(); j++) {
- bitMaps.add(null);
- }
- }
- bitMaps.set(i, this.bitMaps.get(columnIndex.get(i)));
- }
- }
+ return new TimAlignedTVList(dataTypes, initializeValueColumns);
}
- AlignedTVList alignedTvList = AlignedTVList.newAlignedList(new
ArrayList<>(dataTypeList));
- alignedTvList.timestamps = this.timestamps;
- alignedTvList.indices = this.indices;
- alignedTvList.values = values;
- alignedTvList.bitMaps = bitMaps;
- alignedTvList.rowCount = this.rowCount;
- alignedTvList.allValueColDeletedMap = getAllValueColDeletedMap();
- alignedTvList.materializedBitmapMemoryCost =
calculateBitmapRamCost(bitMaps, null);
- alignedTvList.refreshArrayMemCostWithoutIndex();
- alignedTvList.refreshMaterializedValueArrayMemoryCost();
- return alignedTvList;
}
@Override
@@ -236,13 +205,19 @@ public abstract class AlignedTVList extends TVList {
public synchronized PartialClonePlan preparePartialClone(Set<Integer>
columnsToClone) {
Set<Integer> retainedColumns =
new HashSet<>(Objects.requireNonNull(columnsToClone, "columnsToClone
cannot be null"));
- AlignedTVList cloneList = AlignedTVList.newAlignedList(new
ArrayList<>(dataTypes));
+ AlignedTVList cloneList = AlignedTVList.newAlignedList(new
ArrayList<>(dataTypes), false);
+ // Pre-create the inner value lists for the retained columns; the other
slots stay null until
+ // the ownership transfer moves the source columns into place.
+ for (int i = 0; i < values.size(); i++) {
+ if (retainedColumns.contains(i)) {
+ cloneList.values.set(i, new ArrayList<>(values.get(i).size()));
+ }
+ }
cloneAs(cloneList);
cloneColumnDataTo(cloneList, retainedColumns);
return prepareMovePlan(cloneList, retainedColumns);
}
- @SuppressWarnings("unchecked")
private PartialClonePlan prepareMovePlan(AlignedTVList cloneList,
Set<Integer> retainedColumns) {
Objects.requireNonNull(cloneList, "cloneList cannot be null");
int columnCount = values.size();
@@ -251,23 +226,21 @@ public abstract class AlignedTVList extends TVList {
throw new IllegalStateException("Target AlignedTVList has incompatible
column containers");
}
- List<Object>[] valueColumnsToMove = (List<Object>[]) new
List<?>[columnCount];
- List<BitMap>[] bitmapColumnsToMove = (List<BitMap>[]) new
List<?>[columnCount];
+ // Validate the move without allocating any O(N) move-plan arrays;
commit() re-derives the
+ // moved columns from the retained set, which only needs this validation
to be complete.
for (int i = 0; i < columnCount; i++) {
if (retainedColumns.contains(i)) {
continue;
}
- List<Object> columnValues = values.get(i);
- if (columnValues == null) {
+ if (values.get(i) == null) {
throw new IllegalStateException(
String.format("Missing value arrays for aligned column index %d
during move", i));
}
- if (cloneList.values.get(i) == null ||
!cloneList.values.get(i).isEmpty()) {
+ if (cloneList.values.get(i) != null) {
throw new IllegalStateException(
String.format("Target value column index %d is not ready for
move", i));
}
- valueColumnsToMove[i] = columnValues;
if (bitMaps != null && bitMaps.get(i) != null) {
if (cloneList.bitMaps == null
@@ -276,31 +249,32 @@ public abstract class AlignedTVList extends TVList {
throw new IllegalStateException(
String.format("Target bitmap column index %d is not ready for
move", i));
}
- bitmapColumnsToMove[i] = bitMaps.get(i);
}
}
return new PartialClonePlan(
this,
cloneList,
- valueColumnsToMove,
- bitmapColumnsToMove,
- calculateArrayMemCostWithoutIndex(retainedColumns),
- cloneList.calculateArrayMemCostWithoutIndex(null),
+ retainedColumns,
calculateBitmapRamCost(bitMaps, retainedColumns),
calculateBitmapRamCost(bitMaps, null));
}
private synchronized void commitPartialClone(PartialClonePlan plan) {
- for (int i = 0; i < plan.valueColumnsToMove.length; i++) {
- List<Object> columnValues = plan.valueColumnsToMove[i];
+ Set<Integer> retainedColumns = plan.retainedColumns;
+ for (int i = 0; i < dataTypes.size(); i++) {
+ if (retainedColumns.contains(i)) {
+ continue;
+ }
+ List<Object> columnValues = values.get(i);
if (columnValues == null) {
+ // Defensive: prepareMovePlan already validated that every moved
column is materialized.
continue;
}
plan.cloneList.values.set(i, columnValues);
values.set(i, null);
- List<BitMap> columnBitMaps = plan.bitmapColumnsToMove[i];
+ List<BitMap> columnBitMaps = bitMaps == null ? null : bitMaps.get(i);
if (columnBitMaps != null) {
plan.cloneList.bitMaps.set(i, columnBitMaps);
bitMaps.set(i, null);
@@ -308,8 +282,8 @@ public abstract class AlignedTVList extends TVList {
memoryBinaryChunkSize[i] = 0;
}
- arrayMemCostWithoutIndex = plan.sourceArrayMemCostWithoutIndex;
- plan.cloneList.arrayMemCostWithoutIndex =
plan.cloneArrayMemCostWithoutIndex;
+ refreshArrayMemCostWithoutIndex();
+ plan.cloneList.refreshArrayMemCostWithoutIndex();
materializedBitmapMemoryCost = plan.sourceBitmapMemoryCost;
plan.cloneList.materializedBitmapMemoryCost = plan.cloneBitmapMemoryCost;
refreshMaterializedValueArrayMemoryCost();
@@ -1252,6 +1226,7 @@ public abstract class AlignedTVList extends TVList {
size += listRamCostWithReferences(dataTypes);
size += RamUsageEstimator.sizeOfLongArray(memoryBinaryChunkSize.length);
+ size +=
RamUsageEstimator.sizeOfIntArray(materializedValueArrayCounts.length);
size += listRamCostWithoutReferences(timestamps);
if (indices != null) {
size += listRamCostWithoutReferences(indices);
@@ -1284,11 +1259,12 @@ public abstract class AlignedTVList extends TVList {
}
private static long listRamCostWithReferences(List<?> list) {
- return RamUsageEstimator.shallowSizeOf(list) +
RamUsageEstimator.sizeOfObjectArray(list.size());
+ return MemoryEstimationHelper.ARRAY_LIST_INSTANCE_SIZE
+ + RamUsageEstimator.sizeOfObjectArray(list.size());
}
private static long listRamCostWithoutReferences(List<?> list) {
- return RamUsageEstimator.shallowSizeOf(list)
+ return MemoryEstimationHelper.ARRAY_LIST_INSTANCE_SIZE
+ (list.isEmpty() ? 0 : RamUsageEstimator.sizeOfObjectArray(0));
}
@@ -1347,11 +1323,12 @@ public abstract class AlignedTVList extends TVList {
/** Initial list-container memory before the first aligned row is written. */
public static long alignedTvListInitialMemCost(int measurementColumnCount) {
- long arrayListShallowSize =
RamUsageEstimator.shallowSizeOfInstance(ArrayList.class);
+ long arrayListShallowSize =
MemoryEstimationHelper.ARRAY_LIST_INSTANCE_SIZE;
long listWithReferencesSize =
arrayListShallowSize +
RamUsageEstimator.sizeOfObjectArray(measurementColumnCount);
return 2 * listWithReferencesSize
+ RamUsageEstimator.sizeOfLongArray(measurementColumnCount)
+ + RamUsageEstimator.sizeOfIntArray(measurementColumnCount)
+ arrayListShallowSize
+ (long) measurementColumnCount * arrayListShallowSize;
}
@@ -1553,6 +1530,9 @@ public abstract class AlignedTVList extends TVList {
size +=
RamUsageEstimator.sizeOfLongArray(newTotalColumnCount)
- RamUsageEstimator.sizeOfLongArray(oldColumnCount);
+ size +=
+ RamUsageEstimator.sizeOfIntArray(newTotalColumnCount)
+ - RamUsageEstimator.sizeOfIntArray(oldColumnCount);
size +=
RamUsageEstimator.sizeOfObjectArray(newTotalColumnCount)
- RamUsageEstimator.sizeOfObjectArray(oldColumnCount);
@@ -1562,7 +1542,7 @@ public abstract class AlignedTVList extends TVList {
- RamUsageEstimator.sizeOfObjectArray(oldColumnCount);
}
long newColumnContainerCost =
- RamUsageEstimator.shallowSizeOf(new ArrayList<>())
+ MemoryEstimationHelper.ARRAY_LIST_INSTANCE_SIZE
+ (timestamps.isEmpty() ? 0 :
RamUsageEstimator.sizeOfObjectArray(0));
return size + newColumnCount * newColumnContainerCost;
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/BackAlignedTVList.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/BackAlignedTVList.java
index 2603790bd66..c4f5174d195 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/BackAlignedTVList.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/BackAlignedTVList.java
@@ -27,7 +27,11 @@ public class BackAlignedTVList extends QuickAlignedTVList {
private final BackwardSort policy;
BackAlignedTVList(List<TSDataType> types) {
- super(types);
+ this(types, true);
+ }
+
+ BackAlignedTVList(List<TSDataType> types, boolean initializeValueColumns) {
+ super(types, initializeValueColumns);
policy = new BackwardSort(this);
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/QuickAlignedTVList.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/QuickAlignedTVList.java
index c5bd5550056..9ea50a06246 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/QuickAlignedTVList.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/QuickAlignedTVList.java
@@ -26,7 +26,11 @@ public class QuickAlignedTVList extends AlignedTVList {
private final QuickSort policy;
QuickAlignedTVList(List<TSDataType> types) {
- super(types);
+ this(types, true);
+ }
+
+ QuickAlignedTVList(List<TSDataType> types, boolean initializeValueColumns) {
+ super(types, initializeValueColumns);
policy = new QuickSort(this);
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/TVList.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/TVList.java
index 60e421c9af5..2536836069c 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/TVList.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/TVList.java
@@ -540,11 +540,6 @@ public abstract class TVList implements WALEntryValue {
throw new UnsupportedOperationException(ERR_DATATYPE_NOT_CONSISTENT);
}
- public TVList getTvListByColumnIndex(
- List<Integer> columnIndexList, List<TSDataType> dataTypeList) {
- throw new UnsupportedOperationException(ERR_DATATYPE_NOT_CONSISTENT);
- }
-
public long getMaxTime() {
return maxTime;
}
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/TimAlignedTVList.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/TimAlignedTVList.java
index 61d5c75096e..c39297d59b6 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/TimAlignedTVList.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/datastructure/TimAlignedTVList.java
@@ -26,7 +26,11 @@ public class TimAlignedTVList extends AlignedTVList {
private final TimSort policy;
TimAlignedTVList(List<TSDataType> types) {
- super(types);
+ this(types, true);
+ }
+
+ TimAlignedTVList(List<TSDataType> types, boolean initializeValueColumns) {
+ super(types, initializeValueColumns);
policy = new TimSort(this);
}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceExecutionTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceExecutionTest.java
index 0f1b1c7d253..8bde6b302d5 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceExecutionTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/fragment/FragmentInstanceExecutionTest.java
@@ -22,6 +22,7 @@ package org.apache.iotdb.db.queryengine.execution.fragment;
import org.apache.iotdb.commons.concurrent.IoTDBThreadPoolFactory;
import org.apache.iotdb.commons.exception.IllegalPathException;
import org.apache.iotdb.commons.exception.MetadataException;
+import org.apache.iotdb.commons.path.AlignedPath;
import org.apache.iotdb.commons.path.MeasurementPath;
import org.apache.iotdb.commons.path.PartialPath;
import org.apache.iotdb.db.conf.IoTDBDescriptor;
@@ -48,9 +49,12 @@ import com.google.common.collect.ImmutableSet;
import org.apache.tsfile.enums.TSDataType;
import org.apache.tsfile.file.metadata.enums.CompressionType;
import org.apache.tsfile.file.metadata.enums.TSEncoding;
+import org.apache.tsfile.read.TimeValuePair;
import org.apache.tsfile.read.reader.IPointReader;
+import org.apache.tsfile.utils.TsPrimitiveType;
import org.apache.tsfile.write.schema.IMeasurementSchema;
import org.apache.tsfile.write.schema.MeasurementSchema;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mockito;
@@ -58,7 +62,9 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
@@ -67,11 +73,23 @@ import static
org.apache.iotdb.db.queryengine.common.QueryId.MOCK_QUERY_ID;
import static
org.apache.iotdb.db.queryengine.execution.fragment.FragmentInstanceContext.createFragmentInstanceContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class FragmentInstanceExecutionTest {
+ @BeforeClass
+ public static void setUpClass() {
+ // Initialize DataNodeId before any test to avoid
ExceptionInInitializerError when
+ // Coordinator.<clinit> is triggered indirectly by async state-change
listeners
+ // (e.g., via QueryRelatedResourceMetricSet -> Coordinator ->
QueryIdGenerator).
+ IoTDBDescriptor.getInstance().getConfig().setDataNodeId(1);
+ }
+
@Test
public void testFragmentInstanceExecution() {
ExecutorService instanceNotificationExecutor =
@@ -239,6 +257,133 @@ public class FragmentInstanceExecutionTest {
}
}
+ @Test
+ public void testAlignedTVListPartialColumnCloneEndToEnd() {
+ ExecutorService instanceNotificationExecutor =
+ IoTDBThreadPoolFactory.newFixedThreadPool(2,
"test-aligned-partial-clone");
+ try {
+ // Create an unsorted aligned MemTable with 5 columns. Row i (0..99) has
+ // value i * 100 + j in column j, and rows are inserted in reverse order.
+ List<IMeasurementSchema> schemaList = new ArrayList<>();
+ for (int i = 0; i < 5; i++) {
+ schemaList.add(new MeasurementSchema("sensor_" + i, TSDataType.INT64));
+ }
+ String deviceId = "d1";
+ IMemTable memTable = createMemTable(deviceId, schemaList);
+
+ assertEquals(1, memTable.getMemTableMap().size());
+ IWritableMemChunkGroup memChunkGroup =
memTable.getMemTableMap().values().iterator().next();
+ assertEquals(1, memChunkGroup.getMemChunkMap().size());
+ IWritableMemChunk memChunk =
memChunkGroup.getMemChunkMap().values().iterator().next();
+ AlignedTVList workingTvList = (AlignedTVList)
memChunk.getWorkingTVList();
+ assertFalse(workingTvList.isSorted());
+ assertEquals(100, workingTvList.rowCount());
+
+ // Two concurrent query contexts.
+ FragmentInstanceId id1 = new FragmentInstanceId(new
PlanFragmentId(MOCK_QUERY_ID, 1), "1");
+ FragmentInstanceStateMachine stateMachine1 =
+ new FragmentInstanceStateMachine(id1, instanceNotificationExecutor);
+ FragmentInstanceContext context1 = createFragmentInstanceContext(id1,
stateMachine1);
+
+ FragmentInstanceId id2 = new FragmentInstanceId(new
PlanFragmentId(MOCK_QUERY_ID, 2), "2");
+ FragmentInstanceStateMachine stateMachine2 =
+ new FragmentInstanceStateMachine(id2, instanceNotificationExecutor);
+ FragmentInstanceContext context2 = createFragmentInstanceContext(id2,
stateMachine2);
+
+ // Query 1: sensor_2 and sensor_0. It stays active on the unsorted
working TVList
+ // (no point reader is opened yet, so the list is not sorted).
+ List<String> measurements1 = Arrays.asList("sensor_2", "sensor_0");
+ List<IMeasurementSchema> schemas1 = Arrays.asList(schemaList.get(2),
schemaList.get(0));
+ AlignedPath fullPath1 = new AlignedPath(deviceId, measurements1,
schemas1);
+ ReadOnlyMemChunk readOnlyMemChunk1 =
+ memTable.query(context1, fullPath1, Long.MIN_VALUE, null, null);
+
+ // Query 2: sensor_1 and sensor_3. Because Query 1 is still active on
the unsorted
+ // working TVList, this triggers clone-and-swap of the working TVList.
+ List<String> measurements2 = Arrays.asList("sensor_1", "sensor_3");
+ List<IMeasurementSchema> schemas2 = Arrays.asList(schemaList.get(1),
schemaList.get(3));
+ AlignedPath fullPath2 = new AlignedPath(deviceId, measurements2,
schemas2);
+ ReadOnlyMemChunk readOnlyMemChunk2 =
+ memTable.query(context2, fullPath2, Long.MIN_VALUE, null, null);
+
+ // Query 1's columns (0 and 2) stay in the old working TVList; the other
columns are
+ // moved to the clone.
+ assertEquals(
+ new HashSet<>(Arrays.asList(0, 2)),
context1.getAccessedAlignedColumns(workingTvList));
+ assertNotNull(workingTvList.getValues().get(0));
+ assertNull(workingTvList.getValues().get(1));
+ assertNotNull(workingTvList.getValues().get(2));
+ assertNull(workingTvList.getValues().get(3));
+ assertNull(workingTvList.getValues().get(4));
+
+ // The memChunk now points to the clone, which owns all 5 columns.
+ AlignedTVList cloneTvList = (AlignedTVList) memChunk.getWorkingTVList();
+ assertNotSame(workingTvList, cloneTvList);
+ assertEquals(100, cloneTvList.rowCount());
+ assertNotNull(cloneTvList.getValues().get(0));
+ assertNotNull(cloneTvList.getValues().get(1));
+ assertNotNull(cloneTvList.getValues().get(2));
+ assertNotNull(cloneTvList.getValues().get(3));
+ assertNotNull(cloneTvList.getValues().get(4));
+
+ // The old working TVList is owned by Query 1, which also reserved its
memory.
+ assertSame(context1, workingTvList.getOwnerQuery());
+ assertTrue(workingTvList.getReservedMemoryBytes() > 0);
+
+ // Both queries must still read all 100 rows with correct values.
+ IPointReader pointReader1 = readOnlyMemChunk1.getPointReader();
+ IPointReader pointReader2 = readOnlyMemChunk2.getPointReader();
+ long row = 0;
+ while (pointReader1.hasNextTimeValuePair() &&
pointReader2.hasNextTimeValuePair()) {
+ TimeValuePair tvPair1 = pointReader1.nextTimeValuePair();
+ TimeValuePair tvPair2 = pointReader2.nextTimeValuePair();
+
+ assertEquals(row, tvPair1.getTimestamp());
+ assertEquals(row, tvPair2.getTimestamp());
+
+ // Query 1 reads [sensor_2, sensor_0] in query order.
+ TsPrimitiveType[] values1 = tvPair1.getValue().getVector();
+ assertEquals(2, values1.length);
+ assertEquals(row * 100 + 2, values1[0].getLong());
+ assertEquals(row * 100 + 0, values1[1].getLong());
+
+ // Query 2 reads [sensor_1, sensor_3] in query order.
+ TsPrimitiveType[] values2 = tvPair2.getValue().getVector();
+ assertEquals(2, values2.length);
+ assertEquals(row * 100 + 1, values2[0].getLong());
+ assertEquals(row * 100 + 3, values2[1].getLong());
+ row++;
+ }
+ assertEquals(100, row);
+ assertFalse(pointReader1.hasNextTimeValuePair());
+ assertFalse(pointReader2.hasNextTimeValuePair());
+
+ // Before ending the queries, the retained source's reserved memory must
equal the value
+ // recalculated at cleanup time - this is exactly the accounting
releaseTVListOwnedByQuery
+ // validates when the owning query ends (a mismatch would emit a WARN
during release).
+ long reservedBeforeRelease = workingTvList.getReservedMemoryBytes();
+ assertTrue(reservedBeforeRelease > 0);
+ assertEquals(workingTvList.calculateRamSize().getRamSize(),
reservedBeforeRelease);
+
+ // End Query 1, the owner of the retained source. No other query uses
the source any more,
+ // so the final release must return its reserved memory and clear the
list.
+ context1.releaseResource();
+ assertTrue(workingTvList.getQueryContextSet().isEmpty());
+ assertEquals(0, workingTvList.rowCount());
+
+ // End Query 2. The clone is now the memTable's working TVList, so
cleanup only detaches the
+ // query from it; the clone itself must remain intact as the working
list.
+ context2.releaseResource();
+ assertTrue(cloneTvList.getQueryContextSet().isEmpty());
+ assertEquals(100, cloneTvList.rowCount());
+ assertSame(cloneTvList, memChunk.getWorkingTVList());
+ } catch (Exception e) {
+ fail(e.getMessage());
+ } finally {
+ instanceNotificationExecutor.shutdown();
+ }
+ }
+
private FragmentInstanceExecution createFragmentInstanceExecution(int id,
Executor executor)
throws CpuNotEnoughException {
IDriverScheduler scheduler = Mockito.mock(IDriverScheduler.class);
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 c878b9109d9..3ec333d264e 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
@@ -691,11 +691,11 @@ public class TsFileProcessorTest {
// Test Tablet
processor.insertTablet(genInsertTableNode(0, true), 0, 10, new
TSStatus[10]);
IMemTable memTable = processor.getWorkMemTable();
- Assert.assertEquals(1764688, memTable.getTVListsRamCost());
+ Assert.assertEquals(1776704, memTable.getTVListsRamCost());
processor.insertTablet(genInsertTableNode(100, true), 0, 10, new
TSStatus[10]);
- Assert.assertEquals(1764688, memTable.getTVListsRamCost());
+ Assert.assertEquals(1776704, memTable.getTVListsRamCost());
processor.insertTablet(genInsertTableNode(200, true), 0, 10, new
TSStatus[10]);
- Assert.assertEquals(1764688, memTable.getTVListsRamCost());
+ Assert.assertEquals(1776704, memTable.getTVListsRamCost());
Assert.assertEquals(90000, memTable.getTotalPointsNum());
Assert.assertEquals(720360, memTable.memSize());
// Test records
@@ -704,7 +704,7 @@ public class TsFileProcessorTest {
record.addTuple(DataPoint.getDataPoint(dataType, measurementId,
String.valueOf(i)));
processor.insert(buildInsertRowNodeByTSRecord(record), new long[4]);
}
- Assert.assertEquals(1766304, memTable.getTVListsRamCost());
+ Assert.assertEquals(1778320, memTable.getTVListsRamCost());
Assert.assertEquals(90100, memTable.getTotalPointsNum());
Assert.assertEquals(721560, memTable.memSize());
}
@@ -727,21 +727,21 @@ public class TsFileProcessorTest {
// Test Tablet
processor.insertTablet(genInsertTableNode(0, true), 0, 10, new
TSStatus[10]);
IMemTable memTable = processor.getWorkMemTable();
- Assert.assertEquals(1764688, memTable.getTVListsRamCost());
+ Assert.assertEquals(1776704, memTable.getTVListsRamCost());
processor.insertTablet(genInsertTableNodeFors3000ToS6000(0, true), 0, 10,
new TSStatus[10]);
- Assert.assertEquals(4152728, memTable.getTVListsRamCost());
+ Assert.assertEquals(4176744, memTable.getTVListsRamCost());
processor.insertTablet(genInsertTableNode(100, true), 0, 10, new
TSStatus[10]);
- Assert.assertEquals(4152728, memTable.getTVListsRamCost());
+ Assert.assertEquals(4176744, memTable.getTVListsRamCost());
processor.insertTablet(genInsertTableNodeFors3000ToS6000(100, true), 0,
10, new TSStatus[10]);
- Assert.assertEquals(4152728, memTable.getTVListsRamCost());
+ Assert.assertEquals(4176744, memTable.getTVListsRamCost());
processor.insertTablet(genInsertTableNode(200, true), 0, 10, new
TSStatus[10]);
- Assert.assertEquals(4152728, memTable.getTVListsRamCost());
+ Assert.assertEquals(4176744, memTable.getTVListsRamCost());
processor.insertTablet(genInsertTableNodeFors3000ToS6000(200, true), 0,
10, new TSStatus[10]);
- Assert.assertEquals(4152728, memTable.getTVListsRamCost());
+ Assert.assertEquals(4176744, memTable.getTVListsRamCost());
processor.insertTablet(genInsertTableNode(300, true), 0, 10, new
TSStatus[10]);
- Assert.assertEquals(5953280, memTable.getTVListsRamCost());
+ Assert.assertEquals(5977296, memTable.getTVListsRamCost());
processor.insertTablet(genInsertTableNodeFors3000ToS6000(300, true), 0,
10, new TSStatus[10]);
- Assert.assertEquals(7705280, memTable.getTVListsRamCost());
+ Assert.assertEquals(7729296, memTable.getTVListsRamCost());
Assert.assertEquals(240000, memTable.getTotalPointsNum());
Assert.assertEquals(1920960, memTable.memSize());
@@ -751,14 +751,14 @@ public class TsFileProcessorTest {
record.addTuple(DataPoint.getDataPoint(dataType, measurementId,
String.valueOf(i)));
processor.insert(buildInsertRowNodeByTSRecord(record), new long[4]);
}
- Assert.assertEquals(7706896, memTable.getTVListsRamCost());
+ Assert.assertEquals(7730912, memTable.getTVListsRamCost());
// Test records
for (int i = 1; i <= 100; i++) {
TSRecord record = new TSRecord(i, deviceId);
record.addTuple(DataPoint.getDataPoint(dataType, "s1",
String.valueOf(i)));
processor.insert(buildInsertRowNodeByTSRecord(record), new long[4]);
}
- Assert.assertEquals(7708512, memTable.getTVListsRamCost());
+ Assert.assertEquals(7732528, memTable.getTVListsRamCost());
Assert.assertEquals(240200, memTable.getTotalPointsNum());
Assert.assertEquals(1923360, memTable.memSize());
}
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 3f8f1811997..f76752894a0 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
@@ -258,6 +258,8 @@ public class AlignedTVListTest {
- RamUsageEstimator.sizeOfObjectArray(oldColumnCount))
+ RamUsageEstimator.sizeOfLongArray(oldColumnCount + 1)
- RamUsageEstimator.sizeOfLongArray(oldColumnCount)
+ + RamUsageEstimator.sizeOfIntArray(oldColumnCount + 1)
+ - RamUsageEstimator.sizeOfIntArray(oldColumnCount)
+ RamUsageEstimator.shallowSizeOf(new ArrayList<>())
+ RamUsageEstimator.sizeOfObjectArray(0);
tvList.extendColumn(TSDataType.INT32);
@@ -425,6 +427,47 @@ public class AlignedTVListTest {
Assert.assertEquals(retainedRamSize,
tvList.calculateRamSize().getRamSize());
}
+ // After a partial ownership transfer the new working list keeps the full
pre-clone RAM
+ // (retained columns are copied, remaining columns are moved), and its
write-cost estimator must
+ // match the actual RAM delta when a new block is written.
+ @Test
+ public void testPartialCloneKeepsWorkingListRamAndWriteCostMatchesActual() {
+ List<TSDataType> dataTypes =
+ Arrays.asList(TSDataType.INT64, TSDataType.INT32, TSDataType.DOUBLE);
+ AlignedTVList tvList = AlignedTVList.newAlignedList(dataTypes);
+
+ // Fill exactly one block so the next write crosses a block boundary.
+ for (int i = 0; i < ARRAY_SIZE; i++) {
+ tvList.putAlignedValue(i, new Object[] {(long) i, i, (double) i});
+ }
+
+ long fullRamBeforeClone = tvList.getRamSize();
+
+ AlignedTVList.PartialClonePlan plan =
tvList.preparePartialClone(Collections.singleton(0));
+ AlignedTVList workingClone = plan.getCloneList();
+ plan.commit();
+
+ // The new working list still carries all columns, so its complete RAM
must equal the
+ // pre-clone working-list RAM.
+ Assert.assertEquals(fullRamBeforeClone, workingClone.getRamSize());
+
+ // Write a new block into the working list. The incremental write estimate
must equal the
+ // actual RAM increase.
+ long ramBeforeWrite = workingClone.getRamSize();
+ long nextTime = ARRAY_SIZE;
+ Object[] rowValues = {nextTime, (int) nextTime, (double) nextTime};
+ long estimatedWriteCost =
+ workingClone.alignedWriteRowMemCost(
+ new int[] {0, 1, 2},
+ new TSDataType[] {TSDataType.INT64, TSDataType.INT32,
TSDataType.DOUBLE},
+ rowValues,
+ 0);
+ workingClone.putAlignedValue(nextTime, rowValues);
+ long ramAfterWrite = workingClone.getRamSize();
+
+ Assert.assertEquals(estimatedWriteCost, ramAfterWrite - ramBeforeWrite);
+ }
+
@Test
public void testPartialRamSizeIncludesWideColumnContainers() {
int columnCount = 256;