Skip to content

Commit f24b24d

Browse files
Add proactive RegionGroup allocation and policy hot reload (#18692)
1 parent a51258b commit f24b24d

19 files changed

Lines changed: 3390 additions & 77 deletions

File tree

‎integration-test/src/test/java/org/apache/iotdb/confignode/it/partition/IoTDBProactiveRegionGroupDistributionIT.java‎

Lines changed: 1471 additions & 0 deletions
Large diffs are not rendered by default.

‎integration-test/src/test/java/org/apache/iotdb/confignode/it/partition/IoTDBProactiveRegionGroupExtensionIT.java‎

Lines changed: 368 additions & 0 deletions
Large diffs are not rendered by default.

‎integration-test/src/test/java/org/apache/iotdb/db/it/IoTDBSimpleQueryIT.java‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,7 +1047,13 @@ public void testShowDevicesWithLimit() throws SQLException {
10471047
try (Connection connection = EnvFactory.getEnv().getConnection();
10481048
Statement statement = connection.createStatement()) {
10491049
try {
1050-
List<String> exps = Arrays.asList("root.sg1.d0,false", "root.sg1.d1,false");
1050+
List<String> exps =
1051+
new ArrayList<>(
1052+
Arrays.asList(
1053+
"root.sg1.d0,false",
1054+
"root.sg1.d1,false",
1055+
"root.sg1.d2,false",
1056+
"root.sg1.d3,false"));
10511057

10521058
statement.execute("INSERT INTO root.sg1.d0(timestamp, s1) VALUES (5, 5)");
10531059
statement.execute("INSERT INTO root.sg1.d1(timestamp, s2) VALUES (5, 5)");
@@ -1057,8 +1063,8 @@ public void testShowDevicesWithLimit() throws SQLException {
10571063
int count = 0;
10581064
try (ResultSet resultSet = statement.executeQuery("show devices limit 2")) {
10591065
while (resultSet.next()) {
1060-
Assert.assertEquals(
1061-
exps.get(count), resultSet.getString(1) + "," + resultSet.getString(2));
1066+
String device = resultSet.getString(1) + "," + resultSet.getString(2);
1067+
Assert.assertTrue(device, exps.remove(device));
10621068
++count;
10631069
}
10641070
}

‎integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBMultiTAGsWithAttributesTableIT.java‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1380,14 +1380,22 @@ public void maxByMinByExtremeTest() {
13801380
tableResultSetEqualTest(sql, expectedHeader, retArray, DATABASE_NAME);
13811381

13821382
expectedHeader = buildHeaders(30);
1383+
// Exclude l4 so both blob extrema are unique; tied rows may arrive in either order.
13831384
sql =
1384-
"select max_by(time,blob),max_by(device,blob),max_by(level,blob),max_by(attr1,blob),max_by(attr2,blob),max_by(num,blob),max_by(bignum,blob),max_by(floatnum,blob),max_by(str,blob),max_by(bool,blob),max_by(date,blob),max_by(blob,blob),max_by(ts,blob),max_by(stringv,blob),max_by(doubleNum,blob),min_by(time,blob),min_by(device,blob),min_by(level,blob),min_by(attr1,blob),min_by(attr2,blob),min_by(num,blob),min_by(bignum,blob),min_by(floatnum,blob),min_by(str,blob),min_by(bool,blob),min_by(date,blob),min_by(blob,blob),min_by(ts,blob),min_by(stringv,blob),min_by(doubleNum,blob) from table0";
1385+
"select max_by(time,blob),max_by(device,blob),max_by(level,blob),max_by(attr1,blob),max_by(attr2,blob),max_by(num,blob),max_by(bignum,blob),max_by(floatnum,blob),max_by(str,blob),max_by(bool,blob),max_by(date,blob),max_by(blob,blob),max_by(ts,blob),max_by(stringv,blob),max_by(doubleNum,blob),min_by(time,blob),min_by(device,blob),min_by(level,blob),min_by(attr1,blob),min_by(attr2,blob),min_by(num,blob),min_by(bignum,blob),min_by(floatnum,blob),min_by(str,blob),min_by(bool,blob),min_by(date,blob),min_by(blob,blob),min_by(ts,blob),min_by(stringv,blob),min_by(doubleNum,blob) from table0 where level != 'l4'";
13851386
retArray =
13861387
new String[] {
13871388
"1971-01-01T00:00:10.000Z,d1,l5,null,null,7,2147983648,213.112,lemon,true,null,0x108dcd63,2024-09-25T06:15:35.000Z,null,null,1970-01-01T00:00:00.020Z,d1,l2,yy,zz,2,2147483648,434.12,pineapple,true,null,0x108dcd62,2024-09-24T06:15:35.000Z,null,6666.8,",
13881389
};
13891390
tableResultSetEqualTest(sql, expectedHeader, retArray, DATABASE_NAME);
13901391

1392+
// Retain the unfiltered ties: either associated value is valid, but the extrema are fixed.
1393+
expectedHeader = buildHeaders(4);
1394+
sql =
1395+
"select max_by(num,blob) in (7,9),min_by(num,blob) in (2,5),max_by(blob,blob),min_by(blob,blob) from table0";
1396+
retArray = new String[] {"true,true,0x108dcd63,0x108dcd62,"};
1397+
tableResultSetEqualTest(sql, expectedHeader, retArray, DATABASE_NAME);
1398+
13911399
expectedHeader = buildHeaders(3);
13921400
sql = "select extreme(num),extreme(bignum),extreme(floatnum) from table0";
13931401
retArray = new String[] {"15,3147483648,4654.231,"};

‎integration-test/src/test/java/org/apache/iotdb/relational/it/db/it/IoTDBWindowFunctionIT.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ public void testPartitionByAndOrderByWithNulls() {
259259
"2021-01-01T09:20:00.000Z,null,null,2,",
260260
};
261261
tableResultSetEqualTest(
262-
"SELECT *, count(value) OVER (PARTITION BY device ORDER BY value) AS cnt FROM demo2 ORDER BY device",
262+
"SELECT *, count(value) OVER (PARTITION BY device ORDER BY value) AS cnt FROM demo2 ORDER BY device,value,time",
263263
expectedHeader,
264264
retArray,
265265
DATABASE_NAME);

‎integration-test/src/test/java/org/apache/iotdb/relational/it/query/view/recent/IoTDBTableViewQueryIT.java‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,15 +175,16 @@ public void test() throws Exception {
175175
"select current from view1 where time > 1",
176176
"select current from table1 where time > 1" + (aligned ? "" : " and current is not null"),
177177
true);
178+
// FILL runs before an outer ORDER BY, so give both sources the same ordered input.
178179
compareQueryResults(
179180
session,
180-
"select * from view1 fill method linear",
181-
"select * from table1 fill method linear",
181+
"select * from (select * from view1 order by battery, time) fill method linear",
182+
"select * from (select * from table1 order by battery, time) fill method linear",
182183
true);
183184
compareQueryResults(
184185
session,
185-
"select * from view1 fill method previous",
186-
"select * from table1 fill method previous",
186+
"select * from (select * from view1 order by battery, time) fill method previous",
187+
"select * from (select * from table1 order by battery, time) fill method previous",
187188
true);
188189
compareQueryResults(
189190
session,

‎iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/conf/ConfigNodeConfig.java‎

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,34 +95,82 @@ public class ConfigNodeConfig {
9595

9696
private String dataPartitionAllocationStrategy = "INHERIT";
9797

98-
/** The policy of extension SchemaRegionGroup for each Database. */
98+
/**
99+
* The policy of extending SchemaRegionGroups for each Database: CUSTOM, AUTO, or PROACTIVE.
100+
*
101+
* <p>CUSTOM suits known workloads that need manual resource allocation, creating the configured
102+
* target number of groups per Database when schema partitions are first allocated. AUTO suits
103+
* most routine workloads (roughly 80% as an approximate planning guideline), gradually meeting
104+
* the configured minimum and expanding with slot occupancy up to the resource-based maximum to
105+
* balance parallelism and group management overhead. PROACTIVE complements AUTO for workloads
106+
* such as very few devices with many measurements and a high load, creating groups earlier for
107+
* more parallelism at the cost of additional group management overhead.
108+
*
109+
* <p>AUTO and PROACTIVE use the same per-Database maximum calculation, including resource sharing
110+
* across Databases; their group counts match when both reach the same maximum. PROACTIVE targets
111+
* one group per distinct schema series slot, not per measurement, and grows toward the configured
112+
* minimum incrementally like AUTO. When all groups of this type are disabled, it may add one
113+
* group within the maximum. Switching policies does not remove existing groups.
114+
*/
99115
private volatile RegionGroupExtensionPolicy schemaRegionGroupExtensionPolicy =
100-
RegionGroupExtensionPolicy.AUTO;
116+
RegionGroupExtensionPolicy.PROACTIVE;
101117

102118
/**
103119
* When set schema_region_group_extension_policy=CUSTOM, this parameter is the default number of
104-
* SchemaRegionGroups for each Database. When set schema_region_group_extension_policy=AUTO, this
105-
* parameter is the default minimal number of SchemaRegionGroups for each Database.
120+
* SchemaRegionGroups for each Database. For AUTO and PROACTIVE, this parameter is the default
121+
* minimum number of SchemaRegionGroups and a lower bound for the per-Database maximum. Both
122+
* policies grow toward this minimum incrementally, adding at most the number of series slots in
123+
* the pending request to satisfy the minimum. PROACTIVE also grows with the number of active
124+
* series slots.
106125
*/
107126
private volatile int defaultSchemaRegionGroupNumPerDatabase = 1;
108127

109-
/** The maximum number of SchemaRegions expected to be managed by each DataNode. */
128+
/**
129+
* The expected number of SchemaRegions per DataNode, used to calculate the same per-Database
130+
* maximum for AUTO and PROACTIVE. This is not a hard limit on node or cluster totals:
131+
* per-Database minimums, rounding, and existing groups can raise the total above this resource
132+
* estimate.
133+
*/
110134
private volatile int schemaRegionPerDataNode = 1;
111135

112-
/** The policy of extension DataRegionGroup for each Database. */
136+
/**
137+
* The policy of extending DataRegionGroups for each Database: CUSTOM, AUTO, or PROACTIVE.
138+
*
139+
* <p>CUSTOM suits known workloads that need manual resource allocation, creating the configured
140+
* target number of groups per Database when data partitions are first allocated. AUTO suits most
141+
* routine workloads (roughly 80% as an approximate planning guideline), gradually meeting the
142+
* configured minimum and expanding with slot occupancy up to the resource-based maximum to
143+
* balance parallelism and group management overhead. PROACTIVE complements AUTO for workloads
144+
* such as very few devices with many measurements and a high load, creating groups earlier for
145+
* more parallelism at the cost of additional group management overhead.
146+
*
147+
* <p>AUTO and PROACTIVE use the same per-Database maximum calculation, including resource sharing
148+
* across Databases; their group counts match when both reach the same maximum. PROACTIVE targets
149+
* one group per distinct data series slot, not per measurement, and grows toward the configured
150+
* minimum incrementally like AUTO. New time partitions in an existing slot can also trigger this
151+
* growth toward the minimum. When all groups of this type are disabled, it may add one group
152+
* within the maximum. Its allocation policy table balances active slots so new time partitions
153+
* can use new groups; existing time-partition assignments are retained. Switching policies does
154+
* not remove existing groups.
155+
*/
113156
private volatile RegionGroupExtensionPolicy dataRegionGroupExtensionPolicy =
114-
RegionGroupExtensionPolicy.AUTO;
157+
RegionGroupExtensionPolicy.PROACTIVE;
115158

116159
/**
117160
* When set data_region_group_extension_policy=CUSTOM, this parameter is the default number of
118-
* DataRegionGroups for each Database. When set data_region_group_extension_policy=AUTO, this
119-
* parameter is the default minimal number of DataRegionGroups for each Database.
161+
* DataRegionGroups for each Database. For AUTO and PROACTIVE, this parameter is the default
162+
* minimum number of DataRegionGroups and a lower bound for the per-Database maximum. Both
163+
* policies grow toward this minimum incrementally, adding at most the number of series slots in
164+
* the pending request to satisfy the minimum. PROACTIVE also grows with the number of active
165+
* series slots.
120166
*/
121167
private volatile int defaultDataRegionGroupNumPerDatabase = 2;
122168

123169
/**
124-
* The maximum number of DataRegions expected to be managed by each DataNode. Set to 0 means that
125-
* each dataNode automatically has the number of CPU cores / 2 regions.
170+
* The expected number of DataRegions per DataNode, used to calculate the same per-Database
171+
* maximum for AUTO and PROACTIVE. Set to 0 to use half the CPU core count as the expected number
172+
* for each DataNode. This is not a hard limit on node or cluster totals: per-Database minimums,
173+
* rounding, and existing groups can raise the total above this resource estimate.
126174
*/
127175
private volatile int dataRegionPerDataNode = 0;
128176

‎iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ClusterQuotaManager.java‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,11 +272,10 @@ public void updateSpaceQuotaUsage() {
272272
.forEach(
273273
schemaRegionId -> {
274274
if (deviceNum.containsKey(schemaRegionId)) {
275-
deviceCount.addAndGet(deviceCount.get() + deviceNum.get(schemaRegionId));
275+
deviceCount.addAndGet(deviceNum.get(schemaRegionId));
276276
}
277277
if (timeSeriesNum.containsKey(schemaRegionId)) {
278-
timeSeriesCount.addAndGet(
279-
timeSeriesCount.get() + timeSeriesNum.get(schemaRegionId));
278+
timeSeriesCount.addAndGet(timeSeriesNum.get(schemaRegionId));
280279
}
281280
});
282281
quotaInfo.getSpaceQuotaUsage().get(entry.getKey()).setDeviceNum(deviceCount.get());

‎iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java‎

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@
125125
import org.apache.iotdb.confignode.manager.node.NodeMetrics;
126126
import org.apache.iotdb.confignode.manager.partition.PartitionManager;
127127
import org.apache.iotdb.confignode.manager.partition.PartitionMetrics;
128+
import org.apache.iotdb.confignode.manager.partition.RegionGroupExtensionPolicy;
128129
import org.apache.iotdb.confignode.manager.pipe.agent.PipeConfigNodeAgent;
129130
import org.apache.iotdb.confignode.manager.pipe.coordinator.PipeManager;
130131
import org.apache.iotdb.confignode.manager.schema.ClusterSchemaManager;
@@ -1836,6 +1837,10 @@ public TSStatus setConfiguration(TSetConfigurationReq req) {
18361837
long previousHeartbeatIntervalInMs = CONF.getHeartbeatIntervalInMs();
18371838
int previousSchemaRegionPerDataNode = CONF.getSchemaRegionPerDataNode();
18381839
int previousDataRegionPerDataNode = CONF.getDataRegionPerDataNode();
1840+
RegionGroupExtensionPolicy previousSchemaRegionGroupExtensionPolicy =
1841+
CONF.getSchemaRegionGroupExtensionPolicy();
1842+
RegionGroupExtensionPolicy previousDataRegionGroupExtensionPolicy =
1843+
CONF.getDataRegionGroupExtensionPolicy();
18391844
boolean wasTopologyProbingEnabled = CONF.isEnableTopologyProbing();
18401845
int previousProcedureCompletedCleanInterval = CONF.getProcedureCompletedCleanInterval();
18411846
int previousProcedureCompletedEvictTTL = CONF.getProcedureCompletedEvictTTL();
@@ -1866,8 +1871,11 @@ public TSStatus setConfiguration(TSetConfigurationReq req) {
18661871
return tsStatus;
18671872
}
18681873
handleHeartbeatIntervalHotReload(previousHeartbeatIntervalInMs);
1869-
handleRegionPerDataNodeHotReload(
1870-
previousSchemaRegionPerDataNode, previousDataRegionPerDataNode);
1874+
handleRegionGroupConfigHotReload(
1875+
previousSchemaRegionPerDataNode,
1876+
previousDataRegionPerDataNode,
1877+
previousSchemaRegionGroupExtensionPolicy,
1878+
previousDataRegionGroupExtensionPolicy);
18711879
handleTopologyProbingHotReload(wasTopologyProbingEnabled);
18721880
handleProcedureCleanerHotReload(
18731881
previousProcedureCompletedCleanInterval, previousProcedureCompletedEvictTTL);
@@ -1914,16 +1922,31 @@ private void handleHeartbeatIntervalHotReload(long previousHeartbeatIntervalInMs
19141922
getRetryFailedTasksThread().reloadHeartbeatInterval();
19151923
}
19161924

1917-
private void handleRegionPerDataNodeHotReload(
1918-
int previousSchemaRegionPerDataNode, int previousDataRegionPerDataNode) {
1925+
private void handleRegionGroupConfigHotReload(
1926+
int previousSchemaRegionPerDataNode,
1927+
int previousDataRegionPerDataNode,
1928+
RegionGroupExtensionPolicy previousSchemaRegionGroupExtensionPolicy,
1929+
RegionGroupExtensionPolicy previousDataRegionGroupExtensionPolicy) {
19191930
if (previousSchemaRegionPerDataNode == CONF.getSchemaRegionPerDataNode()
1920-
&& previousDataRegionPerDataNode == CONF.getDataRegionPerDataNode()) {
1931+
&& previousDataRegionPerDataNode == CONF.getDataRegionPerDataNode()
1932+
&& previousSchemaRegionGroupExtensionPolicy == CONF.getSchemaRegionGroupExtensionPolicy()
1933+
&& previousDataRegionGroupExtensionPolicy == CONF.getDataRegionGroupExtensionPolicy()) {
19211934
return;
19221935
}
19231936
if (!getConsensusManager().isLeader()) {
19241937
return;
19251938
}
1939+
// Leaving CUSTOM must replace its stored cap with the resource-derived cap, even when
1940+
// per-node quotas are unchanged. This preserves existing groups and skips types still CUSTOM.
19261941
getClusterSchemaManager().adjustMaxRegionGroupNum();
1942+
if (previousDataRegionGroupExtensionPolicy != RegionGroupExtensionPolicy.PROACTIVE
1943+
&& CONF.getDataRegionGroupExtensionPolicy() == RegionGroupExtensionPolicy.PROACTIVE) {
1944+
// Existing groups may already meet the new target, so no creation procedure would rebalance
1945+
// their slot assignments. Refresh the policy table for future time partitions on this leader.
1946+
getClusterSchemaManager()
1947+
.getDatabaseNames(null)
1948+
.forEach(database -> getLoadManager().reBalanceDataPartitionPolicy(database));
1949+
}
19271950
}
19281951

19291952
private void handleTopologyProbingHotReload(boolean wasEnabled) {

‎iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/load/balancer/partition/DataPartitionPolicyTable.java‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.apache.iotdb.confignode.conf.ConfigNodeConfig;
2626
import org.apache.iotdb.confignode.conf.ConfigNodeDescriptor;
2727
import org.apache.iotdb.confignode.i18n.ManagerMessages;
28+
import org.apache.iotdb.confignode.manager.partition.RegionGroupExtensionPolicy;
2829

2930
import org.slf4j.Logger;
3031
import org.slf4j.LoggerFactory;
@@ -34,6 +35,7 @@
3435
import java.util.HashMap;
3536
import java.util.List;
3637
import java.util.Map;
38+
import java.util.Objects;
3739
import java.util.concurrent.locks.ReentrantLock;
3840

3941
public class DataPartitionPolicyTable {
@@ -111,7 +113,7 @@ public void reBalanceDataPartitionPolicy(List<TConsensusGroupId> dataRegionGroup
111113
}
112114
Collections.shuffle(seriesPartitionSlots);
113115

114-
int mu = SERIES_SLOT_NUM / dataRegionGroups.size();
116+
int mu = getRetainedSlotLimit(dataAllotMap, dataRegionGroups.size());
115117
for (TSeriesPartitionSlot seriesPartitionSlot : seriesPartitionSlots) {
116118
if (!dataAllotMap.containsKey(seriesPartitionSlot)) {
117119
// Skip unallocated SeriesPartitionSlot
@@ -141,7 +143,7 @@ public void setDataAllotMap(Map<TSeriesPartitionSlot, TConsensusGroupId> dataAll
141143
}
142144
dataAllotTableLock.lock();
143145
try {
144-
int mu = SERIES_SLOT_NUM / seriesPartitionSlotCounter.size();
146+
int mu = getRetainedSlotLimit(dataAllotMap, seriesPartitionSlotCounter.size());
145147
dataAllotMap.forEach(
146148
(seriesPartitionSlot, regionGroupId) -> {
147149
if (regionGroupId != null && seriesPartitionSlotCounter.get(regionGroupId) < mu) {
@@ -158,6 +160,18 @@ public void setDataAllotMap(Map<TSeriesPartitionSlot, TConsensusGroupId> dataAll
158160
}
159161
}
160162

163+
private int getRetainedSlotLimit(
164+
Map<TSeriesPartitionSlot, TConsensusGroupId> assignments, int regionGroupCount) {
165+
if (CONF.getDataRegionGroupExtensionPolicy() == RegionGroupExtensionPolicy.PROACTIVE) {
166+
// Empty DataPartition entries have no last group and must not inflate the recovery limit.
167+
long activeSlotCount = assignments.values().stream().filter(Objects::nonNull).count();
168+
// New groups are created before the pending slots are activated. Keep at least one slot
169+
// per existing group so that incremental growth does not discard balanced assignments.
170+
return Math.max(1, (int) (activeSlotCount / regionGroupCount));
171+
}
172+
return SERIES_SLOT_NUM / regionGroupCount;
173+
}
174+
161175
public void logDataAllotTable(String database) {
162176
seriesPartitionSlotCounter
163177
.keySet()

0 commit comments

Comments
 (0)