Skip to content

Commit 881f0c7

Browse files
authored
Pipe: dynamically share idle floating memory (#18422) (#18437)
1 parent a177a93 commit 881f0c7

8 files changed

Lines changed: 80 additions & 22 deletions

File tree

‎iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/event/common/tsfile/container/TsFileInsertionDataContainerProvider.java‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import org.apache.iotdb.db.pipe.event.common.tsfile.container.scan.TsFileInsertionScanDataContainer;
2929
import org.apache.iotdb.db.pipe.metric.overview.PipeTsFileToTabletsMetrics;
3030
import org.apache.iotdb.db.pipe.resource.PipeDataNodeResourceManager;
31-
import org.apache.iotdb.db.pipe.resource.memory.PipeMemoryManager;
3231
import org.apache.iotdb.db.pipe.resource.tsfile.PipeTsFilePublicResource;
3332

3433
import org.apache.tsfile.file.metadata.IDeviceID;
@@ -133,7 +132,7 @@ public TsFileInsertionDataContainer provide(final boolean isWithMod) throws IOEx
133132

134133
// Use scan container to save memory
135134
if ((double) PipeDataNodeResourceManager.memory().getUsedMemorySizeInBytes()
136-
/ PipeMemoryManager.getTotalNonFloatingMemorySizeInBytes()
135+
/ PipeDataNodeResourceManager.memory().getTotalNonFloatingMemorySizeInBytes()
137136
> PipeTsFilePublicResource.MEMORY_SUFFICIENT_THRESHOLD) {
138137
return new TsFileInsertionScanDataContainer(
139138
pipeName,

‎iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/metric/overview/PipeResourceMetrics.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,14 @@ public void bindTo(final AbstractMetricService metricService) {
7575
Metric.PIPE_MEM.toString(),
7676
MetricLevel.IMPORTANT,
7777
PipeDataNodeResourceManager.memory(),
78-
o -> PipeMemoryManager.getTotalNonFloatingMemorySizeInBytes(),
78+
PipeMemoryManager::getTotalNonFloatingMemorySizeInBytes,
7979
Tag.NAME.toString(),
8080
PIPE_TOTAL_MEMORY);
8181
metricService.createAutoGauge(
8282
Metric.PIPE_MEM.toString(),
8383
MetricLevel.IMPORTANT,
8484
PipeDataNodeResourceManager.memory(),
85-
o -> PipeMemoryManager.getTotalFloatingMemorySizeInBytes(),
85+
PipeMemoryManager::getTotalFloatingMemorySizeInBytes,
8686
Tag.NAME.toString(),
8787
PIPE_FLOATING_MEMORY);
8888
metricService.createAutoGauge(

‎iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManager.java‎

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.util.Map;
3939
import java.util.Objects;
4040
import java.util.Set;
41+
import java.util.function.LongSupplier;
4142
import java.util.function.LongUnaryOperator;
4243

4344
public class PipeMemoryManager {
@@ -53,6 +54,9 @@ public class PipeMemoryManager {
5354
private static final long MEMORY_ALLOCATE_MIN_SIZE_IN_BYTES =
5455
PipeConfig.getInstance().getPipeMemoryAllocateMinSizeInBytes();
5556

57+
private final long totalMemorySizeInBytes;
58+
private final LongSupplier floatingMemoryUsageSupplier;
59+
5660
private long usedMemorySizeInBytes;
5761

5862
private static final double EXCEED_PROTECT_THRESHOLD = 0.95;
@@ -79,13 +83,22 @@ public class PipeMemoryManager {
7983
private final Set<PipeMemoryBlock> expandableBlocks = new HashSet<>();
8084

8185
public PipeMemoryManager() {
86+
this(
87+
TOTAL_MEMORY_SIZE_IN_BYTES,
88+
() -> PipeDataNodeAgent.task().getAllFloatingMemoryUsageInByte());
8289
PipeDataNodeAgent.runtime()
8390
.registerPeriodicalJob(
8491
"PipeMemoryManager#tryExpandAll()",
8592
this::tryExpandAllAndCheckConsistency,
8693
PipeConfig.getInstance().getPipeMemoryExpanderIntervalSeconds());
8794
}
8895

96+
PipeMemoryManager(
97+
final long totalMemorySizeInBytes, final LongSupplier floatingMemoryUsageSupplier) {
98+
this.totalMemorySizeInBytes = totalMemorySizeInBytes;
99+
this.floatingMemoryUsageSupplier = floatingMemoryUsageSupplier;
100+
}
101+
89102
// NOTE: Here we unify the memory threshold judgment for tablet and tsfile memory block, because
90103
// introducing too many heuristic rules not conducive to flexible dynamic adjustment of memory
91104
// configuration:
@@ -96,15 +109,15 @@ public PipeMemoryManager() {
96109
// 3. The sum of the memory proportion occupied by the tablet memory block and the tsfile memory
97110
// block does not exceed TABLET_MEMORY_REJECT_THRESHOLD + TS_FILE_MEMORY_REJECT_THRESHOLD
98111

99-
private static double allowedMaxMemorySizeInBytesOfTabletsAndTsFiles() {
112+
private double allowedMaxMemorySizeInBytesOfTabletsAndTsFiles() {
100113
return (PipeConfig.getInstance()
101114
.getPipeDataStructureTabletMemoryBlockAllocationRejectThreshold()
102115
+ PipeConfig.getInstance()
103116
.getPipeDataStructureTsFileMemoryBlockAllocationRejectThreshold())
104117
* getTotalNonFloatingMemorySizeInBytes();
105118
}
106119

107-
private static double allowedMaxMemorySizeInBytesOfTablets() {
120+
private double allowedMaxMemorySizeInBytesOfTablets() {
108121
return (PipeConfig.getInstance()
109122
.getPipeDataStructureTabletMemoryBlockAllocationRejectThreshold()
110123
+ PipeConfig.getInstance()
@@ -113,7 +126,7 @@ private static double allowedMaxMemorySizeInBytesOfTablets() {
113126
* getTotalNonFloatingMemorySizeInBytes();
114127
}
115128

116-
private static double allowedMaxMemorySizeInBytesOfTsTiles() {
129+
private double allowedMaxMemorySizeInBytesOfTsTiles() {
117130
return (PipeConfig.getInstance()
118131
.getPipeDataStructureTsFileMemoryBlockAllocationRejectThreshold()
119132
+ PipeConfig.getInstance()
@@ -1037,19 +1050,30 @@ public long getUsedMemorySizeInBytesOfTsFiles() {
10371050
}
10381051

10391052
public long getFreeMemorySizeInBytes() {
1040-
return TOTAL_MEMORY_SIZE_IN_BYTES - usedMemorySizeInBytes;
1053+
return Math.max(0, getTotalNonFloatingMemorySizeInBytes() - usedMemorySizeInBytes);
10411054
}
10421055

1043-
public static long getTotalNonFloatingMemorySizeInBytes() {
1044-
return (long)
1045-
(TOTAL_MEMORY_SIZE_IN_BYTES
1046-
* (1 - PipeConfig.getInstance().getPipeTotalFloatingMemoryProportion()));
1056+
public long getTotalNonFloatingMemorySizeInBytes() {
1057+
// Floating memory is an upper limit for retained InsertNodes instead of a statically reserved
1058+
// partition. Non-floating allocations can borrow all floating memory that is not actually in
1059+
// use, which is especially important for TsFile-only pipes.
1060+
return Math.max(0, totalMemorySizeInBytes - getUsedFloatingMemorySizeInBytes());
1061+
}
1062+
1063+
public long getTotalFloatingMemorySizeInBytes() {
1064+
final long configuredUpperLimit =
1065+
Math.max(
1066+
0,
1067+
(long)
1068+
(totalMemorySizeInBytes
1069+
* PipeConfig.getInstance().getPipeTotalFloatingMemoryProportion()));
1070+
final long memoryNotUsedByNonFloatingAllocations =
1071+
Math.max(0, totalMemorySizeInBytes - usedMemorySizeInBytes);
1072+
return Math.min(configuredUpperLimit, memoryNotUsedByNonFloatingAllocations);
10471073
}
10481074

1049-
public static long getTotalFloatingMemorySizeInBytes() {
1050-
return (long)
1051-
(TOTAL_MEMORY_SIZE_IN_BYTES
1052-
* PipeConfig.getInstance().getPipeTotalFloatingMemoryProportion());
1075+
private long getUsedFloatingMemorySizeInBytes() {
1076+
return Math.max(0, floatingMemoryUsageSupplier.getAsLong());
10531077
}
10541078

10551079
public static long getTotalMemorySizeInBytes() {

‎iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionHybridSource.java‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import org.apache.iotdb.db.pipe.event.realtime.PipeRealtimeEvent;
3030
import org.apache.iotdb.db.pipe.metric.overview.PipeDataNodeRemainingEventAndTimeOperator;
3131
import org.apache.iotdb.db.pipe.metric.overview.PipeDataNodeSinglePipeMetrics;
32-
import org.apache.iotdb.db.pipe.resource.memory.PipeMemoryManager;
32+
import org.apache.iotdb.db.pipe.resource.PipeDataNodeResourceManager;
3333
import org.apache.iotdb.db.pipe.source.dataregion.realtime.assigner.PipeTsFileEpochProgressIndexKeeper;
3434
import org.apache.iotdb.db.pipe.source.dataregion.realtime.epoch.TsFileEpoch;
3535
import org.apache.iotdb.pipe.api.event.Event;
@@ -178,7 +178,8 @@ private boolean canNotUseTabletAnymore(final PipeRealtimeEvent event) {
178178
final long floatingMemoryUsageInByte =
179179
PipeDataNodeAgent.task().getFloatingMemoryUsageInByte(pipeName);
180180
final long pipeCount = PipeDataNodeAgent.task().getPipeCount();
181-
long totalFloatingMemorySizeInBytes = PipeMemoryManager.getTotalFloatingMemorySizeInBytes();
181+
long totalFloatingMemorySizeInBytes =
182+
PipeDataNodeResourceManager.memory().getTotalFloatingMemorySizeInBytes();
182183
// If the occupied memory has reached the max, it may cause a large latency to the receiver due
183184
// to queuing. To reduce the latency, we lower the memory limit forcibly in the single tsFile
184185
// since the tsFile is doomed to be transferred, then more downgrading will just cause more

‎iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/agent/task/PipeProcessorSubtaskExecutorTest.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public void testTsFileInsertionEventPreservesOutOfMemoryCause() {
7070
try {
7171
memoryBlock =
7272
memoryManager.forceAllocateForTabletWithRetry(
73-
PipeMemoryManager.getTotalNonFloatingMemorySizeInBytes());
73+
memoryManager.getTotalNonFloatingMemorySizeInBytes());
7474
Assert.assertFalse(memoryManager.isEnough4TabletParsing());
7575

7676
final File tsFile =

‎iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/resource/memory/PipeMemoryManagerResizeTest.java‎

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,11 @@
2828
import org.junit.Before;
2929
import org.junit.Test;
3030

31+
import java.util.concurrent.atomic.AtomicLong;
32+
3133
public class PipeMemoryManagerResizeTest {
3234

35+
private static final long TOTAL_MEMORY_SIZE_IN_BYTES = 2000;
3336
private final CommonConfig config = CommonDescriptor.getInstance().getConfig();
3437

3538
private boolean originalMemoryManagementEnabled;
@@ -76,7 +79,7 @@ public void testTabletResizeCannotCrossTabletHardLimit() {
7679
final PipeTabletMemoryBlock tablet = manager.forceAllocateForTabletWithRetry(0);
7780
final long tabletMemorySizeInBytes =
7881
(long)
79-
(PipeMemoryManager.getTotalNonFloatingMemorySizeInBytes()
82+
(manager.getTotalNonFloatingMemorySizeInBytes()
8083
* (config.getPipeDataStructureTabletMemoryBlockAllocationRejectThreshold()
8184
+ config.getPipeDataStructureTsFileMemoryBlockAllocationRejectThreshold()
8285
/ 2))
@@ -97,8 +100,7 @@ public void testTabletResizeCannotCrossTabletHardLimit() {
97100
@Test
98101
public void testTabletResizeLeavesMemoryForSinkForwardProgress() {
99102
final PipeMemoryManager manager = new PipeMemoryManager();
100-
final long totalNonFloatingMemorySizeInBytes =
101-
PipeMemoryManager.getTotalNonFloatingMemorySizeInBytes();
103+
final long totalNonFloatingMemorySizeInBytes = manager.getTotalNonFloatingMemorySizeInBytes();
102104
final long tabletMemorySizeInBytes =
103105
(long)
104106
(totalNonFloatingMemorySizeInBytes
@@ -134,4 +136,32 @@ public void testTabletResizeLeavesMemoryForSinkForwardProgress() {
134136

135137
Assert.assertEquals(0, manager.getUsedMemorySizeInBytes());
136138
}
139+
140+
@Test
141+
public void testFloatingAndNonFloatingMemoryShareTheSamePool() {
142+
final AtomicLong floatingMemoryUsageInBytes = new AtomicLong(0);
143+
final PipeMemoryManager manager =
144+
new PipeMemoryManager(TOTAL_MEMORY_SIZE_IN_BYTES, floatingMemoryUsageInBytes::get);
145+
146+
Assert.assertEquals(TOTAL_MEMORY_SIZE_IN_BYTES, manager.getTotalNonFloatingMemorySizeInBytes());
147+
Assert.assertEquals(
148+
TOTAL_MEMORY_SIZE_IN_BYTES / 2, manager.getTotalFloatingMemorySizeInBytes());
149+
150+
final PipeTsFileMemoryBlock nonFloatingMemory = manager.forceAllocateForTsFileWithRetry(1200);
151+
try {
152+
// Non-floating memory can borrow the unused half that was previously reserved for InsertNode
153+
// queues. Its usage also reduces the current floating-memory limit symmetrically.
154+
Assert.assertEquals(1200, manager.getUsedMemorySizeInBytes());
155+
Assert.assertEquals(800, manager.getTotalFloatingMemorySizeInBytes());
156+
157+
floatingMemoryUsageInBytes.set(500);
158+
Assert.assertEquals(1500, manager.getTotalNonFloatingMemorySizeInBytes());
159+
Assert.assertEquals(300, manager.getFreeMemorySizeInBytes());
160+
161+
Assert.assertThrows(
162+
PipeRuntimeOutOfMemoryCriticalException.class, () -> manager.forceAllocate(301));
163+
} finally {
164+
manager.release(nonFloatingMemory);
165+
}
166+
}
137167
}

‎iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/conf/CommonConfig.java‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,9 @@ public class CommonConfig {
224224
private int pipeDataStructureTabletSizeInBytes = 16 * 1024 * 1024;
225225
private double pipeDataStructureTabletMemoryBlockAllocationRejectThreshold = 0.3;
226226
private double pipeDataStructureTsFileMemoryBlockAllocationRejectThreshold = 0.3;
227+
228+
// Maximum proportion for floating memory retained by InsertNode queues. Unused floating memory
229+
// can be borrowed by non-floating Pipe allocations.
227230
private volatile double pipeTotalFloatingMemoryProportion = 0.5;
228231

229232
// Check if memory check is enabled for Pipe

‎iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/PipeConfig.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public double getPipeDataStructureTsFileMemoryBlockAllocationRejectThreshold() {
6666
}
6767

6868
public double getPipeTotalFloatingMemoryProportion() {
69+
// This is the upper limit of floating memory, not a statically reserved partition.
6970
return COMMON_CONFIG.getPipeTotalFloatingMemoryProportion();
7071
}
7172

0 commit comments

Comments
 (0)