github-actions[bot] commented on code in PR #68282:
URL: https://github.com/apache/doris/pull/68282#discussion_r4063317594


##########
fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java:
##########
@@ -3904,7 +3904,7 @@ public void truncateTable(String dbName, String 
tableName, PartitionNamesInfo pa
             oldPartitions = truncateTableInternal(olapTable, newPartitions,
                     truncateEntireTable, recyclePartitionParamMap, forceDrop, 
version, versionTimeMs);
             if (truncateEntireTable) {
-                
Env.getCurrentEnv().getAnalysisManager().removeTableStats(olapTable.getId());
+                
Env.getCurrentEnv().getAnalysisManager().resetTableStats(olapTable);

Review Comment:
   `replayTruncateTable` is now creating/resetting the stats record, but 
`Env.replayTruncateTable` still removes it immediately after this method 
returns (`Env.java:6770-6772`). On a follower or after checkpoint recovery the 
record is therefore absent, so subsequent loads cannot accumulate their deltas 
and the reported row count falls back to unknown until another analysis creates 
stats. Please remove/condition that outer removal while preserving 
compatibility for legacy truncate journals that did not emit the new reset 
entry.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/InternalCatalog.java:
##########
@@ -3904,7 +3904,7 @@ public void truncateTable(String dbName, String 
tableName, PartitionNamesInfo pa
             oldPartitions = truncateTableInternal(olapTable, newPartitions,
                     truncateEntireTable, recyclePartitionParamMap, forceDrop, 
version, versionTimeMs);
             if (truncateEntireTable) {
-                
Env.getCurrentEnv().getAnalysisManager().removeTableStats(olapTable.getId());
+                
Env.getCurrentEnv().getAnalysisManager().resetTableStats(olapTable);

Review Comment:
   Resetting the table-wide counter here does not fence deltas already owned by 
a load that started before the truncate. If that load publishes after the 
truncate, `DatabaseTransactionMgr.finishCheckPartitionVersion` drops its old 
partition commit entries but `updateCatalogAfterVisible` still forwards the 
transaction's old tablet-delta map; `AnalysisManager.replayUpdateRowsRecord` 
sums it by table ID with no generation check. Those discarded rows are then 
reported as new post-truncate rows. Please filter deltas for removed 
partitions/tablets (or add a truncate generation fence) before updating this 
record.



##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/TableStatsMeta.java:
##########
@@ -130,6 +137,58 @@ public TableStatsMeta(long rowCount, AnalysisInfo 
analyzedJob, TableIf table) {
         update(analyzedJob, table);
     }
 
+    /**
+     * Create a record for a table which doesn't have one yet, in the state of 
an empty table. The rows
+     * loaded into the table are accumulated by {@link 
AnalysisManager#replayUpdateRowsRecord}, so a record
+     * has to exist before the first load, otherwise these rows can never be 
turned into a row count.
+     */
+    public TableStatsMeta(OlapTable table) {
+        this.ctlId = table.getDatabase().getCatalog().getId();
+        this.ctlName = table.getDatabase().getCatalog().getName();
+        this.dbId = table.getDatabase().getId();
+        this.dbName = table.getDatabase().getFullName();
+        this.tblId = table.getId();
+        this.tblName = table.getName();
+        this.idxId = -1;
+        this.indexesRowCount = buildEmptyIndexRowCount(table);

Review Comment:
   This reset is not protected from stale asynchronous producers. An analyze 
job started before the truncate can finish afterward and 
`AnalysisManager.updateTableStats` will repopulate this same `TableStatsMeta` 
with its pre-truncate row/index/column baseline and journal that stale state, 
because neither path checks a truncate epoch/table version. The `DropStatsTask` 
scheduled after truncate also captures this record and can clear stats set by a 
new `ALTER ... SET STATS` if it runs late. Please cancel/fence pre-reset work 
and make cleanup/update operations generation-aware.



##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/TableStatsMeta.java:
##########
@@ -105,6 +105,13 @@ public class TableStatsMeta implements Writable, 
GsonPostProcessable {
     @SerializedName("irc")
     private ConcurrentMap<Long, Long> indexesRowCount = new 
ConcurrentHashMap<>();
 
+    // The value of updatedRows when indexesRowCount was collected, i.e. the 
number of rows the collected
+    // row count already includes. The rows loaded after that point are the 
delta row count of the table.
+    // It is kept here, and not derived from colToColStatsMeta, so that 
dropping the column statistics of
+    // the table doesn't lose it. -1 means no row count has ever been 
collected from the table.
+    @SerializedName("updatedRowsBase")

Review Comment:
   `updatedRowsBase` is table-wide, but this assignment runs even when the job 
did not collect the base-index row count. For example, an MV-only analysis 
updates only the MV entry, and a named-partition analysis can finish without 
populating `indexesRowCount`; after 50 rows load, the stored base index remains 
100 while `updatedRowsBase` becomes 150, so the unknown-BE fallback returns 
`100 + (150-150) = 100` instead of 150. Track the baseline per index or advance 
the base-index baseline only when it was actually refreshed.



##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/AnalysisManager.java:
##########
@@ -1487,6 +1487,38 @@ public void removeTableStats(long tableId) {
         }
     }
 
+    /**
+     * TRUNCATE TABLE removes all the data of the table, but the table itself 
stays and can be loaded
+     * immediately. The stats record must be kept, otherwise the row count of 
the newly loaded data can
+     * never be reported: the backends report the row count of the new empty 
tablets with a delay of up to
+     * {@code tablet_stat_update_interval_second}, and without a record there 
is nothing to accumulate the
+     * loaded rows into. So reset the record to the state of an empty table 
instead of removing it.
+     */
+    public void resetTableStats(OlapTable table) {
+        // Keep the followers and the checkpoint image consistent with the 
master.
+        logCreateTableStats(resetTableStatsInternal(table));

Review Comment:
   This call writes a separate `OP_UPDATE_TABLE_STATS` entry before 
`InternalCatalog` writes the `OP_TRUNCATE_TABLE` entry. `EditLog.logEdit` 
explicitly has no cross-entry atomicity, so a FE crash after this write but 
before the truncate journal is durable replays a zeroed stats record onto the 
old partitions/data. The next planner fallback can therefore undercount 
existing rows until reanalysis. Persist the reset as part of the truncate 
operation or make recovery ignore/roll back an unpaired reset.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to