englefly commented on code in PR #68282:
URL: https://github.com/apache/doris/pull/68282#discussion_r4254643900


##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/AnalysisManager.java:
##########
@@ -1487,6 +1487,75 @@ public void removeTableStats(long tableId) {
         }
     }
 
+    /**
+     * Remove the statistics record of a table and write the journal entry of 
the removal while holding the
+     * monitor of the statistics records, so that this transition and a 
concurrent truncation, which writes
+     * its own entry under the same monitor, are ordered the same way in 
memory and in the journal. Replay
+     * paths, which must not journal, use {@link #removeTableStats(long)}.
+     */
+    public void removeTableStatsAndLog(long tableId) {
+        synchronized (idToTblStats) {
+            idToTblStats.remove(tableId);
+            Env.getCurrentEnv().getEditLog().logDeleteTableStats(new 
TableStatsDeletionLog(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.
+     *
+     * <p>The reset and the journal entry of the truncation are written while 
holding the monitor of the
+     * statistics records, and so is the deletion of a record, so that the 
order of two concurrent
+     * transitions between the states of a record is the same in memory and in 
the journal: otherwise a
+     * truncation and a deletion which do not share a table lock could be 
applied in one order and replayed
+     * in the other, leaving the followers with a record the master removed 
(or the reverse).
+     *
+     * @return whether the record had to be created, i.e. the table had no 
statistics record
+     */
+    public boolean resetTableStats(OlapTable table, TruncateTableInfo 
truncateInfo) {
+        synchronized (idToTblStats) {
+            TableStatsMeta tableStats = idToTblStats.get(table.getId());
+            boolean recordCreated = false;
+            if (tableStats == null) {
+                tableStats = new TableStatsMeta(table);
+                idToTblStats.put(table.getId(), tableStats);
+                recordCreated = true;
+            }
+            // The entry records what this truncation did to the record, so 
that the replay reproduces it.
+            truncateInfo.setTableStatsRecordCreated(recordCreated);
+            tableStats.reset(table);
+            Env.getCurrentEnv().getEditLog().logTruncateTable(truncateInfo);
+            return recordCreated;
+        }
+    }
+
+    /**
+     * Apply the transition which the given truncate entry recorded, instead 
of the one this frontend would
+     * apply now.
+     *
+     * <p>A truncate creates the statistics record of a table which has none, 
and that creation is not a
+     * journal entry of its own. The truncate entry therefore carries whether 
the truncate created the record,
+     * so that the replay reproduces exactly what the master did and a 
truncate entry never resurrects a
+     * record which another journaled transition, for instance a whole table 
DROP STATS or the cleanup of an
+     * empty table by the analyzer, removed around it.
+     */
+    public void replayResetTableStats(OlapTable table, boolean recordCreated) {

Review Comment:
   Fixed in `ae01164abcd` by removing the flag - your question is the reason it 
can go.
   
   Its purpose: the truncate entry recorded whether the truncation had created 
the record, because a truncate creates the record of a table which has none and 
that creation is not a journal entry of its own. The replay used it so that a 
truncate entry could not resurrect a record which a concurrent journaled 
deletion (a whole table `DROP STATS`, the analyzer's cleanup of an empty table) 
had removed. Back then a record transition and its entry were not written 
atomically, so the master could end up without the record while the entries 
implied the opposite.
   
   Since every transition and its entry are written under the same 
`idToTblStats` monitor, the journal order is the mutation order and the entries 
are replayed in that order. At a truncate entry the record state is therefore 
the state the master had when it truncated the table, so the answer to 
`tableStats == null` is simply "the master had no record either and created 
one": `replayResetTableStats()` now creates the record when it is absent and 
resets it otherwise, which is exactly the transition the truncation applied on 
the master.
   
   The combination you asked about (`tableStats == null && recordCreated == 
false`) now only occurs for entries written before the field existed; those get 
the semantics of this change when they are replayed, i.e. the record is 
created, which is what this PR wants: the record has to exist for the rows 
loaded after the truncation to be reported.
   
   `TruncateTableInfo` no longer carries the field (nor its accessors), 
`resetTableStats()` no longer returns it, and 
`AnalysisManagerTest#testReplayOfTruncateFollowsTheJournalOrder` covers the two 
orders: an existing record is reset (its accumulated rows are dropped), an 
absent one is created with `updatedRows = 0`, and a removal replayed afterwards 
removes it again. Verified live as well: after a hard kill and a restart of the 
frontend, the replay recreated the record of a truncated table with its 3 
loaded rows, including a table which had never been analyzed.
   



-- 
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