This is an automated email from the ASF dual-hosted git repository.
jt2594838 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new 9e909d3ce26 [Fix] Reconcile actual aligned TVList memory usage (#18330)
9e909d3ce26 is described below
commit 9e909d3ce26002507efd7a72cc53e7bf40c145bc
Author: Caideyipi <[email protected]>
AuthorDate: Wed Jul 29 10:04:39 2026 +0800
[Fix] Reconcile actual aligned TVList memory usage (#18330)
---
.../db/it/IoTDBAlignedTVListLazyAllocationIT.java | 5 +-
.../dataregion/memtable/TsFileProcessor.java | 348 ++++++++++++---------
.../db/utils/datastructure/AlignedTVList.java | 85 +++--
...ignedBitmapMemoryAccountingPerformanceTest.java | 295 +++++++++++++++++
.../dataregion/memtable/TsFileProcessorTest.java | 99 +++++-
.../db/utils/datastructure/AlignedTVListTest.java | 31 +-
6 files changed, 671 insertions(+), 192 deletions(-)
diff --git
a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBAlignedTVListLazyAllocationIT.java
b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBAlignedTVListLazyAllocationIT.java
index 375f560c9ee..314a098211c 100644
---
a/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBAlignedTVListLazyAllocationIT.java
+++
b/integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBAlignedTVListLazyAllocationIT.java
@@ -83,10 +83,7 @@ public class IoTDBAlignedTVListLazyAllocationIT {
* NEW_COLUMN_COUNT
* AlignedTVList.valueListArrayMemCost(TSDataType.INT64);
long lazyAllocationCost =
- (long) historicalBlockCount
- * NEW_COLUMN_COUNT
- * AlignedTVList.valueListArrayMemCostWithoutPrimitiveArray()
- + (long) NEW_COLUMN_COUNT *
AlignedTVList.primitiveArrayMemCost(TSDataType.INT64);
+ (long) NEW_COLUMN_COUNT *
AlignedTVList.valueListArrayMemCost(TSDataType.INT64);
long dataNodeMaxHeapSize = DATANODE_MAX_HEAP_SIZE_IN_MB * 1024L * 1024L;
Assert.assertTrue(
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 285e29b4065..ee9b39d2ffe 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
@@ -313,6 +313,10 @@ public class TsFileProcessor {
ensureMemTable(infoForMetrics);
workMemTable.checkDataType(insertRowNode);
+ AlignedTVListRamCostSnapshot alignedRamCostSnapshot =
+ insertRowNode.isAligned()
+ ? new AlignedTVListRamCostSnapshot(workMemTable,
insertRowNode.getDeviceID())
+ : null;
long[] memIncrements;
@@ -379,11 +383,15 @@ public class TsFileProcessor {
insertRowNode,
tsFileResource);
- int pointInserted;
- if (insertRowNode.isAligned()) {
- pointInserted = workMemTable.insertAlignedRow(insertRowNode);
- } else {
- pointInserted = workMemTable.insert(insertRowNode);
+ int pointInserted = 0;
+ try {
+ if (insertRowNode.isAligned()) {
+ pointInserted = workMemTable.insertAlignedRow(insertRowNode);
+ } else {
+ pointInserted = workMemTable.insert(insertRowNode);
+ }
+ } finally {
+ reconcileAlignedTVListRamCost(alignedRamCostSnapshot, memIncrements[0]);
}
// Update start time of this memtable
@@ -408,6 +416,17 @@ public class TsFileProcessor {
workMemTable.checkDataType(insertRowsNode);
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);
long memControlStartTime = System.nanoTime();
if (insertRowsNode.isMixingAlignment()) {
@@ -421,6 +440,7 @@ public class TsFileProcessor {
}
}
long[] alignedMemIncrements =
checkAlignedMemCostAndAddToTspInfoForRows(alignedList);
+ alignedMemTableIncrement = alignedMemIncrements[0];
final long[] nonAlignedMemIncrements;
try {
nonAlignedMemIncrements =
checkMemCostAndAddToTspInfoForRows(nonAlignedList);
@@ -436,6 +456,7 @@ public class TsFileProcessor {
if (insertRowsNode.isAligned()) {
memIncrements =
checkAlignedMemCostAndAddToTspInfoForRows(insertRowsNode.getInsertRowNodeList());
+ alignedMemTableIncrement = memIncrements[0];
} else {
memIncrements =
checkMemCostAndAddToTspInfoForRows(insertRowsNode.getInsertRowNodeList());
}
@@ -486,19 +507,23 @@ public class TsFileProcessor {
tsFileResource);
int pointInserted = 0;
- for (InsertRowNode insertRowNode : insertRowsNode.getInsertRowNodeList()) {
- if (insertRowNode.isAligned()) {
- pointInserted += workMemTable.insertAlignedRow(insertRowNode);
- } else {
- pointInserted += workMemTable.insert(insertRowNode);
- }
- // update start time of this memtable
- tsFileResource.updateStartTime(insertRowNode.getDeviceID(),
insertRowNode.getTime());
- // for sequence tsfile, we update the endTime only when the file is
prepared to be closed.
- // for unsequence tsfile, we have to update the endTime for each
insertion.
- if (!sequence) {
- tsFileResource.updateEndTime(insertRowNode.getDeviceID(),
insertRowNode.getTime());
+ try {
+ for (InsertRowNode insertRowNode :
insertRowsNode.getInsertRowNodeList()) {
+ if (insertRowNode.isAligned()) {
+ pointInserted += workMemTable.insertAlignedRow(insertRowNode);
+ } else {
+ pointInserted += workMemTable.insert(insertRowNode);
+ }
+ // update start time of this memtable
+ tsFileResource.updateStartTime(insertRowNode.getDeviceID(),
insertRowNode.getTime());
+ // for sequence tsfile, we update the endTime only when the file is
prepared to be closed.
+ // for unsequence tsfile, we have to update the endTime for each
insertion.
+ if (!sequence) {
+ tsFileResource.updateEndTime(insertRowNode.getDeviceID(),
insertRowNode.getTime());
+ }
}
+ } finally {
+ reconcileAlignedTVListRamCost(alignedRamCostSnapshot,
alignedMemTableIncrement);
}
tsFileResource.updateProgressIndex(insertRowsNode.getProgressIndex());
@@ -623,6 +648,19 @@ public class TsFileProcessor {
ensureMemTable(infoForMetrics);
workMemTable.checkDataType(insertTabletNode);
+ Set<IDeviceID> alignedDeviceIds = new HashSet<>();
+ if (insertTabletNode.isAligned()) {
+ for (int[] range : rangeList) {
+ for (Pair<IDeviceID, Integer> deviceEndPosition :
+ insertTabletNode.splitByDevice(range[0], range[1])) {
+ alignedDeviceIds.add(deviceEndPosition.getLeft());
+ }
+ }
+ }
+ AlignedTVListRamCostSnapshot alignedRamCostSnapshot =
+ alignedDeviceIds.isEmpty()
+ ? null
+ : new AlignedTVListRamCostSnapshot(workMemTable, alignedDeviceIds);
long[] memIncrements =
scheduleMemoryBlock(insertTabletNode, rangeList, results,
infoForMetrics);
@@ -670,59 +708,63 @@ public class TsFileProcessor {
tsFileResource);
int pointInserted = 0;
- for (int rangeIndex = 0; rangeIndex < rangeList.size(); rangeIndex++) {
- final int[] rangePair = rangeList.get(rangeIndex);
- int start = rangePair[0];
- int end = rangePair[1];
- try {
- if (insertTabletNode.isAligned()) {
- pointInserted +=
- workMemTable.insertAlignedTablet(
- insertTabletNode, start, end, noFailure ? null : results);
- } else {
- pointInserted += workMemTable.insertTablet(insertTabletNode, start,
end);
- }
- } catch (final WriteProcessException e) {
- final TSStatus failureStatus = RpcUtils.getStatus(e.getErrorCode(),
e.getMessage());
- for (int failedRangeIndex = rangeIndex;
- failedRangeIndex < rangeList.size();
- failedRangeIndex++) {
- final int[] failedRange = rangeList.get(failedRangeIndex);
- for (int i = failedRange[0]; i < failedRange[1]; i++) {
- results[i] = failureStatus;
+ try {
+ for (int rangeIndex = 0; rangeIndex < rangeList.size(); rangeIndex++) {
+ final int[] rangePair = rangeList.get(rangeIndex);
+ int start = rangePair[0];
+ int end = rangePair[1];
+ try {
+ if (insertTabletNode.isAligned()) {
+ pointInserted +=
+ workMemTable.insertAlignedTablet(
+ insertTabletNode, start, end, noFailure ? null : results);
+ } else {
+ pointInserted += workMemTable.insertTablet(insertTabletNode,
start, end);
+ }
+ } catch (final WriteProcessException e) {
+ final TSStatus failureStatus = RpcUtils.getStatus(e.getErrorCode(),
e.getMessage());
+ for (int failedRangeIndex = rangeIndex;
+ failedRangeIndex < rangeList.size();
+ failedRangeIndex++) {
+ final int[] failedRange = rangeList.get(failedRangeIndex);
+ for (int i = failedRange[0]; i < failedRange[1]; i++) {
+ results[i] = failureStatus;
+ }
}
+ throw e;
}
- throw e;
- }
- for (int i = start; i < end; i++) {
- if (results[i] == null
- || results[i].getCode() ==
TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
- results[i] = RpcUtils.SUCCESS_STATUS;
+ for (int i = start; i < end; i++) {
+ if (results[i] == null
+ || results[i].getCode() ==
TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ results[i] = RpcUtils.SUCCESS_STATUS;
+ }
}
- }
- final List<Pair<IDeviceID, Integer>> deviceEndOffsetPairs =
- insertTabletNode.splitByDevice(start, end);
- tsFileResource.updateStartTime(
- deviceEndOffsetPairs.get(0).left,
insertTabletNode.getTimes()[start]);
- if (!sequence) {
- // For sequence tsfile, we update the endTime only when the file is
prepared to be closed.
- // For unsequence tsfile, we have to update the endTime for each
insertion.
- tsFileResource.updateEndTime(
- deviceEndOffsetPairs.get(0).left,
- insertTabletNode.getTimes()[deviceEndOffsetPairs.get(0).right -
1]);
- }
- for (int i = 1; i < deviceEndOffsetPairs.size(); i++) {
- // the end offset of i - 1 is the start offset of i
+ final List<Pair<IDeviceID, Integer>> deviceEndOffsetPairs =
+ insertTabletNode.splitByDevice(start, end);
tsFileResource.updateStartTime(
- deviceEndOffsetPairs.get(i).left,
- insertTabletNode.getTimes()[deviceEndOffsetPairs.get(i -
1).right]);
+ deviceEndOffsetPairs.get(0).left,
insertTabletNode.getTimes()[start]);
if (!sequence) {
+ // For sequence tsfile, we update the endTime only when the file is
prepared to be closed.
+ // For unsequence tsfile, we have to update the endTime for each
insertion.
tsFileResource.updateEndTime(
+ deviceEndOffsetPairs.get(0).left,
+ insertTabletNode.getTimes()[deviceEndOffsetPairs.get(0).right -
1]);
+ }
+ for (int i = 1; i < deviceEndOffsetPairs.size(); i++) {
+ // the end offset of i - 1 is the start offset of i
+ tsFileResource.updateStartTime(
deviceEndOffsetPairs.get(i).left,
- insertTabletNode.getTimes()[deviceEndOffsetPairs.get(i).right -
1]);
+ insertTabletNode.getTimes()[deviceEndOffsetPairs.get(i -
1).right]);
+ if (!sequence) {
+ tsFileResource.updateEndTime(
+ deviceEndOffsetPairs.get(i).left,
+ insertTabletNode.getTimes()[deviceEndOffsetPairs.get(i).right
- 1]);
+ }
}
}
+ } finally {
+ reconcileAlignedTVListRamCost(alignedRamCostSnapshot, memIncrements[0]);
}
tsFileResource.updateProgressIndex(insertTabletNode.getProgressIndex());
@@ -853,52 +895,33 @@ public class TsFileProcessor {
ChunkMetadata.calculateRamSize(AlignedPath.VECTOR_PLACEHOLDER,
TSDataType.VECTOR)
* writableFieldDataTypes.length;
// The first row creates the first aligned TVList block. All writable
fields have a non-null
- // value, so this includes the timestamp array, value primitive arrays,
bitmap reservations,
- // array headers, and ArrayList references for that block.
+ // value, so this includes the timestamp array, value primitive arrays,
array headers, and
+ // ArrayList references for that block.
memTableIncrement +=
AlignedTVList.alignedTvListArrayMemCost(writableFieldDataTypes, null);
} else {
// For existed device of this mem table
AlignedWritableMemChunk alignedMemChunk = (AlignedWritableMemChunk)
memChunk;
int currentPointNum = alignedMemChunk.alignedListSize();
- int currentArrayNum =
- currentPointNum / PrimitiveArrayManager.ARRAY_SIZE
- + (currentPointNum % PrimitiveArrayManager.ARRAY_SIZE > 0 ? 1 :
0);
int targetArrayIndex = currentPointNum /
PrimitiveArrayManager.ARRAY_SIZE;
- int newColumnCount = 0;
for (int i = 0; dataTypes != null && i < dataTypes.length; i++) {
// Skip failed Measurements
if (!isWritableFieldMeasurement(measurements, dataTypes, values,
columnCategories, i)) {
continue;
}
- if (!alignedMemChunk.containsMeasurement(measurements[i])) {
- // Extending a column adds one null value-list placeholder and one
conservatively reserved
- // bitmap to every historical block. No value primitive array is
allocated there.
- memTableIncrement +=
- currentArrayNum *
AlignedTVList.valueListArrayMemCostWithoutPrimitiveArray();
- newColumnCount++;
- }
if (!isValueArrayMaterialized(alignedMemChunk, measurements[i],
targetArrayIndex)) {
// The non-null value materializes this column's primitive array in
the target block.
- // This cost contains the primitive-array payload and its array
header.
- memTableIncrement +=
AlignedTVList.primitiveArrayMemCost(dataTypes[i]);
+ // Null historical placeholders are not charged.
+ memTableIncrement +=
AlignedTVList.valueListArrayMemCost(dataTypes[i]);
}
}
if ((currentPointNum % PrimitiveArrayManager.ARRAY_SIZE) == 0) {
- // Starting a block allocates its timestamp array, optional sort-index
array, value-list
- // null
- // placeholders, bitmap reservations, array headers, and ArrayList
references for all
- // existing columns. Value primitive arrays are excluded because they
were charged above
- // only for written columns.
+ // Starting a block allocates its timestamp array and optional
sort-index array. Value
+ // arrays and their references are charged above only for written
columns.
memTableIncrement +=
alignedMemChunk.getWorkingTVList().alignedTvListArrayMemCostWithoutPrimitiveArrays();
- // The working TVList does not contain newly extended columns yet, so
add their value-list
- // placeholder and bitmap reservation for this new block separately.
- memTableIncrement +=
- (long) newColumnCount *
AlignedTVList.valueListArrayMemCostWithoutPrimitiveArray();
}
}
-
for (int i = 0; dataTypes != null && i < dataTypes.length; i++) {
if (isWritableFieldMeasurement(measurements, dataTypes, values,
columnCategories, i)
&& dataTypes[i].isBinary()) {
@@ -942,7 +965,7 @@ public class TsFileProcessor {
ChunkMetadata.calculateRamSize(AlignedPath.VECTOR_PLACEHOLDER,
TSDataType.VECTOR)
* writableFieldDataTypes.length;
// The first row creates the first complete aligned TVList block:
timestamp array, value
- // primitive arrays for the writable fields, bitmap reservations,
headers, and references.
+ // primitive arrays for the writable fields, headers, and references.
memTableIncrement +=
AlignedTVList.alignedTvListArrayMemCost(writableFieldDataTypes, null);
for (int i = 0; dataTypes != null && i < dataTypes.length; i++) {
// Skip failed Measurements
@@ -980,13 +1003,6 @@ public class TsFileProcessor {
if (!currentMemChunkContainsMeasurement
&& !addingPointNumInfo.left.containsKey(measurements[i])) {
addingPointNumInfo.left.put(measurements[i], dataTypes[i]);
- int currentArrayNum =
- pointNumBeforeCurrentRow / PrimitiveArrayManager.ARRAY_SIZE
- + (pointNumBeforeCurrentRow %
PrimitiveArrayManager.ARRAY_SIZE > 0 ? 1 : 0);
- // A column first seen in this batch adds a null value-list
placeholder and a bitmap
- // reservation to every block that already exists before the
current row.
- memTableIncrement +=
- currentArrayNum *
AlignedTVList.valueListArrayMemCostWithoutPrimitiveArray();
}
Set<Integer> materializedArrayIndexes =
materializedArraysInCurrentBatch
@@ -994,32 +1010,25 @@ public class TsFileProcessor {
.computeIfAbsent(measurements[i], key -> new HashSet<>());
if (!isValueArrayMaterialized(alignedMemChunk, measurements[i],
targetArrayIndex)
&& materializedArrayIndexes.add(targetArrayIndex)) {
- // Charge the payload and header of a value primitive array
exactly once when this
- // batch first writes a non-null value to the column in the target
block.
- memTableIncrement +=
AlignedTVList.primitiveArrayMemCost(dataTypes[i]);
+ // Charge a value array and its reference exactly once when this
batch first writes a
+ // non-null value to the column in the target block.
+ memTableIncrement +=
AlignedTVList.valueListArrayMemCost(dataTypes[i]);
}
}
if ((pointNumBeforeCurrentRow % PrimitiveArrayManager.ARRAY_SIZE) ==
0) {
if (alignedMemChunk == null) {
- // A device created earlier in this batch starts another block.
Charge the timestamp
- // array, value-list placeholders, bitmap reservations, headers,
and references for all
- // columns discovered in the batch, excluding their value
primitive arrays.
+ // A device created earlier in this batch starts another timestamp
block. Value arrays
+ // and their references are charged only when materialized.
memTableIncrement +=
AlignedTVList.alignedTvListArrayMemCostWithoutPrimitiveArrays(
addingPointNumInfo.left.values().toArray(new
TSDataType[0]), null);
} else {
- // An existing aligned TVList starts another block. Charge its
timestamp array, optional
- // sort-index array, value-list placeholders, bitmap reservations,
headers, and
- // references for columns that were already present before this
batch.
+ // An existing aligned TVList starts another timestamp block.
Value arrays and their
+ // references are charged only when materialized.
memTableIncrement +=
alignedMemChunk
.getWorkingTVList()
.alignedTvListArrayMemCostWithoutPrimitiveArrays();
- // Columns first introduced by this batch are absent from the
working TVList's type
- // list, so add one value-list placeholder and bitmap reservation
for each of them.
- memTableIncrement +=
- (long) addingPointNumInfo.left.size()
- *
AlignedTVList.valueListArrayMemCostWithoutPrimitiveArray();
}
}
addingPointNumInfo.setRight(addingPointNum + 1);
@@ -1172,9 +1181,8 @@ public class TsFileProcessor {
int numArraysToAdd =
incomingPointNum / PrimitiveArrayManager.ARRAY_SIZE
+ (incomingPointNum % PrimitiveArrayManager.ARRAY_SIZE > 0 ? 1 :
0);
- // Each new block allocates its timestamp array, value-list null
placeholders, bitmap
- // reservations, array headers, and ArrayList references. Value
primitive arrays are excluded
- // here because all-null and failed-only column segments leave them
unmaterialized.
+ // Each new block allocates its timestamp array. Value arrays and their
references are
+ // excluded here because all-null and failed-only column segments leave
them unmaterialized.
memIncrements[0] +=
numArraysToAdd
* AlignedTVList.alignedTvListArrayMemCostWithoutPrimitiveArrays(
@@ -1182,7 +1190,7 @@ public class TsFileProcessor {
// Add the payload and header of a value primitive array only for a
column/block segment that
// contains at least one successful non-null value.
memIncrements[0] +=
- calculateTabletPrimitiveArrayMemCost(
+ calculateTabletValueArrayMemCost(
null,
measurementIds,
dataTypes,
@@ -1195,28 +1203,11 @@ public class TsFileProcessor {
0);
} else {
AlignedWritableMemChunk alignedMemChunk = (AlignedWritableMemChunk)
memChunk;
- List<TSDataType> newDataTypes = new ArrayList<>();
int currentPointNum = alignedMemChunk.alignedListSize();
int newPointNum = currentPointNum + incomingPointNum;
int currentArrayCnt =
currentPointNum / PrimitiveArrayManager.ARRAY_SIZE
+ (currentPointNum % PrimitiveArrayManager.ARRAY_SIZE > 0 ? 1 :
0);
- for (int i = 0; dataTypes != null && i < dataTypes.length; i++) {
- TSDataType dataType = dataTypes[i];
- if (!isWritableFieldMeasurement(measurementIds, dataTypes, columns,
columnCategories, i)) {
- continue;
- }
-
- if (!alignedMemChunk.containsMeasurement(measurementIds[i])) {
- // A new column adds one null value-list placeholder and one
conservatively reserved
- // bitmap to every historical block. It does not materialize value
primitive arrays in
- // those blocks.
- memIncrements[0] +=
- currentArrayCnt *
AlignedTVList.valueListArrayMemCostWithoutPrimitiveArray();
- newDataTypes.add(dataType);
- }
- }
-
// calculate how many new arrays will be added after this insertion
int newArrayCnt =
newPointNum / PrimitiveArrayManager.ARRAY_SIZE
@@ -1224,26 +1215,18 @@ public class TsFileProcessor {
long acquireArray = newArrayCnt - currentArrayCnt;
if (acquireArray != 0) {
- // Each acquired block adds a timestamp array, optional sort-index
array, value-list null
- // placeholders, bitmap reservations, array headers, and ArrayList
references for columns
- // already present in the working TVList. Value primitive arrays are
charged separately
- // below.
+ // Each acquired block adds a timestamp array and optional sort-index
array. Value arrays
+ // and their references are charged separately below.
memIncrements[0] +=
acquireArray
* alignedMemChunk
.getWorkingTVList()
.alignedTvListArrayMemCostWithoutPrimitiveArrays();
- for (TSDataType ignored : newDataTypes) {
- // Newly extended columns are not included in the working TVList's
per-block cost yet, so
- // add their value-list placeholder and bitmap reservation to every
acquired block.
- memIncrements[0] +=
- acquireArray *
AlignedTVList.valueListArrayMemCostWithoutPrimitiveArray();
- }
}
// Charge the payload and header of each value primitive array that this
tablet actually
// materializes. Arrays already present in the working TVList are not
charged again.
memIncrements[0] +=
- calculateTabletPrimitiveArrayMemCost(
+ calculateTabletValueArrayMemCost(
alignedMemChunk,
measurementIds,
dataTypes,
@@ -1255,7 +1238,6 @@ public class TsFileProcessor {
end,
currentPointNum);
}
-
// flexible-length data size
for (int i = 0; dataTypes != null && i < dataTypes.length; i++) {
TSDataType dataType = dataTypes[i];
@@ -1273,13 +1255,12 @@ public class TsFileProcessor {
}
/**
- * Calculates only the value primitive arrays that an aligned tablet will
materialize. The
- * per-block timestamp arrays, value-list placeholders, bitmap reservations,
headers, and
- * references are charged by the caller. A column/block pair is charged only
when the tablet has
- * at least one successful non-null value in that block and the working
TVList has not already
- * allocated its value array.
+ * Calculates only the value arrays and list references that an aligned
tablet will materialize.
+ * The per-block timestamp arrays are charged by the caller. A column/block
pair is charged only
+ * when the tablet has at least one successful non-null value in that block
and the working TVList
+ * has not already allocated its value array.
*/
- private static long calculateTabletPrimitiveArrayMemCost(
+ private static long calculateTabletValueArrayMemCost(
AlignedWritableMemChunk alignedMemChunk,
String[] measurementIds,
TSDataType[] dataTypes,
@@ -1308,9 +1289,9 @@ public class TsFileProcessor {
if (containsNonNullValue(bitMap, results, inputIndex, length)
&& !isValueArrayMaterialized(
alignedMemChunk, measurementIds[column], targetArrayIndex)) {
- // One successful non-null write materializes the whole fixed-size
value array, whose
- // memory consists of the primitive payload and the array header.
- size += AlignedTVList.primitiveArrayMemCost(dataTypes[column]);
+ // One successful non-null write materializes the fixed-size value
array and its list
+ // reference.
+ size += AlignedTVList.valueListArrayMemCost(dataTypes[column]);
}
inputIndex += length;
targetArrayIndex++;
@@ -1395,6 +1376,75 @@ public class TsFileProcessor {
&& columnCategories[index] == TsTableColumnCategory.FIELD);
}
+ private void reconcileAlignedTVListRamCost(
+ AlignedTVListRamCostSnapshot snapshot, long estimatedMemTableIncrement) {
+ if (snapshot == null) {
+ return;
+ }
+
+ long correction = snapshot.getMemoryCorrection(estimatedMemTableIncrement);
+ if (correction > 0) {
+ dataRegionInfo.addStorageGroupMemCost(correction);
+ snapshot.memTable.addTVListRamCost(correction);
+ } else if (correction < 0) {
+ long releasedMemory = -correction;
+ dataRegionInfo.releaseStorageGroupMemCost(releasedMemory);
+ snapshot.memTable.releaseTVListRamCost(releasedMemory);
+ SystemInfo.getInstance().resetStorageGroupStatus(dataRegionInfo);
+ }
+ }
+
+ static final class AlignedTVListRamCostSnapshot {
+
+ private final IMemTable memTable;
+ private final IDeviceID deviceId;
+ private final Set<IDeviceID> deviceIds;
+ private final long ramCostBeforeWrite;
+
+ AlignedTVListRamCostSnapshot(IMemTable memTable, IDeviceID deviceId) {
+ this.memTable = memTable;
+ this.deviceId = deviceId;
+ this.deviceIds = null;
+ this.ramCostBeforeWrite = getRamCost(memTable, deviceId);
+ }
+
+ AlignedTVListRamCostSnapshot(IMemTable memTable, Set<IDeviceID> deviceIds)
{
+ this.memTable = memTable;
+ this.deviceId = null;
+ this.deviceIds = deviceIds;
+ this.ramCostBeforeWrite = getRamCost(memTable, deviceIds);
+ }
+
+ long getMemoryCorrection(long estimatedMemTableIncrement) {
+ return (deviceId == null ? getRamCost(memTable, deviceIds) :
getRamCost(memTable, deviceId))
+ - ramCostBeforeWrite
+ - estimatedMemTableIncrement;
+ }
+
+ private static long getRamCost(IMemTable memTable, Set<IDeviceID>
deviceIds) {
+ long ramCost = 0;
+ for (IDeviceID currentDeviceId : deviceIds) {
+ ramCost += getRamCost(memTable, currentDeviceId);
+ }
+ return ramCost;
+ }
+
+ private static long getRamCost(IMemTable memTable, IDeviceID deviceId) {
+ IWritableMemChunk memChunk =
+ memTable.getWritableMemChunk(deviceId,
AlignedPath.VECTOR_PLACEHOLDER);
+ if (!(memChunk instanceof AlignedWritableMemChunk)) {
+ return 0;
+ }
+
+ AlignedWritableMemChunk alignedMemChunk = (AlignedWritableMemChunk)
memChunk;
+ long ramCost = alignedMemChunk.getWorkingTVList().getRamSize();
+ for (AlignedTVList sortedTVList : alignedMemChunk.getSortedList()) {
+ ramCost += sortedTVList.getRamSize();
+ }
+ return ramCost;
+ }
+ }
+
private void updateMemoryInfo(
long memTableIncrement, long chunkMetadataIncrement, long
textDataIncrement)
throws WriteProcessRejectException {
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 644890d0bed..a589e1ea97a 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
@@ -72,8 +72,8 @@ import static
org.apache.tsfile.utils.RamUsageEstimator.NUM_BYTES_OBJECT_REF;
public abstract class AlignedTVList extends TVList {
- private static final long BITMAP_RAM_COST_PER_BLOCK =
- BitMap.createBitMapDynamically(ARRAY_SIZE).ramBytesUsed() +
NUM_BYTES_OBJECT_REF;
+ private static final long BITMAP_RAM_COST =
+ BitMap.createBitMapDynamically(ARRAY_SIZE).ramBytesUsed();
// Data types of this aligned tvList
protected List<TSDataType> dataTypes;
@@ -84,6 +84,8 @@ public abstract class AlignedTVList extends TVList {
// Number and memory cost of primitive arrays that have been allocated for
each value column
private int[] materializedValueArrayCounts;
private long materializedValueArrayMemCost;
+ private long materializedBitmapMemoryCost;
+ private long arrayMemCostWithoutPrimitiveArraysAndIndex;
// Data type list -> list of TVList, add 1 when expanded -> primitive array
of basic type
// Index relation: columnIndex(dataTypeIndex) -> arrayIndex -> elementIndex
@@ -108,6 +110,7 @@ public abstract class AlignedTVList extends TVList {
dataTypes = types;
memoryBinaryChunkSize = new long[dataTypes.size()];
materializedValueArrayCounts = new int[dataTypes.size()];
+ refreshArrayMemCostWithoutPrimitiveArrays();
values = new ArrayList<>(types.size());
for (int i = 0; i < types.size(); i++) {
@@ -174,13 +177,14 @@ public abstract class AlignedTVList extends TVList {
alignedTvList.allValueColDeletedMap = ignoreAllNullRows ?
getAllValueColDeletedMap() : null;
alignedTvList.timeColDeletedMap = this.timeColDeletedMap;
alignedTvList.timeDeletedCnt = this.timeDeletedCnt;
+ alignedTvList.materializedBitmapMemoryCost =
calculateBitmapRamCost(bitMaps);
for (int i = 0; i < columnIndexList.size(); i++) {
int columnIndex = columnIndexList.get(i);
if (columnIndex != -1 && values.get(i) != null) {
int materializedArrayCount = materializedValueArrayCounts[columnIndex];
alignedTvList.materializedValueArrayCounts[i] = materializedArrayCount;
alignedTvList.materializedValueArrayMemCost +=
- (long) materializedArrayCount *
primitiveArrayMemCost(dataTypeList.get(i));
+ (long) materializedArrayCount *
valueListArrayMemCost(dataTypeList.get(i));
}
}
@@ -199,6 +203,7 @@ public abstract class AlignedTVList extends TVList {
cloneList.materializedValueArrayCounts =
Arrays.copyOf(materializedValueArrayCounts,
materializedValueArrayCounts.length);
cloneList.materializedValueArrayMemCost = materializedValueArrayMemCost;
+ cloneList.materializedBitmapMemoryCost = materializedBitmapMemoryCost;
return cloneList;
}
@@ -237,6 +242,7 @@ public abstract class AlignedTVList extends TVList {
cloneList.materializedValueArrayCounts =
Arrays.copyOf(materializedValueArrayCounts,
materializedValueArrayCounts.length);
cloneList.materializedValueArrayMemCost = materializedValueArrayMemCost;
+ cloneList.materializedBitmapMemoryCost = materializedBitmapMemoryCost;
return cloneList;
}
@@ -428,9 +434,12 @@ public abstract class AlignedTVList extends TVList {
}
columnBitMaps.add(bitMap);
}
+ materializedBitmapMemoryCost +=
+ (long) timestamps.size() * (bitmapReferenceRamCost() +
bitmapRamCost());
this.bitMaps.add(columnBitMaps);
this.values.add(columnValue);
this.dataTypes.add(dataType);
+ refreshArrayMemCostWithoutPrimitiveArrays();
long[] tmpValueChunkRawSize = memoryBinaryChunkSize;
memoryBinaryChunkSize = new long[dataTypes.size()];
@@ -726,10 +735,13 @@ public abstract class AlignedTVList extends TVList {
columnBitMaps.add(BitMap.createBitMapDynamically(ARRAY_SIZE));
}
bitMaps.set(columnIndex, columnBitMaps);
+ materializedBitmapMemoryCost +=
+ (long) columnBitMaps.size() * (bitmapReferenceRamCost() +
bitmapRamCost());
}
for (int i = 0; i < bitMaps.get(columnIndex).size(); i++) {
if (bitMaps.get(columnIndex).get(i) == null) {
bitMaps.get(columnIndex).set(i,
BitMap.createBitMapDynamically(ARRAY_SIZE));
+ materializedBitmapMemoryCost += bitmapRamCost();
}
bitMaps.get(columnIndex).get(i).markAll();
}
@@ -808,6 +820,7 @@ public abstract class AlignedTVList extends TVList {
}
}
}
+ materializedBitmapMemoryCost = 0;
}
@Override
@@ -819,6 +832,7 @@ public abstract class AlignedTVList extends TVList {
values.get(i).add(null);
if (bitMaps != null && bitMaps.get(i) != null) {
bitMaps.get(i).add(null);
+ materializedBitmapMemoryCost += bitmapReferenceRamCost();
}
}
}
@@ -1089,7 +1103,7 @@ public abstract class AlignedTVList extends TVList {
valueArray = getPrimitiveArraysByType(dataTypes.get(columnIndex));
columnValues.set(arrayIndex, valueArray);
materializedValueArrayCounts[columnIndex]++;
- materializedValueArrayMemCost +=
primitiveArrayMemCost(dataTypes.get(columnIndex));
+ materializedValueArrayMemCost +=
valueListArrayMemCost(dataTypes.get(columnIndex));
}
return valueArray;
}
@@ -1111,11 +1125,13 @@ public abstract class AlignedTVList extends TVList {
columnBitMaps.add(null);
}
bitMaps.set(columnIndex, columnBitMaps);
+ materializedBitmapMemoryCost += (long) columnBitMaps.size() *
bitmapReferenceRamCost();
}
// if the bitmap in arrayIndex is null, init the bitmap
if (bitMaps.get(columnIndex).get(arrayIndex) == null) {
bitMaps.get(columnIndex).set(arrayIndex,
BitMap.createBitMapDynamically(ARRAY_SIZE));
+ materializedBitmapMemoryCost += bitmapRamCost();
}
return bitMaps.get(columnIndex).get(arrayIndex);
@@ -1142,12 +1158,36 @@ public abstract class AlignedTVList extends TVList {
return new RamInfo(
timestamps.size(),
alignedTvListArrayMemCost(),
- (long) timestamps.size() *
alignedTvListArrayMemCostWithoutPrimitiveArrays()
- + materializedValueArrayMemCost,
+ getRamSize(),
rowCount,
new ArrayList<>(dataTypes));
}
+ public synchronized long getRamSize() {
+ return (long) timestamps.size() *
alignedTvListArrayMemCostWithoutPrimitiveArrays()
+ + materializedValueArrayMemCost
+ + materializedBitmapMemoryCost;
+ }
+
+ private static long calculateBitmapRamCost(List<List<BitMap>> bitMaps) {
+ if (bitMaps == null) {
+ return 0;
+ }
+ long size = 0;
+ for (List<BitMap> columnBitMaps : bitMaps) {
+ if (columnBitMaps == null) {
+ continue;
+ }
+ size += (long) columnBitMaps.size() * bitmapReferenceRamCost();
+ for (BitMap bitMap : columnBitMaps) {
+ if (bitMap != null) {
+ size += bitMap.ramBytesUsed();
+ }
+ }
+ }
+ return size;
+ }
+
/**
* Get the single alignedTVList array mem cost by give types.
*
@@ -1165,7 +1205,6 @@ public abstract class AlignedTVList extends TVList {
if (type != null
&& (columnCategories == null || columnCategories[i] ==
TsTableColumnCategory.FIELD)) {
size += (long) ARRAY_SIZE * (long) type.getDataTypeSize();
- size += BITMAP_RAM_COST_PER_BLOCK;
measurementColumnNum++;
}
}
@@ -1194,7 +1233,6 @@ public abstract class AlignedTVList extends TVList {
TSDataType type = dataTypes.get(column);
if (type != null) {
size += (long) PrimitiveArrayManager.ARRAY_SIZE * (long)
type.getDataTypeSize();
- size += BITMAP_RAM_COST_PER_BLOCK;
}
}
// size is 0 when all types are null
@@ -1213,13 +1251,21 @@ public abstract class AlignedTVList extends TVList {
}
public long alignedTvListArrayMemCostWithoutPrimitiveArrays() {
+ return arrayMemCostWithoutPrimitiveArraysAndIndex
+ + (indices != null ? (long) PrimitiveArrayManager.ARRAY_SIZE *
Integer.BYTES : 0);
+ }
+
+ private void refreshArrayMemCostWithoutPrimitiveArrays() {
long size = alignedTvListArrayMemCost();
+ if (indices != null) {
+ size -= (long) PrimitiveArrayManager.ARRAY_SIZE * Integer.BYTES;
+ }
for (TSDataType dataType : dataTypes) {
if (dataType != null) {
- size -= primitiveArrayMemCost(dataType);
+ size -= valueListArrayMemCost(dataType);
}
}
- return size;
+ arrayMemCostWithoutPrimitiveArraysAndIndex = size;
}
public static long alignedTvListArrayMemCostWithoutPrimitiveArrays(
@@ -1229,7 +1275,7 @@ public abstract class AlignedTVList extends TVList {
TSDataType dataType = types[i];
if (dataType != null
&& (columnCategories == null || columnCategories[i] ==
TsTableColumnCategory.FIELD)) {
- size -= primitiveArrayMemCost(dataType);
+ size -= valueListArrayMemCost(dataType);
}
}
return size;
@@ -1242,7 +1288,9 @@ public abstract class AlignedTVList extends TVList {
* @return valueListArrayMemCost
*/
public static long valueListArrayMemCost(TSDataType type) {
- return primitiveArrayMemCost(type) +
valueListArrayMemCostWithoutPrimitiveArray();
+ // Charge the reference only after the value array is materialized. Null
historical
+ // placeholders do not contribute to write-memory accounting.
+ return primitiveArrayMemCost(type) + NUM_BYTES_OBJECT_REF;
}
public static long primitiveArrayMemCost(TSDataType type) {
@@ -1251,13 +1299,12 @@ public abstract class AlignedTVList extends TVList {
+ NUM_BYTES_ARRAY_HEADER;
}
- public static long valueListArrayMemCostWithoutPrimitiveArray() {
- long size = 0;
- // bitmap object, byte array, and reference in the bitmap list
- size += BITMAP_RAM_COST_PER_BLOCK;
- // null placeholder reference in the value ArrayList
- size += NUM_BYTES_OBJECT_REF;
- return size;
+ public static long bitmapRamCost() {
+ return BITMAP_RAM_COST;
+ }
+
+ public static long bitmapReferenceRamCost() {
+ return NUM_BYTES_OBJECT_REF;
}
/** Build TsBlock by column. */
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedBitmapMemoryAccountingPerformanceTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedBitmapMemoryAccountingPerformanceTest.java
new file mode 100644
index 00000000000..8a872f6af61
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/memtable/AlignedBitmapMemoryAccountingPerformanceTest.java
@@ -0,0 +1,295 @@
+/*
+ * 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.db.utils.ManualPerformanceTestUtils;
+import org.apache.iotdb.db.utils.ManualPerformanceTestUtils.Measurement;
+import org.apache.iotdb.db.utils.ManualPerformanceTestUtils.Summary;
+
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.file.metadata.IDeviceID;
+import org.apache.tsfile.utils.BitMap;
+import org.apache.tsfile.write.schema.IMeasurementSchema;
+import org.apache.tsfile.write.schema.MeasurementSchema;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+public class AlignedBitmapMemoryAccountingPerformanceTest {
+
+ private static final String ENABLED_PROPERTY =
"iotdb.aligned.bitmap.accounting.perf.enabled";
+ private static final String COLUMNS_PROPERTY =
"iotdb.aligned.bitmap.accounting.perf.columns";
+ private static final String ROWS_PROPERTY =
"iotdb.aligned.bitmap.accounting.perf.rows";
+ private static final String WARMUP_ITERATIONS_PROPERTY =
+ "iotdb.aligned.bitmap.accounting.perf.warmup.iterations";
+ private static final String ITERATIONS_PROPERTY =
+ "iotdb.aligned.bitmap.accounting.perf.iterations";
+ private static final String ROUNDS_PROPERTY =
"iotdb.aligned.bitmap.accounting.perf.rounds";
+ private static final int RECONCILIATION_REPETITIONS = 1024;
+
+ private static volatile long benchmarkBlackhole;
+
+ @Test
+ public void alignedTabletBitmapAccountingBenchmark() {
+ Assume.assumeTrue(
+ String.format(
+ "Manual performance UT. Enable with -D%s=true, optionally tune
-D%s, -D%s, -D%s, -D%s and -D%s.",
+ ENABLED_PROPERTY,
+ COLUMNS_PROPERTY,
+ ROWS_PROPERTY,
+ WARMUP_ITERATIONS_PROPERTY,
+ ITERATIONS_PROPERTY,
+ ROUNDS_PROPERTY),
+ Boolean.getBoolean(ENABLED_PROPERTY));
+ Assume.assumeTrue(
+ "Current-thread CPU time and allocation metrics are required.",
+ ManualPerformanceTestUtils.enableThreadMetrics());
+
+ int columnCount = Integer.getInteger(COLUMNS_PROPERTY, 64);
+ int rowCount = Integer.getInteger(ROWS_PROPERTY, 256);
+ int warmupIterations = Integer.getInteger(WARMUP_ITERATIONS_PROPERTY, 200);
+ int iterations = Integer.getInteger(ITERATIONS_PROPERTY, 2000);
+ int rounds = Integer.getInteger(ROUNDS_PROPERTY, 5);
+ Assert.assertTrue(columnCount > 0);
+ Assert.assertTrue(rowCount > 0);
+ Assert.assertTrue(warmupIterations > 0);
+ Assert.assertTrue(iterations > 0);
+ Assert.assertTrue(rounds > 0);
+
+ runScenario(
+ "dense",
+ createScenario(columnCount, rowCount, false),
+ warmupIterations,
+ iterations,
+ rounds);
+ runScenario(
+ "null-heavy",
+ createScenario(columnCount, rowCount, true),
+ warmupIterations,
+ iterations,
+ rounds);
+ }
+
+ private static void runScenario(
+ String label, Scenario scenario, int warmupIterations, int iterations,
int rounds) {
+ runReconciliation(
+ createAccountingTarget(scenario), warmupIterations *
RECONCILIATION_REPETITIONS);
+ runWrite(scenario, createMemChunks(scenario, warmupIterations));
+
+ Measurement[] reconciliationMeasurements = new Measurement[rounds];
+ Measurement[] writeMeasurements = new Measurement[rounds];
+ for (int i = 0; i < rounds; i++) {
+ if ((i & 1) == 0) {
+ reconciliationMeasurements[i] = measureReconciliation(scenario,
iterations);
+ writeMeasurements[i] = measureWrite(scenario, iterations);
+ } else {
+ writeMeasurements[i] = measureWrite(scenario, iterations);
+ reconciliationMeasurements[i] = measureReconciliation(scenario,
iterations);
+ }
+ }
+
+ Summary reconciliationSummary =
+ ManualPerformanceTestUtils.summarize(
+ reconciliationMeasurements, iterations *
RECONCILIATION_REPETITIONS);
+ Summary writeSummary =
ManualPerformanceTestUtils.summarize(writeMeasurements, iterations);
+ printResult(
+ label, scenario, warmupIterations, iterations, rounds,
reconciliationSummary, writeSummary);
+ }
+
+ private static Measurement measureReconciliation(Scenario scenario, int
iterations) {
+ AccountingTarget target = createAccountingTarget(scenario);
+ return ManualPerformanceTestUtils.measure(
+ 1, () -> runReconciliation(target, iterations *
RECONCILIATION_REPETITIONS));
+ }
+
+ private static Measurement measureWrite(Scenario scenario, int iterations) {
+ AlignedWritableMemChunk[] memChunks = createMemChunks(scenario,
iterations);
+ return ManualPerformanceTestUtils.measure(1, () -> runWrite(scenario,
memChunks));
+ }
+
+ private static void runReconciliation(AccountingTarget target, int
iterations) {
+ long correction = 0;
+ for (int i = 0; i < iterations; i++) {
+ TsFileProcessor.AlignedTVListRamCostSnapshot snapshot =
+ new TsFileProcessor.AlignedTVListRamCostSnapshot(target.memTable,
target.deviceId);
+ correction += snapshot.getMemoryCorrection(0);
+ }
+ benchmarkBlackhole = correction + iterations;
+ }
+
+ private static void runWrite(Scenario scenario, AlignedWritableMemChunk[]
memChunks) {
+ for (AlignedWritableMemChunk memChunk : memChunks) {
+ memChunk.writeAlignedTablet(
+ scenario.times,
+ scenario.columns,
+ scenario.bitMaps,
+ scenario.schemas,
+ 0,
+ scenario.times.length,
+ null);
+ }
+ benchmarkBlackhole = memChunks[memChunks.length - 1].rowCount();
+ }
+
+ private static AlignedWritableMemChunk[] createMemChunks(Scenario scenario,
int count) {
+ AlignedWritableMemChunk[] memChunks = new AlignedWritableMemChunk[count];
+ for (int i = 0; i < count; i++) {
+ memChunks[i] = new AlignedWritableMemChunk(new
ArrayList<>(scenario.schemas), false);
+ }
+ return memChunks;
+ }
+
+ private static AccountingTarget createAccountingTarget(Scenario scenario) {
+ AlignedWritableMemChunk memChunk =
+ new AlignedWritableMemChunk(new ArrayList<>(scenario.schemas), false);
+ memChunk.writeAlignedTablet(
+ scenario.times,
+ scenario.columns,
+ scenario.bitMaps,
+ scenario.schemas,
+ 0,
+ scenario.times.length,
+ null);
+ IDeviceID deviceId =
IDeviceID.Factory.DEFAULT_FACTORY.create("root.accounting.d0");
+ IMemTable memTable =
+ new PrimitiveMemTable(
+ "root.accounting",
+ "0",
+ Collections.singletonMap(
+ deviceId,
+ new AlignedWritableMemChunkGroup(
+ memChunk, new ArrayList<>(scenario.schemas), false)));
+ return new AccountingTarget(memTable, deviceId);
+ }
+
+ private static Scenario createScenario(int columnCount, int rowCount,
boolean nullHeavy) {
+ String[] measurements = new String[columnCount];
+ TSDataType[] dataTypes = new TSDataType[columnCount];
+ Object[] columns = new Object[columnCount];
+ BitMap[] bitMaps = nullHeavy ? new BitMap[columnCount] : null;
+ List<IMeasurementSchema> schemas = new ArrayList<>(columnCount);
+ for (int column = 0; column < columnCount; column++) {
+ measurements[column] = "s" + column;
+ dataTypes[column] = TSDataType.INT32;
+ schemas.add(new MeasurementSchema(measurements[column],
TSDataType.INT32));
+ int[] values = new int[rowCount];
+ for (int row = 0; row < rowCount; row++) {
+ values[row] = row;
+ }
+ columns[column] = values;
+ if (nullHeavy) {
+ bitMaps[column] = BitMap.createBitMapDynamically(rowCount);
+ for (int row = column & 1; row < rowCount; row += 2) {
+ bitMaps[column].mark(row);
+ }
+ }
+ }
+ long[] times = new long[rowCount];
+ for (int row = 0; row < rowCount; row++) {
+ times[row] = row;
+ }
+ return new Scenario(measurements, dataTypes, columns, bitMaps, schemas,
times);
+ }
+
+ private static void printResult(
+ String label,
+ Scenario scenario,
+ int warmupIterations,
+ int iterations,
+ int rounds,
+ Summary reconciliationSummary,
+ Summary writeSummary) {
+ System.out.printf(
+ Locale.ROOT,
+ "Aligned bitmap accounting benchmark (%s): columns=%d, rows=%d,
warmups=%d, iterations/round=%d, rounds=%d%n",
+ label,
+ scenario.measurements.length,
+ scenario.times.length,
+ warmupIterations,
+ iterations,
+ rounds);
+ printSummary("reconcile", reconciliationSummary);
+ printSummary("write", writeSummary);
+ System.out.printf(
+ Locale.ROOT,
+ " reconciliation/write CPU ratio=%.2f%%, allocation ratio=%.2f%%%n",
+ percentage(
+ reconciliationSummary.getCpuNanosPerOperation(),
+ writeSummary.getCpuNanosPerOperation()),
+ percentage(
+ reconciliationSummary.getAllocatedBytesPerOperation(),
+ writeSummary.getAllocatedBytesPerOperation()));
+ }
+
+ private static void printSummary(String label, Summary summary) {
+ System.out.printf(
+ Locale.ROOT,
+ " %-10s CPU=%.3f us/batch, allocated=%.1f bytes/batch, peak heap
delta=%.3f MiB%n",
+ label,
+ summary.getCpuNanosPerOperation() / 1_000.0,
+ summary.getAllocatedBytesPerOperation(),
+ summary.getPeakHeapDeltaBytes() / 1024.0 / 1024.0);
+ }
+
+ private static double percentage(double numerator, double denominator) {
+ return denominator == 0 ? 0 : numerator * 100.0 / denominator;
+ }
+
+ private static final class AccountingTarget {
+
+ private final IMemTable memTable;
+ private final IDeviceID deviceId;
+
+ private AccountingTarget(IMemTable memTable, IDeviceID deviceId) {
+ this.memTable = memTable;
+ this.deviceId = deviceId;
+ }
+ }
+
+ private static final class Scenario {
+
+ private final String[] measurements;
+ private final TSDataType[] dataTypes;
+ private final Object[] columns;
+ private final BitMap[] bitMaps;
+ private final List<IMeasurementSchema> schemas;
+ private final long[] times;
+
+ private Scenario(
+ String[] measurements,
+ TSDataType[] dataTypes,
+ Object[] columns,
+ BitMap[] bitMaps,
+ List<IMeasurementSchema> schemas,
+ long[] times) {
+ this.measurements = measurements;
+ this.dataTypes = dataTypes;
+ this.columns = columns;
+ this.bitMaps = bitMaps;
+ this.schemas = schemas;
+ this.times = times;
+ }
+ }
+}
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 3a85754a71c..a8193a9c0b2 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
@@ -23,6 +23,7 @@ import
org.apache.iotdb.commons.exception.IllegalPathException;
import org.apache.iotdb.commons.exception.MetadataException;
import org.apache.iotdb.commons.file.SystemFileFactory;
import org.apache.iotdb.commons.path.AlignedFullPath;
+import org.apache.iotdb.commons.path.AlignedPath;
import org.apache.iotdb.commons.path.NonAlignedFullPath;
import org.apache.iotdb.commons.path.PartialPath;
import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
@@ -63,6 +64,7 @@ import org.apache.tsfile.read.expression.QueryExpression;
import org.apache.tsfile.read.query.dataset.QueryDataSet;
import org.apache.tsfile.read.reader.IPointReader;
import org.apache.tsfile.utils.Binary;
+import org.apache.tsfile.utils.BitMap;
import org.apache.tsfile.write.record.TSRecord;
import org.apache.tsfile.write.record.datapoint.DataPoint;
import org.apache.tsfile.write.schema.MeasurementSchema;
@@ -537,21 +539,21 @@ public class TsFileProcessorTest {
true,
new long[5]);
IMemTable memTable = processor.getWorkMemTable();
- Assert.assertEquals(1752552, memTable.getTVListsRamCost());
+ Assert.assertEquals(1596552, memTable.getTVListsRamCost());
processor.insertTablet(
genInsertTableNode(100, true),
Collections.singletonList(new int[] {0, 10}),
new TSStatus[10],
true,
new long[5]);
- Assert.assertEquals(1752552, memTable.getTVListsRamCost());
+ Assert.assertEquals(1596552, memTable.getTVListsRamCost());
processor.insertTablet(
genInsertTableNode(200, true),
Collections.singletonList(new int[] {0, 10}),
new TSStatus[10],
true,
new long[5]);
- Assert.assertEquals(1752552, memTable.getTVListsRamCost());
+ Assert.assertEquals(1596552, memTable.getTVListsRamCost());
Assert.assertEquals(90000, memTable.getTotalPointsNum());
Assert.assertEquals(720360, memTable.memSize());
// Test records
@@ -560,7 +562,7 @@ public class TsFileProcessorTest {
record.addTuple(DataPoint.getDataPoint(dataType, measurementId,
String.valueOf(i)));
processor.insert(buildInsertRowNodeByTSRecord(record), new long[5]);
}
- Assert.assertEquals(1754168, memTable.getTVListsRamCost());
+ Assert.assertEquals(1598168, memTable.getTVListsRamCost());
Assert.assertEquals(90100, memTable.getTotalPointsNum());
Assert.assertEquals(721560, memTable.memSize());
}
@@ -587,7 +589,9 @@ public class TsFileProcessorTest {
Assert.assertEquals(
expectedProcessor.getWorkMemTable().getTVListsRamCost()
- - AlignedTVList.primitiveArrayMemCost(dataType),
+ - AlignedTVList.valueListArrayMemCost(dataType)
+ + 2 * AlignedTVList.bitmapReferenceRamCost()
+ + AlignedTVList.bitmapRamCost(),
actualProcessor.getWorkMemTable().getTVListsRamCost());
Assert.assertEquals(
TSStatusCode.OUT_OF_TTL.getStatusCode(),
actualResults[failedIndex].getCode());
@@ -616,7 +620,10 @@ public class TsFileProcessorTest {
AlignedTVList.alignedTvListArrayMemCost(
new TSDataType[] {TSDataType.INT32, TSDataType.INT32}, null);
Assert.assertEquals(
- denseBlockCost - AlignedTVList.primitiveArrayMemCost(TSDataType.INT32),
+ denseBlockCost
+ - AlignedTVList.valueListArrayMemCost(TSDataType.INT32)
+ + 2 * AlignedTVList.bitmapReferenceRamCost()
+ + AlignedTVList.bitmapRamCost(),
processor.getWorkMemTable().getTVListsRamCost() -
ramCostBeforeNewBlock);
}
@@ -642,10 +649,63 @@ public class TsFileProcessorTest {
AlignedTVList.alignedTvListArrayMemCost(
new TSDataType[] {TSDataType.INT32, TSDataType.INT32}, null);
Assert.assertEquals(
- denseBlockCost - AlignedTVList.primitiveArrayMemCost(TSDataType.INT32),
+ denseBlockCost
+ - AlignedTVList.valueListArrayMemCost(TSDataType.INT32)
+ + 2 * AlignedTVList.bitmapReferenceRamCost()
+ + AlignedTVList.bitmapRamCost(),
processor.getWorkMemTable().getTVListsRamCost() -
ramCostBeforeNewBlock);
}
+ @Test
+ public void alignedBitmapMemoryAccountingMatchesActualAllocations()
+ throws MetadataException, WriteProcessException, IOException,
IllegalPathException {
+ processor = newTestProcessor(filePath + ".bitmap-accounting");
+ int denseRowCount = PrimitiveArrayManager.ARRAY_SIZE * 2 + 1;
+ processor.insertTablet(
+ genAlignedTablet(new String[] {"s0", "s1"}, denseRowCount, 0),
+ Collections.singletonList(new int[] {0, denseRowCount}),
+ new TSStatus[denseRowCount],
+ true,
+ new long[5]);
+
+ AlignedWritableMemChunk alignedMemChunk = getAlignedMemChunk(deviceId);
+ Assert.assertNull(alignedMemChunk.getWorkingTVList().getBitMaps());
+ assertAlignedTvListRamCostMatchesActual(deviceId);
+
+ InsertTabletNode nullTablet = genAlignedTablet(new String[] {"s0", "s1"},
2, denseRowCount);
+ BitMap secondColumnNulls = new BitMap(2);
+ secondColumnNulls.markAll();
+ nullTablet.setBitMaps(new BitMap[] {null, secondColumnNulls});
+ processor.insertTablet(
+ nullTablet,
+ Arrays.asList(new int[] {0, 1}, new int[] {1, 2}),
+ new TSStatus[2],
+ true,
+ new long[5]);
+
+ List<BitMap> secondColumnBitMaps =
alignedMemChunk.getWorkingTVList().getBitMaps().get(1);
+ Assert.assertNull(secondColumnBitMaps.get(0));
+ Assert.assertNull(secondColumnBitMaps.get(1));
+ Assert.assertNotNull(secondColumnBitMaps.get(2));
+ assertAlignedTvListRamCostMatchesActual(deviceId);
+
+ TSRecord extendedColumnRecord = new TSRecord(deviceId, denseRowCount + 2L);
+ extendedColumnRecord.addTuple(DataPoint.getDataPoint(TSDataType.INT32,
"s2", "1"));
+ InsertRowNode extendedColumnRow =
buildInsertRowNodeByTSRecord(extendedColumnRecord);
+ extendedColumnRow.setAligned(true);
+ processor.insert(extendedColumnRow, new long[5]);
+
+ int extendedColumnIndex = alignedMemChunk.getMeasurementIndex("s2");
+ Assert.assertNull(
+
alignedMemChunk.getWorkingTVList().getValues().get(extendedColumnIndex).get(0));
+ Assert.assertNull(
+
alignedMemChunk.getWorkingTVList().getValues().get(extendedColumnIndex).get(1));
+ for (BitMap bitMap :
alignedMemChunk.getWorkingTVList().getBitMaps().get(extendedColumnIndex)) {
+ Assert.assertNotNull(bitMap);
+ }
+ assertAlignedTvListRamCostMatchesActual(deviceId);
+ }
+
@Test
public void alignedTvListRamCostTest2()
throws MetadataException, WriteProcessException, IOException {
@@ -669,7 +729,7 @@ public class TsFileProcessorTest {
true,
new long[5]);
IMemTable memTable = processor.getWorkMemTable();
- Assert.assertEquals(1752552, memTable.getTVListsRamCost());
+ Assert.assertEquals(1596552, memTable.getTVListsRamCost());
processor.insertTablet(
genInsertTableNodeFors3000ToS6000(0, true),
Collections.singletonList(new int[] {0, 10}),
@@ -711,7 +771,7 @@ public class TsFileProcessorTest {
new TSStatus[10],
true,
new long[5]);
- Assert.assertEquals(5425104, memTable.getTVListsRamCost());
+ Assert.assertEquals(5269104, memTable.getTVListsRamCost());
processor.insertTablet(
genInsertTableNodeFors3000ToS6000(300, true),
Collections.singletonList(new int[] {0, 10}),
@@ -1009,7 +1069,9 @@ public class TsFileProcessorTest {
long denseRowRamIncrement = memTable.getTVListsRamCost() -
ramCostBeforeDenseRow;
Assert.assertEquals(
- AlignedTVList.primitiveArrayMemCost(dataType),
+ AlignedTVList.valueListArrayMemCost(dataType)
+ - 2 * AlignedTVList.bitmapReferenceRamCost()
+ - AlignedTVList.bitmapRamCost(),
denseRowRamIncrement - sparseRowRamIncrement);
}
@@ -1404,6 +1466,23 @@ public class TsFileProcessorTest {
targetProcessor.insert(node, new long[5]);
}
+ private AlignedWritableMemChunk getAlignedMemChunk(String targetDevice) {
+ IWritableMemChunk memChunk =
+ processor
+ .getWorkMemTable()
+ .getWritableMemChunk(
+ IDeviceID.Factory.DEFAULT_FACTORY.create(targetDevice),
+ AlignedPath.VECTOR_PLACEHOLDER);
+ Assert.assertNotNull(memChunk);
+ return (AlignedWritableMemChunk) memChunk;
+ }
+
+ private void assertAlignedTvListRamCostMatchesActual(String targetDevice) {
+ Assert.assertEquals(
+
getAlignedMemChunk(targetDevice).getWorkingTVList().calculateRamSize().getRamSize(),
+ processor.getWorkMemTable().getTVListsRamCost());
+ }
+
private InsertTabletNode genSingleMeasurementTablet(int rowCount, boolean
isAligned)
throws IllegalPathException {
String[] measurements = new String[] {measurementId};
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 06e422ad776..381f733bf4d 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
@@ -45,14 +45,13 @@ import static
org.apache.tsfile.utils.RamUsageEstimator.NUM_BYTES_OBJECT_REF;
public class AlignedTVListTest {
@Test
- public void testValueListArrayMemCostIncludesBitmapImplementation() {
- long expected =
- (long) ARRAY_SIZE * Long.BYTES
- + BitMap.createBitMapDynamically(ARRAY_SIZE).ramBytesUsed()
- + NUM_BYTES_ARRAY_HEADER
- + 2L * NUM_BYTES_OBJECT_REF;
+ public void testValueListArrayMemCostExcludesLazyBitmapAndNullPlaceholder() {
+ long expected = (long) ARRAY_SIZE * Long.BYTES + NUM_BYTES_ARRAY_HEADER +
NUM_BYTES_OBJECT_REF;
Assert.assertEquals(expected,
AlignedTVList.valueListArrayMemCost(TSDataType.INT64));
+ Assert.assertEquals(
+ BitMap.createBitMapDynamically(ARRAY_SIZE).ramBytesUsed(),
AlignedTVList.bitmapRamCost());
+ Assert.assertEquals(NUM_BYTES_OBJECT_REF,
AlignedTVList.bitmapReferenceRamCost());
}
@Test
@@ -189,6 +188,9 @@ public class AlignedTVListTest {
firstColumnBitMaps.get(2).ramBytesUsed() < new
BitMap(ARRAY_SIZE).ramBytesUsed());
Assert.assertTrue(tvList.isNullValue(ARRAY_SIZE * 2 + 1, 0));
Assert.assertFalse(tvList.isNullValue(ARRAY_SIZE * 2, 0));
+ Assert.assertEquals(
+ 3L * AlignedTVList.bitmapReferenceRamCost() +
AlignedTVList.bitmapRamCost(),
+ tvList.calculateRamSize().getRamSize() - 3L *
tvList.alignedTvListArrayMemCost());
}
@Test
@@ -263,10 +265,14 @@ public class AlignedTVListTest {
Assert.assertTrue(tvList.isNullValue(0, 1));
Assert.assertEquals(1, tvList.getLongByValueIndex(ARRAY_SIZE + 1, 1));
+ long ramSizeBeforeExtension = tvList.calculateRamSize().getRamSize();
tvList.extendColumn(TSDataType.INT32);
Assert.assertNull(tvList.getValues().get(2).get(0));
Assert.assertNull(tvList.getValues().get(2).get(1));
+ Assert.assertEquals(
+ 2L * (AlignedTVList.bitmapReferenceRamCost() +
AlignedTVList.bitmapRamCost()),
+ tvList.calculateRamSize().getRamSize() - ramSizeBeforeExtension);
long ramSizeBeforeExtendedColumnMaterialization =
tvList.calculateRamSize().getRamSize();
tvList.putAlignedValue(ARRAY_SIZE + 2L, new Object[] {null, null, 2});
@@ -277,7 +283,7 @@ public class AlignedTVListTest {
Assert.assertFalse(tvList.isNullValue(ARRAY_SIZE + 2, 2));
Assert.assertEquals(2, tvList.getIntByValueIndex(ARRAY_SIZE + 2, 2));
Assert.assertEquals(
- AlignedTVList.primitiveArrayMemCost(TSDataType.INT32),
+ AlignedTVList.valueListArrayMemCost(TSDataType.INT32),
tvList.calculateRamSize().getRamSize() -
ramSizeBeforeExtendedColumnMaterialization);
}
@@ -293,7 +299,7 @@ public class AlignedTVListTest {
tvList.putAlignedValue(ARRAY_SIZE + 1L, new Object[] {1L, 1L});
Assert.assertEquals(
- AlignedTVList.primitiveArrayMemCost(TSDataType.INT64),
+ AlignedTVList.valueListArrayMemCost(TSDataType.INT64),
tvList.calculateRamSize().getRamSize() - ramSizeBeforeMaterialization);
Assert.assertEquals(
@@ -307,7 +313,9 @@ public class AlignedTVListTest {
Assert.assertEquals(
(long) projectedTvList.getValues().get(0).size()
*
projectedTvList.alignedTvListArrayMemCostWithoutPrimitiveArrays()
- + AlignedTVList.primitiveArrayMemCost(TSDataType.INT64),
+ + AlignedTVList.valueListArrayMemCost(TSDataType.INT64)
+ + (long) projectedTvList.getBitMaps().get(0).size()
+ * (AlignedTVList.bitmapReferenceRamCost() +
AlignedTVList.bitmapRamCost()),
projectedTvList.calculateRamSize().getRamSize());
tvList.clear();
@@ -325,7 +333,10 @@ public class AlignedTVListTest {
int blockCount = tvList.getValues().get(0).size();
long denseRamSize = blockCount * tvList.alignedTvListArrayMemCost();
long expectedRamSize =
- denseRamSize - blockCount *
AlignedTVList.primitiveArrayMemCost(TSDataType.INT64);
+ denseRamSize
+ - blockCount *
AlignedTVList.valueListArrayMemCost(TSDataType.INT64)
+ + (long) blockCount
+ * (AlignedTVList.bitmapReferenceRamCost() +
AlignedTVList.bitmapRamCost());
Assert.assertEquals(expectedRamSize,
tvList.calculateRamSize().getRamSize());
}