From 2805f4880cb01f2b963816f14dc8de2558cefa5e Mon Sep 17 00:00:00 2001 From: seawinde Date: Mon, 10 Aug 2026 09:53:45 +0800 Subject: [PATCH] [fix](fe) Keep table version monotonic after truncate (#66255) MTMV snapshots for non-PCT base tables compare `(tableId, tableVersion)`. `TRUNCATE` keeps the table ID but reset the non-Cloud table version to 1, so later writes could reuse a previously recorded snapshot and cause AUTO refresh to miss changed data. **Root cause:** `InternalCatalog.truncateTableInternal()` replaced the old partitions and then called `OlapTable.resetVisibleVersion()`. A sequence such as version 3 -> TRUNCATE to 1 -> two writes to version 3 creates an ABA collision for table-level snapshot consumers. **Change Summary:** | File | Change Description | |------|--------------------| | `InternalCatalog.java` | Increment the non-Cloud table version once after whole-table or partition TRUNCATE; Cloud remains Meta Service-managed | | `OlapTable.java`, `TableAttributes.java` | Remove the TRUNCATE-only version reset helpers | | `TruncateTableCommandTest.java` | Verify whole-table and partition TRUNCATE each advance the table version once | | `test_truncate_table_mtmv.groovy` | Reproduce the non-PCT table snapshot ABA while preserving the existing expected output | | `truncate_version_reset.groovy` | Keep the SimpleAggCache regression description consistent with monotonic versions | **Design Rationale:** The table version represents table-level data changes, while PCT refresh separately compares partition ID and partition version. Reusing the existing table version avoids new persisted flags or MTMV-specific invalidation state. Live execution and journal replay use the same locked path. Cloud is skipped locally because `commit_partition` already advances and returns the Meta Service table version. --- .../doris/datasource/InternalCatalog.java | 22 ++++++--- .../doris/persist/TruncateTableInfo.java | 16 ++++++- .../commands/TruncateTableCommandTest.java | 5 ++ .../doris/persist/TruncateTableInfoTest.java | 46 +++++++++++++++++++ .../data/mtmv_p0/test_truncate_table_mtmv.out | 4 ++ .../mtmv_p0/test_truncate_table_mtmv.groovy | 30 +++++++++++- .../truncate_version_reset.groovy | 32 ++----------- 7 files changed, 117 insertions(+), 38 deletions(-) create mode 100644 fe/fe-core/src/test/java/org/apache/doris/persist/TruncateTableInfoTest.java diff --git a/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java b/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java index b9300bc058ee66..147a60d181e978 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java +++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java @@ -3814,8 +3814,10 @@ public void truncateTable(String dbName, String tableName, PartitionNamesInfo pa //replace Map recyclePartitionParamMap = new HashMap<>(); + long version = Config.isNotCloudMode() ? olapTable.getNextVersion() : 0L; + long versionTimeMs = Config.isNotCloudMode() ? System.currentTimeMillis() : 0L; oldPartitions = truncateTableInternal(olapTable, newPartitions, - truncateEntireTable, recyclePartitionParamMap, forceDrop); + truncateEntireTable, recyclePartitionParamMap, forceDrop, version, versionTimeMs); if (truncateEntireTable) { Env.getCurrentEnv().getAnalysisManager().removeTableStats(olapTable.getId()); } else { @@ -3827,7 +3829,7 @@ public void truncateTable(String dbName, String tableName, PartitionNamesInfo pa TruncateTableInfo info = new TruncateTableInfo(db.getId(), db.getFullName(), olapTable.getId(), olapTable.getName(), newPartitions, truncateEntireTable, - rawTruncateSql, oldPartitions, forceDrop, updateRecords); + rawTruncateSql, oldPartitions, forceDrop, updateRecords, version, versionTimeMs); Env.getCurrentEnv().getEditLog().logTruncateTable(info); } catch (DdlException e) { failedCleanCallback.run(); @@ -3843,7 +3845,8 @@ public void truncateTable(String dbName, String tableName, PartitionNamesInfo pa } private List truncateTableInternal(OlapTable olapTable, List newPartitions, - boolean isEntireTable, Map recyclePartitionParamMap, boolean isforceDrop) { + boolean isEntireTable, Map recyclePartitionParamMap, boolean isforceDrop, + long version, long versionTimeMs) { // use new partitions to replace the old ones. List oldPartitions = Lists.newArrayList(); for (Partition newPartition : newPartitions) { @@ -3873,9 +3876,13 @@ private List truncateTableInternal(OlapTable olapTable, List 0) { + // Persisted values make the version transition deterministic during journal replay. + olapTable.updateVisibleVersionAndTime(version, versionTimeMs); + } else { + // Preserve legacy replay and Cloud's local cache invalidation behavior. + olapTable.resetVisibleVersion(); + } return oldPartitions; } @@ -3889,7 +3896,8 @@ public void replayTruncateTable(TruncateTableInfo info) throws MetaNotFoundExcep try { Map recyclePartitionParamMap = new HashMap<>(); truncateTableInternal(olapTable, info.getPartitions(), info.isEntireTable(), - recyclePartitionParamMap, isForceDrop); + recyclePartitionParamMap, isForceDrop, + info.getVersion(), info.getVersionTimeMs()); // add tablet to inverted index TabletInvertedIndex invertedIndex = Env.getCurrentInvertedIndex(); diff --git a/fe/fe-core/src/main/java/org/apache/doris/persist/TruncateTableInfo.java b/fe/fe-core/src/main/java/org/apache/doris/persist/TruncateTableInfo.java index b846d1acbdc1a9..7edffceaf0be2c 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/persist/TruncateTableInfo.java +++ b/fe/fe-core/src/main/java/org/apache/doris/persist/TruncateTableInfo.java @@ -60,6 +60,10 @@ public class TruncateTableInfo implements Writable { private Map updateRecords; @SerializedName(value = "ut") private long updateTime; + @SerializedName(value = "version") + private long version; + @SerializedName(value = "versionTime") + private long versionTimeMs; public TruncateTableInfo() { @@ -68,7 +72,7 @@ public TruncateTableInfo() { // for internal table public TruncateTableInfo(long dbId, String db, long tblId, String table, List partitions, boolean isEntireTable, String rawSql, List oldPartitions, boolean force, - Map updateRecords) { + Map updateRecords, long version, long versionTimeMs) { this.dbId = dbId; this.db = db; this.tblId = tblId; @@ -81,6 +85,8 @@ public TruncateTableInfo(long dbId, String db, long tblId, String table, List