englefly opened a new pull request, #69028:
URL: https://github.com/apache/doris/pull/69028

   An alternative implementation of the fix discussed in #68282, with a much 
smaller change: instead of keeping
   and resetting the statistics record across `TRUNCATE TABLE` (which also had 
to order every record transition
   with its journal entry), the record is recreated by the load itself, so a 
truncation keeps removing it exactly
   as it does today.
   
   ### What problem does this PR solve?
   
   Issue Number: None (no upstream issue filed; the case is described below)
   
   Problem Summary:
   
   Reproduction on a single bucket duplicate key table:
   
       CREATE TABLE t (k INT, v INT) DUPLICATE KEY(k) DISTRIBUTED BY HASH(k) 
BUCKETS 1;
       INSERT INTO t VALUES (1,1),(2,2),(3,3),(4,4),(5,5);
       TRUNCATE TABLE t;
       INSERT INTO t VALUES (1,1),(2,2),(3,3);
       EXPLAIN SELECT * FROM t;   -- reports cardinality=1, although the table 
holds 3 rows
   
   TRUNCATE TABLE replaces the partitions and tablets of the table with new 
ones and removes its statistics
   record. The backends report the row count of the new tablets only after the 
delay of
   `tablet_stat_update_interval_second` (60s by default), and a partition 
reports no row count at all until its
   first load becomes visible (`DatabaseTransactionMgr` sets `rowCountReported` 
to false for the first
   transaction of a partition), so during that interval 
`OlapTable.getRowCountForIndex(..., strict = true)`
   returns -1 for the whole table. The only other source of a row count is the 
statistics record of the table,
   and the truncation had just removed it, so the planner clamps the table to 
one row: joins are ordered and
   distributed for a table that looks empty instead of for the rows that were 
just loaded.
   
   The load now recreates the record itself. While the loading transaction 
becomes visible,
   `AnalysisManager.replayUpdateRowsRecord()` creates the record of a table 
which has none in the state of a
   table whose row count has never been collected, and accumulates the rows it 
reports into it, so the planner
   reads them instead of -1. The record is created with a baseline of 0 
(`updatedRowsBase`) and a collected row
   count of 0 for the base index, so every row loaded afterwards is a delta row 
and the reported row count is
   the number of rows the table holds.
   
   The creation happens in `updateUpdatedRows()`, which is the path the master 
walks when the loading
   transaction becomes visible 
(`DatabaseTransactionMgr.updateCatalogAfterVisible`) and the path a follower
   walks while it replays the transaction state 
(`replayUpsertTransactionState`), so every frontend ends up with
   the record. It needs no edit log entry of its own and does not touch the 
transaction state machine: the
   record is a function of the transaction state the journal already carries. 
The whole table `DROP STATS`, the
   schema changes and the truncation keep removing the record as before, and 
the next load recreates it. A
   partition truncation is deliberately left out: the rows it reports are the 
rows it removed, so it does not
   create a record.
   
   Two related fixes come with it, because the record is now the row count 
source in more cases:
   - the delta is only added to an index which keeps one row per base row
     (`TableStatsMeta.keepsOneRowPerBaseRow`): an aggregating rollup, an index 
which merges the rows of a unique
     key table and a filtered materialized index have their own row count and 
must not be reported with the
     physical rows of the base index, and a row count which was never collected 
is not combined with a delta;
   - `StatisticsMetricCollector` decided "not analyzed" by the absence of a 
record. A record now exists for a
     loaded table which was never analyzed, so it decides by the analyze 
timestamp instead.
   
   ### Known limitation
   
   A record which is created for a table that already holds rows, for instance 
right after a whole table
   `DROP STATS` or after a schema change (the truncation case starts from an 
empty table), only knows the rows
   loaded after that point, so the table is estimated with them until the 
backends report the tablets or an
   analysis collects the row count. It is still closer than the unknown row 
count it replaces: a merge key
   table reports the physical row count in that window, which is an upper bound 
of its logical row count, as it
   does in the steady state today.
   
   ### Release note
   
   None
   
   ### Check List (For Author)
   
   - Test: new unit tests in `AnalysisManagerTest` (a load creates the record 
and accumulates its rows, a load
     which reports the rows by table id creates it as well, a replayed load 
creates the same record, an existing
     analyzed record is only accumulated, and an unresolvable table creates 
none) and in `TableStatsMetaTest`
     (the record of a table whose row count was never collected reports the 
loaded rows; the baseline survives
     dropping the column statistics; a legacy record derives it from the column 
statistics; a stale analyze
     snapshot and its baseline cancel out; a user injected row count is not 
counted twice; the reader of the
     snapshot never pairs two states; a row preserving rollup reports the 
loaded rows while an aggregating one
     and a filtered one do not). `AnalysisManagerTest` 21/21, 
`TableStatsMetaTest` 12/12 (checkstyle not
     skipped). Regression: the new suite `test_row_count_after_truncate` (the 
truncate case, an aggregate rollup
     and a projection rollup) and the existing `test_drop_stats_and_truncate` 
both pass. Manual test on a local
     FE and BE cluster with the reproduction above: the scan reports 
cardinality=3 right after the load instead
     of 1, a never analyzed table which receives 4 rows reports 4 instead of 1, 
and `SHOW TABLE STATS` shows the
     accumulated rows with an empty analyze time.
   - Behavior changed: Yes. A table whose statistics record was removed reports 
the rows its loads accumulate
     instead of an unknown row count (planned as one row) while the backends 
have not reported the new tablets
     yet; the "not analyzed" metric counts a table by its analyze timestamp.
   - Does this need documentation: No
   
   


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