Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
Expand Down Expand Up @@ -465,8 +466,17 @@ private void setStorageMediumProperty(HashMap<String, String> partitionPropertie
partitionProperties.put(PropertyAnalyzer.PROPERTIES_STORAGE_COOLDOWN_TIME,
TimeUtils.longToTimeString(DataProperty.MAX_COOLDOWN_TIME_MS));
} else {
String cooldownTime = DynamicPartitionUtil.getPartitionRangeString(
// getPartitionRangeString returns a datetime string in the partition's timezone
// (e.g., "2026-04-13 00:00:00" in Europe/London), but analyzeDataProperty parses
// storage_cooldown_time using the FE server timezone. To avoid this mismatch,
// parse the string back with the partition timezone to get the correct UTC instant,
// then re-format it using TimeUtils.longToTimeString() which uses the FE timezone.
String partitionTzTime = DynamicPartitionUtil.getPartitionRangeString(
property, now, offset + hotPartitionNum, DynamicPartitionUtil.DATETIME_FORMAT);
LocalDateTime localDt = LocalDateTime.parse(partitionTzTime,
DateTimeFormatter.ofPattern(DynamicPartitionUtil.DATETIME_FORMAT));
ZonedDateTime cooldownZdt = localDt.atZone(property.getTimeZone().toZoneId());
String cooldownTime = TimeUtils.longToTimeString(cooldownZdt.toInstant().toEpochMilli());
partitionProperties.put(PropertyAnalyzer.PROPERTIES_STORAGE_MEDIUM, TStorageMedium.SSD.name());
partitionProperties.put(PropertyAnalyzer.PROPERTIES_STORAGE_COOLDOWN_TIME, cooldownTime);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1326,6 +1326,177 @@ public void testHotPartitionNumAbnormalMissSSD() throws Exception {
createTable(createOlapTblStmt);
}

/**
* Regression test: when dynamic_partition.time_zone differs from the FE server timezone,
* setStorageMediumProperty formats the cooldown time string in partition timezone but
* analyzeDataProperty parses it in FE timezone. This mismatch shifts the cooldown time
* by the timezone difference (up to ±15 hours), which can make it appear in the past.
*
* This test verifies the cooldown time equals the correct UTC epoch by independently
* computing the expected value, so it catches the bug regardless of what time the test runs.
*/
@Test
public void testHotPartitionCooldownTimeWithNonDefaultTimezone() throws Exception {
changeBeDisk(TStorageMedium.SSD);

Database testDb = Env.getCurrentInternalCatalog().getDbOrAnalysisException("test");

// Use UTC+0 timezone to maximize difference from a CST (UTC+8) FE.
// hot_partition_num=1: partition at offset=0 (today) is "hot" with cooldown at
// "tomorrow midnight in partition timezone". The bug would interpret this as
// "tomorrow midnight in FE timezone" — an 8-hour difference.
String createOlapTblStmt = "CREATE TABLE test.`hot_partition_tz_cooldown` (\n"
+ " `k1` date NULL COMMENT \"\",\n"
+ " `k2` int NULL COMMENT \"\"\n"
+ ") ENGINE=OLAP\n"
+ "PARTITION BY RANGE(`k1`)\n"
+ "()\n"
+ "DISTRIBUTED BY HASH(`k2`) BUCKETS 3\n"
+ "PROPERTIES (\n"
+ "\"replication_num\" = \"1\",\n"
+ "\"dynamic_partition.enable\" = \"true\",\n"
+ "\"dynamic_partition.start\" = \"-3\",\n"
+ "\"dynamic_partition.end\" = \"3\",\n"
+ "\"dynamic_partition.create_history_partition\" = \"true\",\n"
+ "\"dynamic_partition.time_unit\" = \"DAY\",\n"
+ "\"dynamic_partition.prefix\" = \"p\",\n"
+ "\"dynamic_partition.buckets\" = \"1\",\n"
+ "\"dynamic_partition.hot_partition_num\" = \"1\",\n"
+ "\"dynamic_partition.time_zone\" = \"+00:00\"\n"
+ ");";

createTable(createOlapTblStmt);

OlapTable tbl = (OlapTable) testDb.getTableOrAnalysisException("hot_partition_tz_cooldown");
RangePartitionInfo partitionInfo = (RangePartitionInfo) tbl.getPartitionInfo();
Map<Long, DataProperty> idToDataProperty = new TreeMap<>(partitionInfo.idToDataProperty);

// Independently compute the expected cooldown: "tomorrow midnight in UTC+0" as epoch ms.
// hot_partition_num=1 means the cooldown for idx=0 (today) is the start of idx=1 (tomorrow).
ZonedDateTime expectedCooldown = ZonedDateTime.now(ZoneId.of("+00:00"))
.plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
long expectedCooldownMs = expectedCooldown.toInstant().toEpochMilli();

boolean foundHotPartition = false;
for (DataProperty dp : idToDataProperty.values()) {
if (dp.getStorageMedium() == TStorageMedium.SSD
&& dp.getCooldownTimeMs() != DataProperty.MAX_COOLDOWN_TIME_MS) {
foundHotPartition = true;
long actualMs = dp.getCooldownTimeMs();
long diffHours = Math.abs(actualMs - expectedCooldownMs) / (3600 * 1000);
// Allow 1 hour tolerance for test execution time, but the timezone bug
// would cause an 8-hour shift (for CST) — this assertion catches it.
Assert.assertTrue(
"Cooldown time differs from expected by " + diffHours + " hours."
+ " Expected ~" + expectedCooldownMs + " (tomorrow midnight UTC+0)"
+ " but got " + actualMs
+ ". Likely timezone mismatch in setStorageMediumProperty.",
diffHours <= 1);
}
}
Assert.assertTrue("Should have at least one hot partition with non-MAX cooldown",
foundHotPartition);

// 2. Test HOUR granularity with America/Los_Angeles (UTC-7/-8).
// 15-16 hour difference from CST makes the bug even more visible.
createOlapTblStmt = "CREATE TABLE test.`hot_partition_tz_cooldown_hour` (\n"
+ " `k1` datetime NULL COMMENT \"\",\n"
+ " `k2` int NULL COMMENT \"\"\n"
+ ") ENGINE=OLAP\n"
+ "PARTITION BY RANGE(`k1`)\n"
+ "()\n"
+ "DISTRIBUTED BY HASH(`k2`) BUCKETS 3\n"
+ "PROPERTIES (\n"
+ "\"replication_num\" = \"1\",\n"
+ "\"dynamic_partition.enable\" = \"true\",\n"
+ "\"dynamic_partition.start\" = \"-3\",\n"
+ "\"dynamic_partition.end\" = \"3\",\n"
+ "\"dynamic_partition.create_history_partition\" = \"true\",\n"
+ "\"dynamic_partition.time_unit\" = \"HOUR\",\n"
+ "\"dynamic_partition.prefix\" = \"p\",\n"
+ "\"dynamic_partition.buckets\" = \"1\",\n"
+ "\"dynamic_partition.hot_partition_num\" = \"1\",\n"
+ "\"dynamic_partition.time_zone\" = \"America/Los_Angeles\"\n"
+ ");";

createTable(createOlapTblStmt);

tbl = (OlapTable) testDb.getTableOrAnalysisException("hot_partition_tz_cooldown_hour");
partitionInfo = (RangePartitionInfo) tbl.getPartitionInfo();
idToDataProperty = new TreeMap<>(partitionInfo.idToDataProperty);

ZoneId laZone = ZoneId.of("America/Los_Angeles");
ZonedDateTime expectedHourCooldown = ZonedDateTime.now(laZone)
.plusHours(1).withMinute(0).withSecond(0).withNano(0);
long expectedHourCooldownMs = expectedHourCooldown.toInstant().toEpochMilli();

foundHotPartition = false;
for (DataProperty dp : idToDataProperty.values()) {
if (dp.getStorageMedium() == TStorageMedium.SSD
&& dp.getCooldownTimeMs() != DataProperty.MAX_COOLDOWN_TIME_MS) {
foundHotPartition = true;
long actualMs = dp.getCooldownTimeMs();
long diffHours = Math.abs(actualMs - expectedHourCooldownMs) / (3600 * 1000);
Assert.assertTrue(
"HOUR cooldown time differs from expected by " + diffHours + " hours."
+ " Expected ~" + expectedHourCooldownMs
+ " but got " + actualMs,
diffHours <= 1);
}
}
Assert.assertTrue("Should have at least one hot partition with non-MAX cooldown (HOUR)",
foundHotPartition);

// 3. Verify that when partition timezone matches FE timezone, cooldown is still correct.
// This guards against regressions where the fix might break the same-timezone case.
createOlapTblStmt = "CREATE TABLE test.`hot_partition_tz_cooldown_same` (\n"
+ " `k1` date NULL COMMENT \"\",\n"
+ " `k2` int NULL COMMENT \"\"\n"
+ ") ENGINE=OLAP\n"
+ "PARTITION BY RANGE(`k1`)\n"
+ "()\n"
+ "DISTRIBUTED BY HASH(`k2`) BUCKETS 3\n"
+ "PROPERTIES (\n"
+ "\"replication_num\" = \"1\",\n"
+ "\"dynamic_partition.enable\" = \"true\",\n"
+ "\"dynamic_partition.start\" = \"-3\",\n"
+ "\"dynamic_partition.end\" = \"3\",\n"
+ "\"dynamic_partition.create_history_partition\" = \"true\",\n"
+ "\"dynamic_partition.time_unit\" = \"DAY\",\n"
+ "\"dynamic_partition.prefix\" = \"p\",\n"
+ "\"dynamic_partition.buckets\" = \"1\",\n"
+ "\"dynamic_partition.hot_partition_num\" = \"1\",\n"
+ "\"dynamic_partition.time_zone\" = \"" + java.util.TimeZone.getDefault().getID() + "\"\n"
+ ");";

createTable(createOlapTblStmt);

tbl = (OlapTable) testDb.getTableOrAnalysisException("hot_partition_tz_cooldown_same");
partitionInfo = (RangePartitionInfo) tbl.getPartitionInfo();
idToDataProperty = new TreeMap<>(partitionInfo.idToDataProperty);

ZonedDateTime expectedSameTzCooldown = ZonedDateTime.now()
.plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
long expectedSameTzMs = expectedSameTzCooldown.toInstant().toEpochMilli();

foundHotPartition = false;
for (DataProperty dp : idToDataProperty.values()) {
if (dp.getStorageMedium() == TStorageMedium.SSD
&& dp.getCooldownTimeMs() != DataProperty.MAX_COOLDOWN_TIME_MS) {
foundHotPartition = true;
long actualMs = dp.getCooldownTimeMs();
long diffHours = Math.abs(actualMs - expectedSameTzMs) / (3600 * 1000);
Assert.assertTrue(
"Same-timezone cooldown time differs from expected by " + diffHours + " hours."
+ " Expected ~" + expectedSameTzMs
+ " but got " + actualMs,
diffHours <= 1);
}
}
Assert.assertTrue("Should have at least one hot partition with non-MAX cooldown (same tz)",
foundHotPartition);
}

@Test
public void testRuntimeInfo() throws Exception {
DynamicPartitionScheduler scheduler = new DynamicPartitionScheduler("test", 10);
Expand Down
Loading