You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by "englefly (via GitHub)" <gi...@apache.org> on 2026/10/13 12:34:05 UTC

[PR] [fix](fe) Create the table stats record when a load finishes [doris]

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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6163362959

   # FE Regression Coverage Report
   Increment line coverage `  36.93% (89/241)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/69028_e2ce99b43fe1332fbad22f00c5019273a29bf94c_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/69028_e2ce99b43fe1332fbad22f00c5019273a29bf94c_merge_fe/report/index.html)
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4355140831


##########
fe/fe-core/src/main/java/org/apache/doris/statistics/repository/StatisticsCleaner.java:
##########
@@ -140,6 +140,8 @@ private void clearTableStats() {
                 }
                 LOG.info("Table {}.{}.{} with id {} not exist, remove its table stats record.",
                         stats.ctlName, stats.dbName, stats.tblName, id);
+                // No table lock: the table is gone, so no load can recreate the record of this id while the

Review Comment:
   [P2] Order cleaner deletion with recovery of the same table ID
   
   `RECOVER TABLE` re-registers the recycled `Table` with its original ID, so the absence observed here does not prevent load-driven recreation. The cleaner can capture `stats` before DROP, observe the dropped table, remove its record, and pause before journaling DELETE. Run `RECOVER TABLE t; TRUNCATE TABLE t; INSERT ...` with N rows, then resume the cleaner: the master retains the load-created record, while replay applies RECOVER -> TRUNCATE -> VISIBLE (create N) -> DELETE (remove). With the replacement tablets still unreported, `Project(*) -> OlapScan(t)` consequently estimates N on the master but 1 after replay/failover. The old get-only load path left both records absent. This is a separate remover from the existing DROP STATS/schema threads. Serialize the cleaner's absence check and removal/journal with recovery/load publication, and cover this interleaving with barriers.



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4312382722


##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/TableStatsMeta.java:
##########
@@ -194,6 +273,21 @@ public void update(AnalysisInfo analyzedJob, TableIf tableIf) {
         if (tableIf != null) {
             if (tableIf instanceof OlapTable) {
                 OlapTable olapTable = (OlapTable) tableIf;
+                // The collected row counts published below already include the rows which had been loaded when
+                // the job was built, remember how many they were, they are not delta rows. The baseline may
+                // only advance together with the collected base index row count, an analysis of another index
+                // (a materialized view) doesn't touch it.
+                // NOTICE: the baseline is published before the collected counts. They are both read without
+                // the table lock by the planner, and a reader which pairs the collected count of this analysis
+                // with the baseline of the previous one counts the rows loaded since that previous analysis
+                // twice.
+                if (analyzedJob.indexesRowCount.containsKey(olapTable.getBaseIndexId())) {
+                    // A row count supplied by the user replaces the collected one and describes the table as
+                    // of now, so the rows loaded so far are part of it and the baseline has to move to the
+                    // current value. Keeping the older baseline would count those rows again once DROP STATS
+                    // clears userInjected.
+                    updatedRowsBase.set(analyzedJob.userInject ? updatedRows.get() : analyzedJob.updateRows);

Review Comment:
   Agreed that the two values describe different snapshots, but this one is pre-existing rather than a regression from this change: before the record-creation work the baseline was derived from `ColStatsMeta.updatedRows`, which is also `analyzedJob.updateRows`, captured in `AnalysisManager#buildAnalysisInfoForTbl` (line 428) when the job is built, while the count comes from the task's `COUNT(1)` executed later. `updatedRowsBase` keeps exactly the same capture point, so master computes the same wrong estimate (`150 + (150 - 100) = 200`).
   
   Tracked separately in #69281. The fix is to capture the load counter when the analyze query collects the row count (after the query, so the residual race biases to an under-count instead of a double count) and use it as the baseline, plus a deterministic "build job -> load -> count" test. The test added here only covers a stale snapshot delivered after the record was recreated, where the baseline and the collected count cancel out.
   



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4309991283


##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/AnalysisManager.java:
##########
@@ -1366,52 +1405,52 @@ public void replayUpdateRowsRecord(UpdateRowsEvent event) {
         // Record : TableId -> (TabletId -> update rows)
         if (event.getTabletRecords() != null) {
             for (Entry<Long, Map<Long, Long>> record : event.getTabletRecords().entrySet()) {
-                TableStatsMeta statsStatus = idToTblStats.get(record.getKey());
-                if (statsStatus != null) {
-                    Optional<Database> dbOption = catalog.getDb(event.getDbId());
-                    if (!dbOption.isPresent()) {
-                        LOG.warn("Database {} does not exist.", event.getDbId());
-                        continue;
-                    }
-                    Database database = dbOption.get();
-                    Optional<Table> tableOption = database.getTable(record.getKey());
-                    if (!tableOption.isPresent()) {
-                        LOG.warn("Table {} does not exist in DB {}.", record.getKey(), event.getDbId());
-                        continue;
-                    }
-                    Table table = tableOption.get();
-                    if (!(table instanceof OlapTable)) {
-                        continue;
-                    }
-                    OlapTable olapTable = (OlapTable) table;
-                    short replicaNum = olapTable.getTableProperty().getReplicaAllocation().getTotalReplicaNum();
-                    Map<Long, Long> tabletRows = record.getValue();
-                    if (tabletRows == null || tabletRows.isEmpty()) {
-                        LOG.info("Tablet row count map is empty");
-                        continue;
-                    }
-                    long rowsForAllReplica = 0;
-                    for (Entry<Long, Long> entry : tabletRows.entrySet()) {
-                        rowsForAllReplica += entry.getValue();
-                        if (LOG.isDebugEnabled()) {
-                            LOG.debug("Table id {}, tablet id {}, row count {}",
-                                    record.getKey(), entry.getKey(), entry.getValue());
-                        }
-                    }
-                    long tableUpdateRows = rowsForAllReplica / replicaNum;
-                    LOG.info("Update rows for table {} is {}, replicaNum is {}, "
-                            + "rows for all replica {}, tablets count {}",
-                            olapTable.getName(), tableUpdateRows, replicaNum, rowsForAllReplica, tabletRows.size());
-                    statsStatus.updatedRows.addAndGet(tableUpdateRows);
-                    if (StatisticsUtil.enablePartitionAnalyze()) {
-                        updatePartitionRows(olapTable, tabletRows, statsStatus, replicaNum);
+                Optional<Database> dbOption = catalog.getDb(event.getDbId());
+                if (!dbOption.isPresent()) {
+                    LOG.warn("Database {} does not exist.", event.getDbId());
+                    continue;
+                }
+                Database database = dbOption.get();
+                Optional<Table> tableOption = database.getTable(record.getKey());
+                if (!tableOption.isPresent()) {
+                    LOG.warn("Table {} does not exist in DB {}.", record.getKey(), event.getDbId());
+                    continue;
+                }
+                Table table = tableOption.get();
+                if (!(table instanceof OlapTable)) {
+                    continue;
+                }
+                OlapTable olapTable = (OlapTable) table;
+                TableStatsMeta statsStatus = findOrCreateTableStats(olapTable);
+                short replicaNum = olapTable.getTableProperty().getReplicaAllocation().getTotalReplicaNum();
+                Map<Long, Long> tabletRows = record.getValue();
+                if (tabletRows == null || tabletRows.isEmpty()) {
+                    LOG.info("Tablet row count map is empty");
+                    continue;
+                }
+                long rowsForAllReplica = 0;
+                for (Entry<Long, Long> entry : tabletRows.entrySet()) {
+                    rowsForAllReplica += entry.getValue();
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug("Table id {}, tablet id {}, row count {}",
+                                record.getKey(), entry.getKey(), entry.getValue());
                     }
                 }
+                long tableUpdateRows = rowsForAllReplica / replicaNum;
+                LOG.info("Update rows for table {} is {}, replicaNum is {}, "
+                        + "rows for all replica {}, tablets count {}",
+                        olapTable.getName(), tableUpdateRows, replicaNum, rowsForAllReplica, tabletRows.size());
+                statsStatus.updatedRows.addAndGet(tableUpdateRows);

Review Comment:
   这是另一个bug, 和本pr 无关. 临时分区的行数不应该记录在updatedRows里.



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4355526082


##########
fe/fe-core/src/main/java/org/apache/doris/statistics/repository/StatisticsCleaner.java:
##########
@@ -140,6 +140,8 @@ private void clearTableStats() {
                 }
                 LOG.info("Table {}.{}.{} with id {} not exist, remove its table stats record.",
                         stats.ctlName, stats.dbName, stats.tblName, id);
+                // No table lock: the table is gone, so no load can recreate the record of this id while the

Review Comment:
   Fixed in 4c792cff11f.
   
   The cleaner now takes the **write lock of the recycled table** — the very instance `RECOVER TABLE`
   registers again, and the lock a load holds in `finishTransaction` until it recreated the record — around
   the removal **and** its journal entry, and it checks the catalog again under that lock:
   
   - if the deletion is applied before the recovery/load, the load journals its creation after this
     `DELETE_TABLE_STATS`, so the replay order matches the master;
   - if the load already recreated the record, that record is the one which is removed, and the replay
     removes it as well;
   - a table which was recovered between the check and the lock keeps its statistics.
   
   A record whose table is gone for good (not in the recycle bin, hence not recoverable) keeps the old
   unlocked removal. `CatalogRecycleBin#getRecycledTable` exposes the recycled instance; the previous
   "no lock needed" comment was wrong because `RECOVER TABLE` re-registers the table with its original id.
   
   Tests: `StatisticsCleanerTest#testRecycledTableStatsAreRemovedUnderTheTableLock` (the removal and the
   journal entry happen between `writeLock` and `writeUnlock`) and
   `StatisticsCleanerTest#testTableRecoveredBeforeTheRemovalKeepsItsStats` (a table which is back in the
   catalog under the lock is left alone). `StatisticsCleanerTest` 3/3, checkstyle passes.
   



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6355949419

   /review


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6160255591

   run buildall


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4309726925


##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/TableStatsMeta.java:
##########
@@ -268,6 +382,29 @@ public long getBaseIndexDeltaRowCount(OlapTable table) {
         return updatedRows.get() - maxUpdateRows;
     }
 
+    /**
+     * The row count of the index together with the rows loaded since it was collected, i.e. the row count of
+     * the table. Both are read by the planner without the table lock, for instance while it plans a direct
+     * scan of a materialized view, so they have to come from the same state of this record: a collected row
+     * count paired with the baseline of another analysis would count rows twice or miss them. The rows loaded
+     * while this call runs are not part of the snapshot, whichever state reads them accumulates them in
+     * {@link #updatedRows} and reports them as delta rows.
+     */
+    public synchronized long getRowCountWithDeltaRows(OlapTable table, long indexId) {
+        long rowCount = getRowCount(indexId);
+        if (!keepsOneRowPerBaseRow(table, indexId)) {
+            // The index aggregates, or it merges the rows of a unique key table: it has its own, smaller row
+            // count, and the rows loaded into the base index would overstate it.
+            return rowCount;
+        }
+        if (rowCount == TableIf.UNKNOWN_ROW_COUNT) {
+            // The index has no collected row count yet, neither the row count of the table nor a delta row
+            // count can be derived from it. The planner treats the unknown row count as no information.
+            return rowCount;
+        }
+        return rowCount + getBaseIndexDeltaRowCount(table);

Review Comment:
   只有在使用set stats 这条测试命令时才会出现多统计行数的情况, 不是用户场景,不用处理



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4355069153


##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/AnalysisManager.java:
##########
@@ -1298,12 +1344,12 @@ public void updateUpdatedRows(Map<Long, Long> partitionToUpdateRows, long dbId,
     }
 
     // Invoke this for cloud version load.
-    public void updateUpdatedRows(Map<Long, Long> updatedRows) {
+    public void updateUpdatedRows(Map<Long, Long> updatedRows, long dbId) {
         try {
             if (!Env.getCurrentEnv().isMaster() || Env.isCheckpointThread()) {
                 return;
             }
-            UpdateRowsEvent updateRowsEvent = new UpdateRowsEvent(updatedRows);
+            UpdateRowsEvent updateRowsEvent = new UpdateRowsEvent(dbId, updatedRows);

Review Comment:
   Fixed in 50074783a42.
   
   `AnalysisManager.updateUpdatedRows(Map, dbId)` — the cloud report path — now holds the table write
   lock of the reported tables while it applies **and** journals the rows, i.e. the same lock which a
   whole table `DROP STATS` takes to remove the record and journal the deletion. The journal order is
   then always the order in which the mutations are applied, so both interleavings converge:
   
   - report first: journal `[UPDATE_ROWS][DELETE]` -> master create(N) then remove, replay create(N) then remove
   - DROP first:   journal `[DELETE][UPDATE_ROWS]` -> master remove then create(N), replay remove then create(N)
   
   The tables are locked in id order with the 10 second timeout `finishTransaction` uses; when the lock
   cannot be acquired the report is dropped with a warning and the row count stays unknown until the
   backends report it.
   
   Test: `AnalysisManagerTest#testCloudRowUpdateIsSerializedWithTheTableLock` holds the table write lock,
   reports rows from another thread and asserts that neither the record nor the journal entry appears
   while the lock is held (and that both appear after the unlock). It fails without the change with
   "the report was applied while the table was locked".
   



##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/AnalysisManager.java:
##########
@@ -1355,7 +1401,7 @@ public void replayUpdateRowsRecord(UpdateRowsEvent event) {
         InternalCatalog catalog = Env.getCurrentInternalCatalog();
         if (event.getRecords() != null) {
             for (Entry<Long, Long> record : event.getRecords().entrySet()) {
-                TableStatsMeta statsStatus = idToTblStats.get(record.getKey());
+                TableStatsMeta statsStatus = findOrCreateTableStats(catalog, event.getDbId(), record.getKey());

Review Comment:
   Fixed in d1aca0b7213.
   
   The partition generation of the report is now validated: `CloudGlobalTransactionMgr.afterCommitTxnResp`
   hands the `<table_id, partition_id>` pairs which `CommitTxnResponse` already carries to
   `AnalysisManager.updateUpdatedRows`, which drops a table's rows when any of the partitions the report
   counted no longer belongs to the table (a whole table `TRUNCATE` replaces the partitions and keeps the
   table id). The check runs inside the table write lock the report takes, so the generation cannot change
   between the check and the application, and a dropped report is neither applied nor journaled — so the
   record the next load recreates only counts the rows of the current partitions.
   
   Tests: `AnalysisManagerTest#testReportOfAPartitionRemovedByTruncateIsNotApplied` (a delayed report of
   100 rows for the replaced partition is ignored; the reload's 3 rows recreate the record with 3 rows,
   not 103) and `AnalysisManagerTest#testReportOfRemovedPartitionsIsRecognized`.
   



##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/AnalysisManager.java:
##########
@@ -1366,52 +1412,52 @@ public void replayUpdateRowsRecord(UpdateRowsEvent event) {
         // Record : TableId -> (TabletId -> update rows)
         if (event.getTabletRecords() != null) {
             for (Entry<Long, Map<Long, Long>> record : event.getTabletRecords().entrySet()) {
-                TableStatsMeta statsStatus = idToTblStats.get(record.getKey());
-                if (statsStatus != null) {
-                    Optional<Database> dbOption = catalog.getDb(event.getDbId());
-                    if (!dbOption.isPresent()) {
-                        LOG.warn("Database {} does not exist.", event.getDbId());
-                        continue;
-                    }
-                    Database database = dbOption.get();
-                    Optional<Table> tableOption = database.getTable(record.getKey());
-                    if (!tableOption.isPresent()) {
-                        LOG.warn("Table {} does not exist in DB {}.", record.getKey(), event.getDbId());
-                        continue;
-                    }
-                    Table table = tableOption.get();
-                    if (!(table instanceof OlapTable)) {
-                        continue;
-                    }
-                    OlapTable olapTable = (OlapTable) table;
-                    short replicaNum = olapTable.getTableProperty().getReplicaAllocation().getTotalReplicaNum();
-                    Map<Long, Long> tabletRows = record.getValue();
-                    if (tabletRows == null || tabletRows.isEmpty()) {
-                        LOG.info("Tablet row count map is empty");
-                        continue;
-                    }
-                    long rowsForAllReplica = 0;
-                    for (Entry<Long, Long> entry : tabletRows.entrySet()) {
-                        rowsForAllReplica += entry.getValue();
-                        if (LOG.isDebugEnabled()) {
-                            LOG.debug("Table id {}, tablet id {}, row count {}",
-                                    record.getKey(), entry.getKey(), entry.getValue());
-                        }
-                    }
-                    long tableUpdateRows = rowsForAllReplica / replicaNum;
-                    LOG.info("Update rows for table {} is {}, replicaNum is {}, "
-                            + "rows for all replica {}, tablets count {}",
-                            olapTable.getName(), tableUpdateRows, replicaNum, rowsForAllReplica, tabletRows.size());
-                    statsStatus.updatedRows.addAndGet(tableUpdateRows);
-                    if (StatisticsUtil.enablePartitionAnalyze()) {
-                        updatePartitionRows(olapTable, tabletRows, statsStatus, replicaNum);
+                Optional<Database> dbOption = catalog.getDb(event.getDbId());
+                if (!dbOption.isPresent()) {
+                    LOG.warn("Database {} does not exist.", event.getDbId());
+                    continue;
+                }
+                Database database = dbOption.get();
+                Optional<Table> tableOption = database.getTable(record.getKey());
+                if (!tableOption.isPresent()) {
+                    LOG.warn("Table {} does not exist in DB {}.", record.getKey(), event.getDbId());
+                    continue;
+                }
+                Table table = tableOption.get();
+                if (!(table instanceof OlapTable)) {
+                    continue;
+                }
+                OlapTable olapTable = (OlapTable) table;
+                TableStatsMeta statsStatus = findOrCreateTableStats(olapTable);

Review Comment:
   Fixed in b01c9770473.
   
   The removal moved into the same critical section which journals `FINISHED`, i.e. before
   `tbl.writeUnlock()`, so a load can no longer become visible in between: either the load ran before the
   schema change finished (the record it updated is removed by the job on both the master and the replay)
   or after (the record it recreates exists on both). `removeTableStatsOnFinished` asserts that the table
   write lock is held, so the call cannot be moved out of the critical section again; dropping the
   persisted statistics and invalidating the caches (`dropStats`) stays outside the lock because it does
   not touch the record.
   
   Test: `SchemaChangeJobV2StatsTest#testRemoveTableStatsOnFinishedRequiresTheTableWriteLock` (the removal
   is rejected without the table write lock and removes the record with it).
   



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6363021585

   
   <details>
   <summary>ClickBench: <b>Total hot run time: 24.38 s</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
   ClickBench test result on commit 4c792cff11ff0151e7af0174bf908dcb0d8a056c, data reload: false
   
   query1	0.01	0.01	0.00
   query2	0.10	0.05	0.05
   query3	0.27	0.09	0.09
   query4	1.61	0.10	0.11
   query5	0.23	0.23	0.25
   query6	1.17	0.68	0.68
   query7	0.04	0.01	0.00
   query8	0.06	0.04	0.04
   query9	0.41	0.35	0.37
   query10	0.57	0.56	0.59
   query11	0.21	0.15	0.15
   query12	0.18	0.15	0.15
   query13	0.50	0.49	0.50
   query14	1.03	1.01	1.01
   query15	0.63	0.60	0.60
   query16	0.34	0.34	0.36
   query17	1.12	1.15	1.12
   query18	0.21	0.19	0.19
   query19	2.07	2.06	1.99
   query20	0.02	0.01	0.01
   query21	15.49	0.19	0.14
   query22	4.89	0.05	0.05
   query23	16.25	0.32	0.12
   query24	3.05	0.52	0.43
   query25	0.12	0.05	0.06
   query26	1.39	0.23	0.16
   query27	0.07	0.06	0.06
   query28	3.25	0.77	0.36
   query29	12.49	4.13	3.23
   query30	0.28	0.13	0.11
   query31	2.78	0.55	0.33
   query32	3.23	0.58	0.50
   query33	3.27	3.29	3.21
   query34	15.30	3.99	3.39
   query35	3.35	3.32	3.32
   query36	0.55	0.42	0.41
   query37	0.09	0.06	0.06
   query38	0.05	0.04	0.04
   query39	0.03	0.03	0.03
   query40	0.18	0.14	0.14
   query41	0.08	0.03	0.02
   query42	0.04	0.03	0.03
   query43	0.04	0.04	0.04
   Total cold run time: 97.05 s
   Total hot run time: 24.38 s
   ```
   </details>
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6342295103

   
   <details>
   <summary>ClickBench: <b>Total hot run time: 24.4 s</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
   ClickBench test result on commit 70184f06f222076c5ba0b094186ccf27d09fc006, data reload: false
   
   query1	0.00	0.00	0.01
   query2	0.10	0.06	0.05
   query3	0.27	0.08	0.08
   query4	1.61	0.11	0.10
   query5	0.23	0.24	0.26
   query6	1.17	0.67	0.67
   query7	0.04	0.01	0.01
   query8	0.05	0.04	0.04
   query9	0.40	0.31	0.35
   query10	0.57	0.57	0.58
   query11	0.21	0.15	0.14
   query12	0.19	0.15	0.14
   query13	0.52	0.51	0.50
   query14	1.03	1.01	1.02
   query15	0.64	0.61	0.62
   query16	0.35	0.35	0.34
   query17	1.16	1.16	1.14
   query18	0.19	0.18	0.19
   query19	2.07	2.02	2.02
   query20	0.02	0.02	0.01
   query21	15.52	0.19	0.14
   query22	4.81	0.05	0.05
   query23	16.32	0.32	0.13
   query24	2.88	0.51	0.43
   query25	0.12	0.06	0.06
   query26	0.88	0.22	0.15
   query27	0.05	0.05	0.04
   query28	2.91	0.82	0.39
   query29	12.49	4.24	3.22
   query30	0.28	0.15	0.10
   query31	2.77	0.55	0.34
   query32	3.23	0.60	0.52
   query33	3.28	3.18	3.15
   query34	15.31	4.00	3.41
   query35	3.33	3.31	3.35
   query36	0.56	0.45	0.44
   query37	0.09	0.06	0.06
   query38	0.05	0.04	0.04
   query39	0.04	0.03	0.03
   query40	0.17	0.15	0.15
   query41	0.08	0.02	0.02
   query42	0.04	0.03	0.03
   query43	0.05	0.04	0.03
   Total cold run time: 96.08 s
   Total hot run time: 24.4 s
   ```
   </details>
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6362780244

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 28451 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit 4c792cff11ff0151e7af0174bf908dcb0d8a056c, data reload: false
   
   ------ Round 1 ----------------------------------
   ============================================
   q1	17616	5176	5056	5056
   q2	2154	326	292	292
   q3	10162	1400	822	822
   q4	4682	464	342	342
   q5	7448	809	542	542
   q6	181	183	151	151
   q7	757	799	597	597
   q8	9256	1314	1094	1094
   q9	5472	4164	4175	4164
   q10	6829	1292	1026	1026
   q11	435	241	225	225
   q12	644	394	275	275
   q13	18145	3606	3004	3004
   q14	277	262	251	251
   q15	q16	730	718	689	689
   q17	1194	745	578	578
   q18	6962	6185	6126	6126
   q19	1129	946	604	604
   q20	389	338	229	229
   q21	5447	2128	2247	2128
   q22	341	291	256	256
   Total cold run time: 100250 ms
   Total hot run time: 28451 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   ============================================
   q1	5580	5600	5590	5590
   q2	697	580	535	535
   q3	5231	5387	4936	4936
   q4	1992	2174	1541	1541
   q5	5300	5177	5715	5177
   q6	266	200	151	151
   q7	2325	2142	1871	1871
   q8	3129	2932	2671	2671
   q9	7989	8106	8077	8077
   q10	3702	3667	3196	3196
   q11	645	426	396	396
   q12	672	689	510	510
   q13	3536	3742	3230	3230
   q14	318	305	272	272
   q15	q16	687	712	652	652
   q17	8258	7440	7261	7261
   q18	12958	12205	12891	12205
   q19	889	773	779	773
   q20	2134	2125	1909	1909
   q21	5600	4578	4711	4578
   q22	518	451	409	409
   Total cold run time: 72426 ms
   Total hot run time: 65940 ms
   ```
   </details>
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6260135030

   # FE Regression Coverage Report
   Increment line coverage `  3.96% (89/2246)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/69028_e2ce99b43fe1332fbad22f00c5019273a29bf94c_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/69028_e2ce99b43fe1332fbad22f00c5019273a29bf94c_merge_fe/report/index.html)
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6260659798

   # FE Regression Coverage Report
   Increment line coverage `  3.96% (89/2246)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/69028_e2ce99b43fe1332fbad22f00c5019273a29bf94c_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/69028_e2ce99b43fe1332fbad22f00c5019273a29bf94c_merge_fe/report/index.html)
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4311337857


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/stats/StatsCalculator.java:
##########
@@ -384,11 +384,19 @@ public Statistics visitLogicalFilter(LogicalFilter<? extends Plan> filter, Void
      * returns the sum of deltaRowCount for all selected partitions or for the table.
      */
     private long computeDeltaRowCount(OlapScan olapScan) {
+        OlapTable olapTable = olapScan.getTable();
+        // The delta rows are the rows loaded into the base index which the collected base index row count
+        // doesn't include yet. They are not the rows of an index which aggregates them: such an index has its
+        // own (usually much smaller) row count, so the delta only applies to a scan of an index which keeps
+        // one row per base row.
+        if (!TableStatsMeta.keepsOneRowPerBaseRow(olapTable, olapScan.getSelectedIndexId())) {
+            return 0;

Review Comment:
   Good catch, fixed in dc2d63c6987.
   
   `computeDeltaRowCount()` no longer checks `keepsOneRowPerBaseRow`. The value it returns is not the row count of the scanned index: it only feeds `Statistics.deltaRowCount`, which `FilterEstimation.estimate()` uses as the population to retry a zero estimate with, and stale column statistics are not index specific. The check stays in `TableStatsMeta#getRowCountWithDeltaRows()`, the only place where the delta is added to the collected row count of an index, and the javadoc of `computeDeltaRowCount()` now spells that split out so the check does not get re-added by accident.
   
   For the analyze/load/out-of-range-filter case, `StatsCalculatorTest#testDeltaRowCountIsReportedForNonRowPreservingIndex` asserts that a scan of a non-row-preserving index (an AGG rollup) over a record created by a load still reports the delta rows, i.e. the population the retry needs; the recovery itself is already covered by the existing `FilterEstimationTest#testDeltaRow`. Both compile and the new test passes locally.
   



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4312193993


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/stats/StatsCalculator.java:
##########
@@ -384,11 +384,19 @@ public Statistics visitLogicalFilter(LogicalFilter<? extends Plan> filter, Void
      * returns the sum of deltaRowCount for all selected partitions or for the table.
      */
     private long computeDeltaRowCount(OlapScan olapScan) {
+        OlapTable olapTable = olapScan.getTable();
+        // The delta rows are the rows loaded into the base index which the collected base index row count
+        // doesn't include yet. They are not the rows of an index which aggregates them: such an index has its
+        // own (usually much smaller) row count, so the delta only applies to a scan of an index which keeps
+        // one row per base row.
+        if (!TableStatsMeta.keepsOneRowPerBaseRow(olapTable, olapScan.getSelectedIndexId())) {
+            return 0;

Review Comment:
   done



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4355718537


##########
fe/fe-core/src/main/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgr.java:
##########
@@ -545,7 +554,7 @@ public void afterCommitTxnResp(CommitTxnResponse commitTxnResponse, List<TabletC
             }
         }
         Env env = Env.getCurrentEnv();
-        env.getAnalysisManager().updateUpdatedRows(updatedRows);
+        env.getAnalysisManager().updateUpdatedRows(updatedRows, dbId, reportedPartitions);

Review Comment:
   [P2] Supply row counters for the other cloud load producers
   
   On a cloud DUP table, `TRUNCATE TABLE t; BEGIN; INSERT INTO t SELECT number FROM numbers("number"="3"); COMMIT;` still leaves `Project(*) -> OlapScan(t)` estimated at 1 before tablet statistics arrive: `commitTransactionWithSubTxns()` puts base-tablet IDs only in `SubTxnInfo`, but meta-service `commit_txn_with_sub_txn()` passes the empty top-level `request->base_tablet_ids()` to `calc_table_stats()`. Its response therefore has no `updated_row_count`, so the new call here receives an empty `updatedRows` map and creates no record. Non-MoW/non-TSO BE-direct stream loads with `enable_stream_load_commit_txn_on_be=true` have the same gap because `CloudMetaMgr::commit_txn()` supplies no base-tablet IDs either. These paths retain the original symptom despite the new cloud recreation path. Carry the required tablet IDs/counters through both producers and add coverage using their actual commit responses.



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6161097857

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 29273 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit e2ce99b43fe1332fbad22f00c5019273a29bf94c, data reload: false
   
   ------ Round 1 ----------------------------------
   ============================================
   q1	17630	5619	5561	5561
   q2	2136	317	271	271
   q3	10194	1431	851	851
   q4	4682	482	364	364
   q5	7442	790	547	547
   q6	191	186	157	157
   q7	772	798	594	594
   q8	9239	1355	1081	1081
   q9	5698	4475	4482	4475
   q10	6813	1303	1038	1038
   q11	456	257	240	240
   q12	632	404	286	286
   q13	18078	3072	2387	2387
   q14	278	274	248	248
   q15	q16	726	733	678	678
   q17	1190	741	564	564
   q18	7197	6230	6212	6212
   q19	1103	966	619	619
   q20	402	343	234	234
   q21	5632	2564	2662	2564
   q22	399	364	302	302
   Total cold run time: 100890 ms
   Total hot run time: 29273 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   ============================================
   q1	6665	6640	6851	6640
   q2	720	632	579	579
   q3	5524	5762	5221	5221
   q4	2055	2162	1600	1600
   q5	5700	5590	5519	5519
   q6	317	208	150	150
   q7	2326	2028	1764	1764
   q8	3147	2787	2741	2741
   q9	8319	8157	8130	8130
   q10	3768	3590	3274	3274
   q11	592	399	386	386
   q12	673	702	516	516
   q13	2760	3067	2403	2403
   q14	286	295	269	269
   q15	q16	671	673	593	593
   q17	7863	6990	6960	6960
   q18	13193	12256	13051	12256
   q19	922	804	800	800
   q20	2183	2146	1940	1940
   q21	5848	4730	4804	4730
   q22	520	451	394	394
   Total cold run time: 74052 ms
   Total hot run time: 66865 ms
   ```
   </details>
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4309991283


##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/AnalysisManager.java:
##########
@@ -1366,52 +1405,52 @@ public void replayUpdateRowsRecord(UpdateRowsEvent event) {
         // Record : TableId -> (TabletId -> update rows)
         if (event.getTabletRecords() != null) {
             for (Entry<Long, Map<Long, Long>> record : event.getTabletRecords().entrySet()) {
-                TableStatsMeta statsStatus = idToTblStats.get(record.getKey());
-                if (statsStatus != null) {
-                    Optional<Database> dbOption = catalog.getDb(event.getDbId());
-                    if (!dbOption.isPresent()) {
-                        LOG.warn("Database {} does not exist.", event.getDbId());
-                        continue;
-                    }
-                    Database database = dbOption.get();
-                    Optional<Table> tableOption = database.getTable(record.getKey());
-                    if (!tableOption.isPresent()) {
-                        LOG.warn("Table {} does not exist in DB {}.", record.getKey(), event.getDbId());
-                        continue;
-                    }
-                    Table table = tableOption.get();
-                    if (!(table instanceof OlapTable)) {
-                        continue;
-                    }
-                    OlapTable olapTable = (OlapTable) table;
-                    short replicaNum = olapTable.getTableProperty().getReplicaAllocation().getTotalReplicaNum();
-                    Map<Long, Long> tabletRows = record.getValue();
-                    if (tabletRows == null || tabletRows.isEmpty()) {
-                        LOG.info("Tablet row count map is empty");
-                        continue;
-                    }
-                    long rowsForAllReplica = 0;
-                    for (Entry<Long, Long> entry : tabletRows.entrySet()) {
-                        rowsForAllReplica += entry.getValue();
-                        if (LOG.isDebugEnabled()) {
-                            LOG.debug("Table id {}, tablet id {}, row count {}",
-                                    record.getKey(), entry.getKey(), entry.getValue());
-                        }
-                    }
-                    long tableUpdateRows = rowsForAllReplica / replicaNum;
-                    LOG.info("Update rows for table {} is {}, replicaNum is {}, "
-                            + "rows for all replica {}, tablets count {}",
-                            olapTable.getName(), tableUpdateRows, replicaNum, rowsForAllReplica, tabletRows.size());
-                    statsStatus.updatedRows.addAndGet(tableUpdateRows);
-                    if (StatisticsUtil.enablePartitionAnalyze()) {
-                        updatePartitionRows(olapTable, tabletRows, statsStatus, replicaNum);
+                Optional<Database> dbOption = catalog.getDb(event.getDbId());
+                if (!dbOption.isPresent()) {
+                    LOG.warn("Database {} does not exist.", event.getDbId());
+                    continue;
+                }
+                Database database = dbOption.get();
+                Optional<Table> tableOption = database.getTable(record.getKey());
+                if (!tableOption.isPresent()) {
+                    LOG.warn("Table {} does not exist in DB {}.", record.getKey(), event.getDbId());
+                    continue;
+                }
+                Table table = tableOption.get();
+                if (!(table instanceof OlapTable)) {
+                    continue;
+                }
+                OlapTable olapTable = (OlapTable) table;
+                TableStatsMeta statsStatus = findOrCreateTableStats(olapTable);
+                short replicaNum = olapTable.getTableProperty().getReplicaAllocation().getTotalReplicaNum();
+                Map<Long, Long> tabletRows = record.getValue();
+                if (tabletRows == null || tabletRows.isEmpty()) {
+                    LOG.info("Tablet row count map is empty");
+                    continue;
+                }
+                long rowsForAllReplica = 0;
+                for (Entry<Long, Long> entry : tabletRows.entrySet()) {
+                    rowsForAllReplica += entry.getValue();
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug("Table id {}, tablet id {}, row count {}",
+                                record.getKey(), entry.getKey(), entry.getValue());
                     }
                 }
+                long tableUpdateRows = rowsForAllReplica / replicaNum;
+                LOG.info("Update rows for table {} is {}, replicaNum is {}, "
+                        + "rows for all replica {}, tablets count {}",
+                        olapTable.getName(), tableUpdateRows, replicaNum, rowsForAllReplica, tabletRows.size());
+                statsStatus.updatedRows.addAndGet(tableUpdateRows);

Review Comment:
   这是另一个bug, 和本pr 无关. 临时分区的行数不应该记录在updatedRows里.
   单独提了一个issue https://github.com/apache/doris/issues/69262



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4345396050


##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/TableStatsMeta.java:
##########
@@ -268,6 +382,29 @@ public long getBaseIndexDeltaRowCount(OlapTable table) {
         return updatedRows.get() - maxUpdateRows;
     }
 
+    /**
+     * The row count of the index together with the rows loaded since it was collected, i.e. the row count of
+     * the table. Both are read by the planner without the table lock, for instance while it plans a direct
+     * scan of a materialized view, so they have to come from the same state of this record: a collected row
+     * count paired with the baseline of another analysis would count rows twice or miss them. The rows loaded
+     * while this call runs are not part of the snapshot, whichever state reads them accumulates them in
+     * {@link #updatedRows} and reports them as delta rows.
+     */
+    public synchronized long getRowCountWithDeltaRows(OlapTable table, long indexId) {
+        long rowCount = getRowCount(indexId);
+        if (!keepsOneRowPerBaseRow(table, indexId)) {
+            // The index aggregates, or it merges the rows of a unique key table: it has its own, smaller row
+            // count, and the rows loaded into the base index would overstate it.
+            return rowCount;
+        }
+        if (rowCount == TableIf.UNKNOWN_ROW_COUNT) {

Review Comment:
   如果一个rollup index和base index是1:1 的, base index 上报了行数, 但rollup没有上报, 这时可以用base index的行数代替rollup的行数. 已经fix



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6313984452

   /review


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4336013846


##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/AnalysisManager.java:
##########
@@ -1276,6 +1276,45 @@ public TableStatsMeta findTableStatsStatus(long tblId) {
         return idToTblStats.get(tblId);
     }
 
+    /**
+     * The record of the table, created in the state of a table whose row count has never been collected when
+     * it doesn't exist yet. A truncation, a whole table DROP STATS and a schema change remove the record, so
+     * the rows which are loaded afterwards can only be turned into the row count of the table while the
+     * record exists: the planner falls back to it exactly while the backends have not reported the row count
+     * of the new tablets yet, which is the interval right after those operations.
+     *
+     * <p>Loads run this from the transaction visibility of the load, both on the master
+     * ({@code DatabaseTransactionMgr.updateCatalogAfterVisible}) and while a follower replays the transaction
+     * state, so the record exists on every frontend without a journal entry of its own.
+     *
+     * @return the existing or the created record, or null when the table cannot be resolved any more
+     */
+    private TableStatsMeta findOrCreateTableStats(InternalCatalog catalog, long dbId, long tableId) {
+        TableStatsMeta tableStats = idToTblStats.get(tableId);
+        if (tableStats != null) {
+            return tableStats;
+        }
+        Optional<Database> dbOption = catalog.getDb(dbId);
+        if (!dbOption.isPresent()) {
+            return null;
+        }
+        Optional<Table> tableOption = dbOption.get().getTable(tableId);
+        if (!tableOption.isPresent() || !(tableOption.get() instanceof OlapTable)) {
+            return null;
+        }
+        return findOrCreateTableStats((OlapTable) tableOption.get());
+    }
+
+    private TableStatsMeta findOrCreateTableStats(OlapTable table) {
+        TableStatsMeta tableStats = idToTblStats.get(table.getId());
+        if (tableStats != null) {
+            return tableStats;
+        }
+        TableStatsMeta created = new TableStatsMeta(table);
+        TableStatsMeta existing = idToTblStats.putIfAbsent(table.getId(), created);

Review Comment:
   [P2] Preserve the stats-map lock when inserting load records
   
   `writeTableStats()` holds the `idToTblStats` monitor while serializing its size and entries, but this insertion bypasses that monitor. During a live metadata dump (`MetaService.dump()` -> serving `Env.dumpImage()`), a cloud BE's `reportCommitTxnResult()` can reach this helper without a table lock, despite the dump's table read locks. If it inserts a first-load record after the size is written but before the iterator is created, the dump declares N records and emits N+1. `readIdToTblStats()` then leaves the extra JSON in the stream and the next module reader consumes it, making the image unreadable. This concerns live dumps; ordinary checkpoints use an isolated Env. Use the same map monitor as the existing publication/removal helpers, or serialize a consistent snapshot.



##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/TableStatsMeta.java:
##########
@@ -268,6 +382,29 @@ public long getBaseIndexDeltaRowCount(OlapTable table) {
         return updatedRows.get() - maxUpdateRows;
     }
 
+    /**
+     * The row count of the index together with the rows loaded since it was collected, i.e. the row count of
+     * the table. Both are read by the planner without the table lock, for instance while it plans a direct
+     * scan of a materialized view, so they have to come from the same state of this record: a collected row
+     * count paired with the baseline of another analysis would count rows twice or miss them. The rows loaded
+     * while this call runs are not part of the snapshot, whichever state reads them accumulates them in
+     * {@link #updatedRows} and reports them as delta rows.
+     */
+    public synchronized long getRowCountWithDeltaRows(OlapTable table, long indexId) {
+        long rowCount = getRowCount(indexId);
+        if (!keepsOneRowPerBaseRow(table, indexId)) {
+            // The index aggregates, or it merges the rows of a unique key table: it has its own, smaller row
+            // count, and the rows loaded into the base index would overstate it.
+            return rowCount;
+        }
+        if (rowCount == TableIf.UNKNOWN_ROW_COUNT) {

Review Comment:
   [P2] Keep a usable estimate for a newly added projection rollup
   
   On a DUP table `t(k1,k2,v)`, load 5 rows and ANALYZE, then add `r_dup(k1,v)` and load 100 more rows before the new rollup tablets report their counts. ADD ROLLUP retains the existing `TableStatsMeta`, so it has a collected base count and delta=100 but no `r_dup` entry. The plan `Project(k1,v) -> OlapScan(t INDEX r_dup)` passes `keepsOneRowPerBaseRow()`, yet this branch returns -1 and `computeOlapScan()` clamps it to 1 although the rollup holds 105 rows. Previously the fallback returned -1+100=99. For a row-preserving index without its own snapshot, use the known base count and delta, or preserve the usable load estimate. Add coverage for ANALYZE -> ADD ROLLUP -> LOAD before the first new-index report. This needs neither injected stats nor DROP STATS.



##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/AnalysisManager.java:
##########
@@ -1366,52 +1405,52 @@ public void replayUpdateRowsRecord(UpdateRowsEvent event) {
         // Record : TableId -> (TabletId -> update rows)
         if (event.getTabletRecords() != null) {
             for (Entry<Long, Map<Long, Long>> record : event.getTabletRecords().entrySet()) {
-                TableStatsMeta statsStatus = idToTblStats.get(record.getKey());
-                if (statsStatus != null) {
-                    Optional<Database> dbOption = catalog.getDb(event.getDbId());
-                    if (!dbOption.isPresent()) {
-                        LOG.warn("Database {} does not exist.", event.getDbId());
-                        continue;
-                    }
-                    Database database = dbOption.get();
-                    Optional<Table> tableOption = database.getTable(record.getKey());
-                    if (!tableOption.isPresent()) {
-                        LOG.warn("Table {} does not exist in DB {}.", record.getKey(), event.getDbId());
-                        continue;
-                    }
-                    Table table = tableOption.get();
-                    if (!(table instanceof OlapTable)) {
-                        continue;
-                    }
-                    OlapTable olapTable = (OlapTable) table;
-                    short replicaNum = olapTable.getTableProperty().getReplicaAllocation().getTotalReplicaNum();
-                    Map<Long, Long> tabletRows = record.getValue();
-                    if (tabletRows == null || tabletRows.isEmpty()) {
-                        LOG.info("Tablet row count map is empty");
-                        continue;
-                    }
-                    long rowsForAllReplica = 0;
-                    for (Entry<Long, Long> entry : tabletRows.entrySet()) {
-                        rowsForAllReplica += entry.getValue();
-                        if (LOG.isDebugEnabled()) {
-                            LOG.debug("Table id {}, tablet id {}, row count {}",
-                                    record.getKey(), entry.getKey(), entry.getValue());
-                        }
-                    }
-                    long tableUpdateRows = rowsForAllReplica / replicaNum;
-                    LOG.info("Update rows for table {} is {}, replicaNum is {}, "
-                            + "rows for all replica {}, tablets count {}",
-                            olapTable.getName(), tableUpdateRows, replicaNum, rowsForAllReplica, tabletRows.size());
-                    statsStatus.updatedRows.addAndGet(tableUpdateRows);
-                    if (StatisticsUtil.enablePartitionAnalyze()) {
-                        updatePartitionRows(olapTable, tabletRows, statsStatus, replicaNum);
+                Optional<Database> dbOption = catalog.getDb(event.getDbId());
+                if (!dbOption.isPresent()) {
+                    LOG.warn("Database {} does not exist.", event.getDbId());
+                    continue;
+                }
+                Database database = dbOption.get();
+                Optional<Table> tableOption = database.getTable(record.getKey());
+                if (!tableOption.isPresent()) {
+                    LOG.warn("Table {} does not exist in DB {}.", record.getKey(), event.getDbId());
+                    continue;
+                }
+                Table table = tableOption.get();
+                if (!(table instanceof OlapTable)) {
+                    continue;
+                }
+                OlapTable olapTable = (OlapTable) table;
+                TableStatsMeta statsStatus = findOrCreateTableStats(olapTable);

Review Comment:
   [P2] Order record recreation with DROP STATS journaling
   
   The `VISIBLE` transaction is durable before `DatabaseTransactionMgr.finishTransaction()` calls `updateCatalogAfterVisible()`. A concurrent whole-table `DROP STATS t` takes no target-table lock, so it can remove the record, journal `DELETE_TABLE_STATS`, and finish remote invalidation in that gap. Resuming here creates a record containing the load's N rows on the master. Followers and the checkpoint instead replay `VISIBLE` (create N) followed by `DELETE_TABLE_STATS` (remove it), leaving no record. Before tablet reporting, `Project(*) -> OlapScan(t)` consequently estimates N on the master but 1 after replay or failover. The previous get-only path could not resurrect the deleted record. Order creation/removal consistently with the transaction and deletion journals, and cover this interleaving with deterministic barriers.



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6363443671

   # FE Regression Coverage Report
   Increment line coverage `  28.57% (128/448)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/69028_4c792cff11ff0151e7af0174bf908dcb0d8a056c_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/69028_4c792cff11ff0151e7af0174bf908dcb0d8a056c_merge_fe/report/index.html)
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6321119929

   # FE UT Coverage Report
   Increment line coverage `  67.52% (79/117)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/69028_9de2254950e70969946a99e8ce2b9a69e1d9bf6f/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/69028_9de2254950e70969946a99e8ce2b9a69e1d9bf6f/fe_report/index.html)
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4345864730


##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/AnalysisManager.java:
##########
@@ -1298,12 +1344,12 @@ public void updateUpdatedRows(Map<Long, Long> partitionToUpdateRows, long dbId,
     }
 
     // Invoke this for cloud version load.
-    public void updateUpdatedRows(Map<Long, Long> updatedRows) {
+    public void updateUpdatedRows(Map<Long, Long> updatedRows, long dbId) {
         try {
             if (!Env.getCurrentEnv().isMaster() || Env.isCheckpointThread()) {
                 return;
             }
-            UpdateRowsEvent updateRowsEvent = new UpdateRowsEvent(updatedRows);
+            UpdateRowsEvent updateRowsEvent = new UpdateRowsEvent(dbId, updatedRows);

Review Comment:
   [P2] Serialize cloud row updates with statistics deletion
   
   The new DROP STATS table lock does not protect the cloud path: `reportCommitTxnResult() -> afterCommitTxnResp() -> updateUpdatedRows()` takes no target-table lock. After a cloud report creates a record containing N rows, pause between `replayUpdateRowsRecord()` and `logUpdateRowsRecord()`, then complete whole-table DROP STATS. The master removes the record and journals DELETE; resuming the report journals UPDATE_ROWS. Replay therefore executes DELETE -> UPDATE_ROWS and recreates N, while the master has no record. Before tablet reporting, `Project(*) -> OlapScan(t)` estimates 1 on the master versus N after failover. This is distinct from the existing non-cloud VISIBLE/DROP STATS thread: it uses the separate cloud journal and the opposite replay order. Serialize cloud mutation through journal completion with the deletion protocol, and cover this interleaving with barriers.



##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/AnalysisManager.java:
##########
@@ -1355,7 +1401,7 @@ public void replayUpdateRowsRecord(UpdateRowsEvent event) {
         InternalCatalog catalog = Env.getCurrentInternalCatalog();
         if (event.getRecords() != null) {
             for (Entry<Long, Long> record : event.getRecords().entrySet()) {
-                TableStatsMeta statsStatus = idToTblStats.get(record.getKey());
+                TableStatsMeta statsStatus = findOrCreateTableStats(catalog, event.getDbId(), record.getKey());

Review Comment:
   [P2] Exclude pre-truncate cloud reports from the recreated record
   
   Cloud stream loads queue `send_stats_to_fe_async()` and can return success before the FE receives their statistics. Load 100 rows, delay that report, TRUNCATE the entire table, then deliver the old response and load 3 new rows. TRUNCATE keeps the table ID, so this branch recreates a zero-baseline record and accumulates the removed 100 rows; the next load raises it to 103. `afterCommitTxnResp()` applies these counters before `updateVersion()` skips the now-missing old partition. While the replacement tablets are unreported, `Project(*) -> OlapScan(t)` consequently estimates 103 for 3 rows. Previously the delayed report found no record and was ignored. This is separate from the known partial-truncate/drop-partition counter issue. Validate the report's partition generation before recreating/accumulating the record, and add a delayed-report/full-truncate/reload case.



##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/AnalysisManager.java:
##########
@@ -1366,52 +1412,52 @@ public void replayUpdateRowsRecord(UpdateRowsEvent event) {
         // Record : TableId -> (TabletId -> update rows)
         if (event.getTabletRecords() != null) {
             for (Entry<Long, Map<Long, Long>> record : event.getTabletRecords().entrySet()) {
-                TableStatsMeta statsStatus = idToTblStats.get(record.getKey());
-                if (statsStatus != null) {
-                    Optional<Database> dbOption = catalog.getDb(event.getDbId());
-                    if (!dbOption.isPresent()) {
-                        LOG.warn("Database {} does not exist.", event.getDbId());
-                        continue;
-                    }
-                    Database database = dbOption.get();
-                    Optional<Table> tableOption = database.getTable(record.getKey());
-                    if (!tableOption.isPresent()) {
-                        LOG.warn("Table {} does not exist in DB {}.", record.getKey(), event.getDbId());
-                        continue;
-                    }
-                    Table table = tableOption.get();
-                    if (!(table instanceof OlapTable)) {
-                        continue;
-                    }
-                    OlapTable olapTable = (OlapTable) table;
-                    short replicaNum = olapTable.getTableProperty().getReplicaAllocation().getTotalReplicaNum();
-                    Map<Long, Long> tabletRows = record.getValue();
-                    if (tabletRows == null || tabletRows.isEmpty()) {
-                        LOG.info("Tablet row count map is empty");
-                        continue;
-                    }
-                    long rowsForAllReplica = 0;
-                    for (Entry<Long, Long> entry : tabletRows.entrySet()) {
-                        rowsForAllReplica += entry.getValue();
-                        if (LOG.isDebugEnabled()) {
-                            LOG.debug("Table id {}, tablet id {}, row count {}",
-                                    record.getKey(), entry.getKey(), entry.getValue());
-                        }
-                    }
-                    long tableUpdateRows = rowsForAllReplica / replicaNum;
-                    LOG.info("Update rows for table {} is {}, replicaNum is {}, "
-                            + "rows for all replica {}, tablets count {}",
-                            olapTable.getName(), tableUpdateRows, replicaNum, rowsForAllReplica, tabletRows.size());
-                    statsStatus.updatedRows.addAndGet(tableUpdateRows);
-                    if (StatisticsUtil.enablePartitionAnalyze()) {
-                        updatePartitionRows(olapTable, tabletRows, statsStatus, replicaNum);
+                Optional<Database> dbOption = catalog.getDb(event.getDbId());
+                if (!dbOption.isPresent()) {
+                    LOG.warn("Database {} does not exist.", event.getDbId());
+                    continue;
+                }
+                Database database = dbOption.get();
+                Optional<Table> tableOption = database.getTable(record.getKey());
+                if (!tableOption.isPresent()) {
+                    LOG.warn("Table {} does not exist in DB {}.", record.getKey(), event.getDbId());
+                    continue;
+                }
+                Table table = tableOption.get();
+                if (!(table instanceof OlapTable)) {
+                    continue;
+                }
+                OlapTable olapTable = (OlapTable) table;
+                TableStatsMeta statsStatus = findOrCreateTableStats(olapTable);

Review Comment:
   [P2] Order schema-completion removal with load record recreation
   
   `SchemaChangeJobV2.runRunningJob()` journals FINISHED under the table write lock, unlocks at line 732, and removes table statistics only afterwards at line 738. A ready load can become VISIBLE in that gap and update the old record, which the schema worker then removes on the master. Replay applies FINISHED and removes statistics in `EditLog`, then replays VISIBLE; this new call recreates a record containing the load's N rows. The master thus has no record while replay/checkpoint has N. With the new index still unreported, `Project(*) -> OlapScan(t)` estimates 1 versus N after failover. The previous get-only path left both records absent. This is a distinct schema-completion caller, untouched by the DROP STATS locking fix. Remove statistics within the schema FINISHED/table-lock boundary and add a deterministic schema-completion/load interleaving test.



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4266628223


##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/TableStatsMeta.java:
##########
@@ -194,6 +273,21 @@ public void update(AnalysisInfo analyzedJob, TableIf tableIf) {
         if (tableIf != null) {
             if (tableIf instanceof OlapTable) {
                 OlapTable olapTable = (OlapTable) tableIf;
+                // The collected row counts published below already include the rows which had been loaded when
+                // the job was built, remember how many they were, they are not delta rows. The baseline may
+                // only advance together with the collected base index row count, an analysis of another index
+                // (a materialized view) doesn't touch it.
+                // NOTICE: the baseline is published before the collected counts. They are both read without
+                // the table lock by the planner, and a reader which pairs the collected count of this analysis
+                // with the baseline of the previous one counts the rows loaded since that previous analysis
+                // twice.
+                if (analyzedJob.indexesRowCount.containsKey(olapTable.getBaseIndexId())) {
+                    // A row count supplied by the user replaces the collected one and describes the table as
+                    // of now, so the rows loaded so far are part of it and the baseline has to move to the
+                    // current value. Keeping the older baseline would count those rows again once DROP STATS
+                    // clears userInjected.
+                    updatedRowsBase.set(analyzedJob.userInject ? updatedRows.get() : analyzedJob.updateRows);

Review Comment:
   [P2] Align the baseline with the snapshot that ANALYZE actually reads
   
   This baseline is captured when the job is built, while indexesRowCount comes from SQL executed later. On a fresh or truncated table, load 100 rows, build a FULL ANALYZE job (updateRows=100), then let another 50-row load become visible before its COUNT(1) query starts. The new load-created record has updatedRows=150 and analysis collects 150, but this line installs baseline=100, so a full OlapScan before tablet reporting gets cardinality 150+(150-100)=200. Previously the first analysis had no existing record/counter, so this sequence returned the collected 150. The monitor cannot reconcile these two different data snapshots. Pair the collected count with the load baseline for the visible-version snapshot used by the analyze query and add a deterministic load-between-job-build-and-query case.



##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/TableStatsMeta.java:
##########
@@ -268,6 +382,29 @@ public long getBaseIndexDeltaRowCount(OlapTable table) {
         return updatedRows.get() - maxUpdateRows;
     }
 
+    /**
+     * The row count of the index together with the rows loaded since it was collected, i.e. the row count of
+     * the table. Both are read by the planner without the table lock, for instance while it plans a direct
+     * scan of a materialized view, so they have to come from the same state of this record: a collected row
+     * count paired with the baseline of another analysis would count rows twice or miss them. The rows loaded
+     * while this call runs are not part of the snapshot, whichever state reads them accumulates them in
+     * {@link #updatedRows} and reports them as delta rows.
+     */
+    public synchronized long getRowCountWithDeltaRows(OlapTable table, long indexId) {
+        long rowCount = getRowCount(indexId);
+        if (!keepsOneRowPerBaseRow(table, indexId)) {
+            // The index aggregates, or it merges the rows of a unique key table: it has its own, smaller row
+            // count, and the rows loaded into the base index would overstate it.
+            return rowCount;
+        }
+        if (rowCount == TableIf.UNKNOWN_ROW_COUNT) {
+            // The index has no collected row count yet, neither the row count of the table nor a delta row
+            // count can be derived from it. The planner treats the unknown row count as no information.
+            return rowCount;
+        }
+        return rowCount + getBaseIndexDeltaRowCount(table);

Review Comment:
   [P2] Keep a baseline for an independently injected rollup count
   
   A row-preserving rollup can have a current collected count while updatedRowsBase still describes the base index's older snapshot. For a fresh DUP table t(k1,k2,v) with r_dup(k1,v), load 5 rows, run ALTER TABLE t INDEX r_dup MODIFY COLUMN k1 SET STATS ('row_count'='5'), then DROP STATS t(k2). The injection updates only r_dup's count, so the base-index condition in update() leaves the new record's baseline at 0; column DROP STATS keeps that count but clears userInjected. Before tablet reporting, Project(k1,v) -> OlapScan(t, index=r_dup) therefore returns 5+(5-0)=10 here although the rollup has 5 rows. Previously rollup-only injection created a record with no accumulated rows and returned 5. Pair each index count with its own load baseline, or consistently refresh equivalent index snapshots when injecting a count, and cover this injection/drop sequence.



##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/AnalysisManager.java:
##########
@@ -1366,52 +1405,52 @@ public void replayUpdateRowsRecord(UpdateRowsEvent event) {
         // Record : TableId -> (TabletId -> update rows)
         if (event.getTabletRecords() != null) {
             for (Entry<Long, Map<Long, Long>> record : event.getTabletRecords().entrySet()) {
-                TableStatsMeta statsStatus = idToTblStats.get(record.getKey());
-                if (statsStatus != null) {
-                    Optional<Database> dbOption = catalog.getDb(event.getDbId());
-                    if (!dbOption.isPresent()) {
-                        LOG.warn("Database {} does not exist.", event.getDbId());
-                        continue;
-                    }
-                    Database database = dbOption.get();
-                    Optional<Table> tableOption = database.getTable(record.getKey());
-                    if (!tableOption.isPresent()) {
-                        LOG.warn("Table {} does not exist in DB {}.", record.getKey(), event.getDbId());
-                        continue;
-                    }
-                    Table table = tableOption.get();
-                    if (!(table instanceof OlapTable)) {
-                        continue;
-                    }
-                    OlapTable olapTable = (OlapTable) table;
-                    short replicaNum = olapTable.getTableProperty().getReplicaAllocation().getTotalReplicaNum();
-                    Map<Long, Long> tabletRows = record.getValue();
-                    if (tabletRows == null || tabletRows.isEmpty()) {
-                        LOG.info("Tablet row count map is empty");
-                        continue;
-                    }
-                    long rowsForAllReplica = 0;
-                    for (Entry<Long, Long> entry : tabletRows.entrySet()) {
-                        rowsForAllReplica += entry.getValue();
-                        if (LOG.isDebugEnabled()) {
-                            LOG.debug("Table id {}, tablet id {}, row count {}",
-                                    record.getKey(), entry.getKey(), entry.getValue());
-                        }
-                    }
-                    long tableUpdateRows = rowsForAllReplica / replicaNum;
-                    LOG.info("Update rows for table {} is {}, replicaNum is {}, "
-                            + "rows for all replica {}, tablets count {}",
-                            olapTable.getName(), tableUpdateRows, replicaNum, rowsForAllReplica, tabletRows.size());
-                    statsStatus.updatedRows.addAndGet(tableUpdateRows);
-                    if (StatisticsUtil.enablePartitionAnalyze()) {
-                        updatePartitionRows(olapTable, tabletRows, statsStatus, replicaNum);
+                Optional<Database> dbOption = catalog.getDb(event.getDbId());
+                if (!dbOption.isPresent()) {
+                    LOG.warn("Database {} does not exist.", event.getDbId());
+                    continue;
+                }
+                Database database = dbOption.get();
+                Optional<Table> tableOption = database.getTable(record.getKey());
+                if (!tableOption.isPresent()) {
+                    LOG.warn("Table {} does not exist in DB {}.", record.getKey(), event.getDbId());
+                    continue;
+                }
+                Table table = tableOption.get();
+                if (!(table instanceof OlapTable)) {
+                    continue;
+                }
+                OlapTable olapTable = (OlapTable) table;
+                TableStatsMeta statsStatus = findOrCreateTableStats(olapTable);
+                short replicaNum = olapTable.getTableProperty().getReplicaAllocation().getTotalReplicaNum();
+                Map<Long, Long> tabletRows = record.getValue();
+                if (tabletRows == null || tabletRows.isEmpty()) {
+                    LOG.info("Tablet row count map is empty");
+                    continue;
+                }
+                long rowsForAllReplica = 0;
+                for (Entry<Long, Long> entry : tabletRows.entrySet()) {
+                    rowsForAllReplica += entry.getValue();
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug("Table id {}, tablet id {}, row count {}",
+                                record.getKey(), entry.getKey(), entry.getValue());
                     }
                 }
+                long tableUpdateRows = rowsForAllReplica / replicaNum;
+                LOG.info("Update rows for table {} is {}, replicaNum is {}, "
+                        + "rows for all replica {}, tablets count {}",
+                        olapTable.getName(), tableUpdateRows, replicaNum, rowsForAllReplica, tabletRows.size());
+                statsStatus.updatedRows.addAndGet(tableUpdateRows);

Review Comment:
   [P2] Exclude temporary-partition rows from the normal-table fallback
   
   The tablet records also contain loads into temporary partitions: PublishVersionDaemon.getBaseTabletIdsForEachBe resolves their IDs through OlapTable.getPartition(long), which searches temporary partitions too. This increment counts all of them, but an ordinary OlapScan and getRowCountForIndex cover only normal partitions. On a never-analyzed range-partitioned DUP table, load 5 rows into a normal partition and 100 using INSERT INTO t TEMPORARY PARTITION(tp1) before the normal tablets' first report. The new record has collected count/baseline 0 and updatedRows=105, so Project(*) -> OlapScan(t, normal partitions) gets cardinality105 although its table contains only5 queryable rows. The later normal-only updatePartitionRows helper does not correct this global counter. Keep temporary staging rows out of the normal-table fallback, or derive that count from partition-specific deltas, and cover a temporary load alongside an unreported normal partition.



##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/AnalysisManager.java:
##########
@@ -1366,52 +1405,52 @@ public void replayUpdateRowsRecord(UpdateRowsEvent event) {
         // Record : TableId -> (TabletId -> update rows)
         if (event.getTabletRecords() != null) {
             for (Entry<Long, Map<Long, Long>> record : event.getTabletRecords().entrySet()) {
-                TableStatsMeta statsStatus = idToTblStats.get(record.getKey());
-                if (statsStatus != null) {
-                    Optional<Database> dbOption = catalog.getDb(event.getDbId());
-                    if (!dbOption.isPresent()) {
-                        LOG.warn("Database {} does not exist.", event.getDbId());
-                        continue;
-                    }
-                    Database database = dbOption.get();
-                    Optional<Table> tableOption = database.getTable(record.getKey());
-                    if (!tableOption.isPresent()) {
-                        LOG.warn("Table {} does not exist in DB {}.", record.getKey(), event.getDbId());
-                        continue;
-                    }
-                    Table table = tableOption.get();
-                    if (!(table instanceof OlapTable)) {
-                        continue;
-                    }
-                    OlapTable olapTable = (OlapTable) table;
-                    short replicaNum = olapTable.getTableProperty().getReplicaAllocation().getTotalReplicaNum();
-                    Map<Long, Long> tabletRows = record.getValue();
-                    if (tabletRows == null || tabletRows.isEmpty()) {
-                        LOG.info("Tablet row count map is empty");
-                        continue;
-                    }
-                    long rowsForAllReplica = 0;
-                    for (Entry<Long, Long> entry : tabletRows.entrySet()) {
-                        rowsForAllReplica += entry.getValue();
-                        if (LOG.isDebugEnabled()) {
-                            LOG.debug("Table id {}, tablet id {}, row count {}",
-                                    record.getKey(), entry.getKey(), entry.getValue());
-                        }
-                    }
-                    long tableUpdateRows = rowsForAllReplica / replicaNum;
-                    LOG.info("Update rows for table {} is {}, replicaNum is {}, "
-                            + "rows for all replica {}, tablets count {}",
-                            olapTable.getName(), tableUpdateRows, replicaNum, rowsForAllReplica, tabletRows.size());
-                    statsStatus.updatedRows.addAndGet(tableUpdateRows);
-                    if (StatisticsUtil.enablePartitionAnalyze()) {
-                        updatePartitionRows(olapTable, tabletRows, statsStatus, replicaNum);
+                Optional<Database> dbOption = catalog.getDb(event.getDbId());
+                if (!dbOption.isPresent()) {
+                    LOG.warn("Database {} does not exist.", event.getDbId());
+                    continue;
+                }
+                Database database = dbOption.get();
+                Optional<Table> tableOption = database.getTable(record.getKey());
+                if (!tableOption.isPresent()) {
+                    LOG.warn("Table {} does not exist in DB {}.", record.getKey(), event.getDbId());
+                    continue;
+                }
+                Table table = tableOption.get();
+                if (!(table instanceof OlapTable)) {
+                    continue;
+                }
+                OlapTable olapTable = (OlapTable) table;
+                TableStatsMeta statsStatus = findOrCreateTableStats(olapTable);
+                short replicaNum = olapTable.getTableProperty().getReplicaAllocation().getTotalReplicaNum();
+                Map<Long, Long> tabletRows = record.getValue();
+                if (tabletRows == null || tabletRows.isEmpty()) {
+                    LOG.info("Tablet row count map is empty");
+                    continue;
+                }
+                long rowsForAllReplica = 0;
+                for (Entry<Long, Long> entry : tabletRows.entrySet()) {
+                    rowsForAllReplica += entry.getValue();
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug("Table id {}, tablet id {}, row count {}",
+                                record.getKey(), entry.getKey(), entry.getValue());
                     }
                 }
+                long tableUpdateRows = rowsForAllReplica / replicaNum;
+                LOG.info("Update rows for table {} is {}, replicaNum is {}, "
+                        + "rows for all replica {}, tablets count {}",
+                        olapTable.getName(), tableUpdateRows, replicaNum, rowsForAllReplica, tabletRows.size());
+                statsStatus.updatedRows.addAndGet(tableUpdateRows);
+                if (StatisticsUtil.enablePartitionAnalyze()) {
+                    updatePartitionRows(olapTable, tabletRows, statsStatus, replicaNum);
+                }
             }
             return;
         }
 
-        // Handle truncate table
+        // Handle truncate table. A partition truncation is not a load: the rows it reports are the rows it

Review Comment:
   [P2] Invalidate the load-derived count when partitions are removed
   
   A partition truncation also reaches records created by a load, but the retained branch below adds the positive number of removed rows to updatedRows. For a never-analyzed DUP table with two reported 50-row partitions, the new record has a zero collected count/baseline and updatedRows=100. Truncating one partition and inserting 5 rows into its replacement makes this counter 155 although the table has 55 rows. The first load marks the replacement index's row count unreported, so an unpruned scan takes getRowCountWithDeltaRows and uses 155 as cardinality. DROP PARTITION similarly retains the historical count: dropping one 50-row partition, adding a replacement, and loading 5 yields 105 instead of 55 through the same fallback. Before this change an unanalyzed table had no record to turn these modification counts into cardinality. Invalidate or adjust the load-derived count when partitions are removed, including partial TRUNCATE and DROP PARTITION, and cover removal followed by load.



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6258752115

   run nonConcurrent


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6258749919

   run p0


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](fe) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6145645792

   
   Thank you for your contribution to Apache Doris.
   Don't know what should be done next? See [How to process your PR](https://doris.apache.org/community/how-to-contribute/review-and-ci).
   
   Please clearly describe your PR:
   1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
   2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
   3. What features were added. Why was this function added?
   4. Which code was refactored and why was this part of the code refactored?
   5. Which functions were optimized and what is the difference before and after the optimization?
       


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6160256613

   /review


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6161210502

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 152567 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit e2ce99b43fe1332fbad22f00c5019273a29bf94c, data reload: false
   
   query5	4322	585	459	459
   query6	426	218	193	193
   query7	4799	451	223	223
   query8	321	188	169	169
   query9	8749	3937	3973	3937
   query10	461	310	259	259
   query11	5899	3454	3175	3175
   query12	149	89	84	84
   query13	1273	460	335	335
   query14	6552	4751	4472	4472
   query14_1	4290	4284	4184	4184
   query15	214	216	188	188
   query16	951	450	430	430
   query17	866	679	538	538
   query18	2420	427	316	316
   query19	205	167	139	139
   query20	85	84	81	81
   query21	213	135	117	117
   query22	13084	13104	12879	12879
   query23	13339	12525	12122	12122
   query23_1	12250	12097	12097	12097
   query24	7117	823	463	463
   query24_1	478	484	471	471
   query25	543	425	370	370
   query26	1306	261	142	142
   query27	2766	452	274	274
   query28	4586	1940	1949	1940
   query29	1576	606	472	472
   query30	304	222	193	193
   query31	892	777	665	665
   query32	140	100	91	91
   query33	510	314	253	253
   query34	880	871	502	502
   query35	755	782	657	657
   query36	852	816	736	736
   query37	138	102	101	101
   query38	1794	1756	1701	1701
   query39	735	734	703	703
   query39_1	687	694	691	691
   query40	224	120	100	100
   query41	72	72	70	70
   query42	90	85	84	84
   query43	373	377	332	332
   query44	1320	707	726	707
   query45	184	194	164	164
   query46	845	967	585	585
   query47	2939	2926	2965	2926
   query48	306	316	226	226
   query49	609	398	306	306
   query50	689	274	213	213
   query51	10770	10582	10617	10582
   query52	82	79	69	69
   query53	187	206	154	154
   query54	232	191	172	172
   query55	72	67	62	62
   query56	226	213	205	205
   query57	1613	1575	1566	1566
   query58	283	260	250	250
   query59	2347	2383	2110	2110
   query60	280	236	216	216
   query61	149	142	140	140
   query62	411	349	286	286
   query63	190	165	162	162
   query64	2738	982	803	803
   query65	3438	3398	3405	3398
   query66	1802	417	296	296
   query67	20216	20165	20091	20091
   query68	2996	1012	619	619
   query69	380	283	284	283
   query70	919	830	814	814
   query71	284	235	210	210
   query72	2702	2550	2237	2237
   query73	510	528	303	303
   query74	4628	4489	4320	4320
   query75	2349	2321	1960	1960
   query76	2293	1008	595	595
   query77	367	398	301	301
   query78	9082	8961	8449	8449
   query79	965	833	513	513
   query80	1036	444	350	350
   query81	577	325	278	278
   query82	581	135	108	108
   query83	292	201	181	181
   query84	310	122	95	95
   query85	854	436	364	364
   query86	383	239	230	230
   query87	2028	1959	1862	1862
   query88	3662	2729	2677	2677
   query89	338	301	268	268
   query90	1847	182	183	182
   query91	160	152	123	123
   query92	90	87	87	87
   query93	1005	984	571	571
   query94	580	317	285	285
   query95	586	364	383	364
   query96	662	530	244	244
   query97	2441	2485	2357	2357
   query98	159	147	147	147
   query99	799	793	656	656
   Total cold run time: 235196 ms
   Total hot run time: 152567 ms
   ```
   </details>
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6362941517

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 149791 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit 4c792cff11ff0151e7af0174bf908dcb0d8a056c, data reload: false
   
   query5	4331	584	442	442
   query6	429	205	193	193
   query7	4796	440	245	245
   query8	328	165	146	146
   query9	8778	3068	3070	3068
   query10	491	311	259	259
   query11	5944	3818	3564	3564
   query12	147	95	84	84
   query13	1251	423	316	316
   query14	6102	3917	3617	3617
   query14_1	3398	3416	3403	3403
   query15	205	196	184	184
   query16	943	464	422	422
   query17	858	658	525	525
   query18	2430	429	311	311
   query19	199	166	138	138
   query20	85	82	79	79
   query21	220	124	106	106
   query22	12883	12878	12803	12803
   query23	13595	12943	12357	12357
   query23_1	12692	12638	12474	12474
   query24	7356	803	467	467
   query24_1	470	480	476	476
   query25	531	423	362	362
   query26	1267	255	141	141
   query27	2781	440	271	271
   query28	4604	1966	1945	1945
   query29	1561	601	463	463
   query30	330	218	184	184
   query31	1003	928	816	816
   query32	197	93	91	91
   query33	536	327	229	229
   query34	878	848	499	499
   query35	733	740	652	652
   query36	818	822	714	714
   query37	118	97	79	79
   query38	1759	1769	1642	1642
   query39	795	781	728	728
   query39_1	728	732	736	732
   query40	219	110	91	91
   query41	67	61	60	60
   query42	83	79	81	79
   query43	334	354	304	304
   query44	1298	677	702	677
   query45	175	190	164	164
   query46	813	956	582	582
   query47	2725	3005	2730	2730
   query48	288	294	216	216
   query49	586	396	291	291
   query50	656	268	211	211
   query51	10338	10258	10282	10258
   query52	88	84	74	74
   query53	181	214	146	146
   query54	245	197	170	170
   query55	70	64	64	64
   query56	257	224	188	188
   query57	1665	1606	1577	1577
   query58	294	260	256	256
   query59	2124	2163	1919	1919
   query60	288	239	224	224
   query61	144	142	141	141
   query62	387	331	286	286
   query63	184	152	157	152
   query64	2733	906	717	717
   query65	3314	3245	3254	3245
   query66	1763	414	312	312
   query67	19999	20158	19770	19770
   query68	2945	985	602	602
   query69	391	287	238	238
   query70	816	779	766	766
   query71	298	233	207	207
   query72	2712	2578	2331	2331
   query73	515	525	289	289
   query74	4620	4522	4361	4361
   query75	2338	2307	1902	1902
   query76	2360	996	566	566
   query77	352	365	261	261
   query78	9926	9707	9123	9123
   query79	907	900	520	520
   query80	534	410	355	355
   query81	534	322	274	274
   query82	863	129	103	103
   query83	312	189	176	176
   query84	322	122	101	101
   query85	825	412	362	362
   query86	316	238	232	232
   query87	1959	1954	1804	1804
   query88	3705	2652	2652	2652
   query89	325	280	237	237
   query90	1869	162	165	162
   query91	162	152	128	128
   query92	98	77	79	77
   query93	1020	984	564	564
   query94	442	271	268	268
   query95	577	324	352	324
   query96	651	484	229	229
   query97	2485	2427	2335	2335
   query98	157	150	147	147
   query99	780	756	647	647
   Total cold run time: 233702 ms
   Total hot run time: 149791 ms
   ```
   </details>
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6319883205

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 150505 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit 9de2254950e70969946a99e8ce2b9a69e1d9bf6f, data reload: false
   
   query5	4335	577	446	446
   query6	419	202	198	198
   query7	4791	441	222	222
   query8	324	161	141	141
   query9	8775	3062	3056	3056
   query10	489	305	246	246
   query11	5995	3857	3573	3573
   query12	159	101	86	86
   query13	1246	443	316	316
   query14	6108	3905	3659	3659
   query14_1	3456	3432	3438	3432
   query15	208	190	184	184
   query16	951	416	402	402
   query17	847	664	533	533
   query18	2426	431	309	309
   query19	210	165	136	136
   query20	85	98	85	85
   query21	210	129	106	106
   query22	12984	13128	12765	12765
   query23	13457	12787	12523	12523
   query23_1	12532	12701	12667	12667
   query24	7403	804	464	464
   query24_1	471	477	474	474
   query25	541	424	355	355
   query26	1260	261	142	142
   query27	2766	432	270	270
   query28	4653	1921	1937	1921
   query29	1575	595	446	446
   query30	294	214	189	189
   query31	1006	924	818	818
   query32	142	91	93	91
   query33	526	300	247	247
   query34	926	844	490	490
   query35	733	785	638	638
   query36	822	875	715	715
   query37	134	100	84	84
   query38	1771	1729	1649	1649
   query39	800	809	747	747
   query39_1	738	721	741	721
   query40	222	116	99	99
   query41	72	70	67	67
   query42	90	89	82	82
   query43	345	366	310	310
   query44	1293	687	700	687
   query45	181	173	163	163
   query46	826	964	567	567
   query47	3014	2783	2950	2783
   query48	309	309	219	219
   query49	577	405	294	294
   query50	687	318	202	202
   query51	10267	10439	10220	10220
   query52	82	79	68	68
   query53	182	205	149	149
   query54	242	184	181	181
   query55	74	72	61	61
   query56	239	214	231	214
   query57	1658	1628	1572	1572
   query58	283	264	262	262
   query59	2135	2218	1999	1999
   query60	302	250	224	224
   query61	143	148	141	141
   query62	384	339	295	295
   query63	192	152	155	152
   query64	3361	919	831	831
   query65	3318	3275	3248	3248
   query66	1782	424	305	305
   query67	20117	19857	19807	19807
   query68	2999	983	592	592
   query69	380	313	247	247
   query70	858	775	740	740
   query71	299	234	215	215
   query72	2671	2318	2298	2298
   query73	511	512	293	293
   query74	4582	4519	4370	4370
   query75	2352	2286	1908	1908
   query76	2317	1007	573	573
   query77	344	384	282	282
   query78	9861	9752	9164	9164
   query79	1001	830	502	502
   query80	1252	424	351	351
   query81	583	316	268	268
   query82	612	135	108	108
   query83	307	192	173	173
   query84	310	117	102	102
   query85	876	425	372	372
   query86	389	240	231	231
   query87	2024	1925	1788	1788
   query88	3598	2640	2631	2631
   query89	321	272	244	244
   query90	1933	167	163	163
   query91	166	151	132	132
   query92	96	82	86	82
   query93	1030	972	566	566
   query94	641	314	260	260
   query95	575	329	366	329
   query96	658	470	230	230
   query97	2450	2420	2319	2319
   query98	161	177	147	147
   query99	763	765	652	652
   Total cold run time: 235626 ms
   Total hot run time: 150505 ms
   ```
   </details>
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6323057167

   # FE Regression Coverage Report
   Increment line coverage `  74.36% (87/117)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/69028_9de2254950e70969946a99e8ce2b9a69e1d9bf6f_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/69028_9de2254950e70969946a99e8ce2b9a69e1d9bf6f_merge_fe/report/index.html)
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4312279362


##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/TableStatsMeta.java:
##########
@@ -194,6 +273,21 @@ public void update(AnalysisInfo analyzedJob, TableIf tableIf) {
         if (tableIf != null) {
             if (tableIf instanceof OlapTable) {
                 OlapTable olapTable = (OlapTable) tableIf;
+                // The collected row counts published below already include the rows which had been loaded when
+                // the job was built, remember how many they were, they are not delta rows. The baseline may
+                // only advance together with the collected base index row count, an analysis of another index
+                // (a materialized view) doesn't touch it.
+                // NOTICE: the baseline is published before the collected counts. They are both read without
+                // the table lock by the planner, and a reader which pairs the collected count of this analysis
+                // with the baseline of the previous one counts the rows loaded since that previous analysis
+                // twice.
+                if (analyzedJob.indexesRowCount.containsKey(olapTable.getBaseIndexId())) {
+                    // A row count supplied by the user replaces the collected one and describes the table as
+                    // of now, so the rows loaded so far are part of it and the baseline has to move to the
+                    // current value. Keeping the older baseline would count those rows again once DROP STATS
+                    // clears userInjected.
+                    updatedRowsBase.set(analyzedJob.userInject ? updatedRows.get() : analyzedJob.updateRows);

Review Comment:
   这是老代码固有问题, 设计使用deltaRow时就已知有这个缺陷. 提了新issue 跟踪 https://github.com/apache/doris/issues/69280



##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/AnalysisManager.java:
##########
@@ -1366,52 +1405,52 @@ public void replayUpdateRowsRecord(UpdateRowsEvent event) {
         // Record : TableId -> (TabletId -> update rows)
         if (event.getTabletRecords() != null) {
             for (Entry<Long, Map<Long, Long>> record : event.getTabletRecords().entrySet()) {
-                TableStatsMeta statsStatus = idToTblStats.get(record.getKey());
-                if (statsStatus != null) {
-                    Optional<Database> dbOption = catalog.getDb(event.getDbId());
-                    if (!dbOption.isPresent()) {
-                        LOG.warn("Database {} does not exist.", event.getDbId());
-                        continue;
-                    }
-                    Database database = dbOption.get();
-                    Optional<Table> tableOption = database.getTable(record.getKey());
-                    if (!tableOption.isPresent()) {
-                        LOG.warn("Table {} does not exist in DB {}.", record.getKey(), event.getDbId());
-                        continue;
-                    }
-                    Table table = tableOption.get();
-                    if (!(table instanceof OlapTable)) {
-                        continue;
-                    }
-                    OlapTable olapTable = (OlapTable) table;
-                    short replicaNum = olapTable.getTableProperty().getReplicaAllocation().getTotalReplicaNum();
-                    Map<Long, Long> tabletRows = record.getValue();
-                    if (tabletRows == null || tabletRows.isEmpty()) {
-                        LOG.info("Tablet row count map is empty");
-                        continue;
-                    }
-                    long rowsForAllReplica = 0;
-                    for (Entry<Long, Long> entry : tabletRows.entrySet()) {
-                        rowsForAllReplica += entry.getValue();
-                        if (LOG.isDebugEnabled()) {
-                            LOG.debug("Table id {}, tablet id {}, row count {}",
-                                    record.getKey(), entry.getKey(), entry.getValue());
-                        }
-                    }
-                    long tableUpdateRows = rowsForAllReplica / replicaNum;
-                    LOG.info("Update rows for table {} is {}, replicaNum is {}, "
-                            + "rows for all replica {}, tablets count {}",
-                            olapTable.getName(), tableUpdateRows, replicaNum, rowsForAllReplica, tabletRows.size());
-                    statsStatus.updatedRows.addAndGet(tableUpdateRows);
-                    if (StatisticsUtil.enablePartitionAnalyze()) {
-                        updatePartitionRows(olapTable, tabletRows, statsStatus, replicaNum);
+                Optional<Database> dbOption = catalog.getDb(event.getDbId());
+                if (!dbOption.isPresent()) {
+                    LOG.warn("Database {} does not exist.", event.getDbId());
+                    continue;
+                }
+                Database database = dbOption.get();
+                Optional<Table> tableOption = database.getTable(record.getKey());
+                if (!tableOption.isPresent()) {
+                    LOG.warn("Table {} does not exist in DB {}.", record.getKey(), event.getDbId());
+                    continue;
+                }
+                Table table = tableOption.get();
+                if (!(table instanceof OlapTable)) {
+                    continue;
+                }
+                OlapTable olapTable = (OlapTable) table;
+                TableStatsMeta statsStatus = findOrCreateTableStats(olapTable);
+                short replicaNum = olapTable.getTableProperty().getReplicaAllocation().getTotalReplicaNum();
+                Map<Long, Long> tabletRows = record.getValue();
+                if (tabletRows == null || tabletRows.isEmpty()) {
+                    LOG.info("Tablet row count map is empty");
+                    continue;
+                }
+                long rowsForAllReplica = 0;
+                for (Entry<Long, Long> entry : tabletRows.entrySet()) {
+                    rowsForAllReplica += entry.getValue();
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug("Table id {}, tablet id {}, row count {}",
+                                record.getKey(), entry.getKey(), entry.getValue());
                     }
                 }
+                long tableUpdateRows = rowsForAllReplica / replicaNum;
+                LOG.info("Update rows for table {} is {}, replicaNum is {}, "
+                        + "rows for all replica {}, tablets count {}",
+                        olapTable.getName(), tableUpdateRows, replicaNum, rowsForAllReplica, tabletRows.size());
+                statsStatus.updatedRows.addAndGet(tableUpdateRows);
+                if (StatisticsUtil.enablePartitionAnalyze()) {
+                    updatePartitionRows(olapTable, tabletRows, statsStatus, replicaNum);
+                }
             }
             return;
         }
 
-        // Handle truncate table
+        // Handle truncate table. A partition truncation is not a load: the rows it reports are the rows it

Review Comment:
   这是老代码固有问题, 设计使用deltaRow时就已知有这个缺陷. 提了新issue 跟踪 https://github.com/apache/doris/issues/69280



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6161270848

   
   <details>
   <summary>ClickBench: <b>Total hot run time: 24.62 s</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
   ClickBench test result on commit e2ce99b43fe1332fbad22f00c5019273a29bf94c, data reload: false
   
   query1	0.01	0.01	0.01
   query2	0.10	0.06	0.05
   query3	0.28	0.12	0.11
   query4	1.61	0.12	0.12
   query5	0.28	0.25	0.25
   query6	1.21	0.68	0.69
   query7	0.04	0.01	0.01
   query8	0.05	0.04	0.04
   query9	0.43	0.35	0.36
   query10	0.58	0.58	0.58
   query11	0.20	0.14	0.13
   query12	0.18	0.14	0.15
   query13	0.50	0.50	0.51
   query14	0.98	0.98	0.98
   query15	0.64	0.60	0.63
   query16	0.36	0.36	0.35
   query17	1.14	1.14	1.10
   query18	0.22	0.21	0.22
   query19	2.12	1.92	1.96
   query20	0.02	0.01	0.02
   query21	15.49	0.27	0.14
   query22	4.53	0.06	0.05
   query23	16.25	0.31	0.12
   query24	3.40	0.58	0.55
   query25	0.10	0.05	0.05
   query26	1.36	0.24	0.15
   query27	0.04	0.04	0.03
   query28	2.34	0.61	0.41
   query29	12.50	4.18	3.29
   query30	0.28	0.14	0.14
   query31	2.77	0.57	0.35
   query32	3.23	0.62	0.52
   query33	3.39	3.18	3.25
   query34	15.52	4.00	3.38
   query35	3.35	3.34	3.34
   query36	0.57	0.43	0.46
   query37	0.10	0.06	0.07
   query38	0.05	0.04	0.03
   query39	0.04	0.03	0.03
   query40	0.17	0.14	0.14
   query41	0.08	0.03	0.03
   query42	0.04	0.03	0.03
   query43	0.04	0.03	0.04
   Total cold run time: 96.59 s
   Total hot run time: 24.62 s
   ```
   </details>
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6163640709

   # FE UT Coverage Report
   Increment line coverage `  65.55% (78/119)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/69028_e2ce99b43fe1332fbad22f00c5019273a29bf94c/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/69028_e2ce99b43fe1332fbad22f00c5019273a29bf94c/fe_report/index.html)
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6343595782

   # FE UT Coverage Report
   Increment line coverage `  71.21% (94/132)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/69028_70184f06f222076c5ba0b094186ccf27d09fc006/fe_increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/69028_70184f06f222076c5ba0b094186ccf27d09fc006/fe_report/index.html)
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6335201465

   run buildall


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6354755601

   /review


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6313983451

   run buildall


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6342191781

   
   <details>
   <summary>TPC-DS: <b>Total hot run time: 150642 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
   TPC-DS sf100 test result on commit 70184f06f222076c5ba0b094186ccf27d09fc006, data reload: false
   
   query5	4328	600	449	449
   query6	416	211	194	194
   query7	4812	443	223	223
   query8	336	158	146	146
   query9	8787	3119	3102	3102
   query10	478	311	253	253
   query11	5807	3830	3586	3586
   query12	143	95	86	86
   query13	1260	434	328	328
   query14	6058	3924	3621	3621
   query14_1	3445	3389	3479	3389
   query15	211	210	182	182
   query16	957	474	420	420
   query17	869	672	546	546
   query18	2438	426	316	316
   query19	196	166	137	137
   query20	86	86	91	86
   query21	212	127	107	107
   query22	12903	12946	12830	12830
   query23	13590	12870	12429	12429
   query23_1	12533	12502	12458	12458
   query24	7351	837	474	474
   query24_1	472	485	482	482
   query25	582	417	371	371
   query26	1273	262	136	136
   query27	2787	449	277	277
   query28	4602	1970	1971	1970
   query29	1577	614	461	461
   query30	305	225	185	185
   query31	1015	935	824	824
   query32	155	96	95	95
   query33	523	336	236	236
   query34	894	846	482	482
   query35	725	761	655	655
   query36	810	803	715	715
   query37	121	97	80	80
   query38	1778	1706	1648	1648
   query39	800	793	746	746
   query39_1	738	729	754	729
   query40	218	119	96	96
   query41	66	62	61	61
   query42	86	83	79	79
   query43	345	353	306	306
   query44	1314	680	691	680
   query45	176	169	167	167
   query46	848	957	581	581
   query47	2865	2877	2937	2877
   query48	290	308	221	221
   query49	571	410	303	303
   query50	683	272	200	200
   query51	10468	10280	10457	10280
   query52	80	80	73	73
   query53	185	220	154	154
   query54	241	189	176	176
   query55	75	72	68	68
   query56	241	217	219	217
   query57	1714	1625	1561	1561
   query58	288	269	301	269
   query59	2068	2177	1895	1895
   query60	288	260	230	230
   query61	149	148	149	148
   query62	384	331	309	309
   query63	188	155	158	155
   query64	2725	968	794	794
   query65	3323	3286	3317	3286
   query66	1782	419	297	297
   query67	20175	20050	19890	19890
   query68	3268	1014	607	607
   query69	404	292	250	250
   query70	840	780	761	761
   query71	294	238	207	207
   query72	2823	2631	2265	2265
   query73	525	531	294	294
   query74	4651	4534	4359	4359
   query75	2352	2281	1910	1910
   query76	2412	1023	583	583
   query77	343	385	275	275
   query78	9598	9752	9154	9154
   query79	1015	854	525	525
   query80	564	424	342	342
   query81	548	314	270	270
   query82	821	135	106	106
   query83	305	206	190	190
   query84	328	128	108	108
   query85	945	515	380	380
   query86	338	241	215	215
   query87	2033	1979	1780	1780
   query88	3670	2712	2681	2681
   query89	336	278	244	244
   query90	1848	170	169	169
   query91	166	151	129	129
   query92	106	85	93	85
   query93	1060	1013	591	591
   query94	448	303	258	258
   query95	588	329	351	329
   query96	669	478	235	235
   query97	2469	2428	2308	2308
   query98	162	153	147	147
   query99	768	751	666	666
   Total cold run time: 234552 ms
   Total hot run time: 150642 ms
   ```
   </details>
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4356778779


##########
fe/fe-core/src/main/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgr.java:
##########
@@ -545,7 +554,7 @@ public void afterCommitTxnResp(CommitTxnResponse commitTxnResponse, List<TabletC
             }
         }
         Env env = Env.getCurrentEnv();
-        env.getAnalysisManager().updateUpdatedRows(updatedRows);
+        env.getAnalysisManager().updateUpdatedRows(updatedRows, dbId, reportedPartitions);

Review Comment:
   不修. 这是cloud模式在原代码上已有的问题. 这个pr 修复了3种commit 里最常见的1种. 对另外两种没有改进也没有损坏. 没有处理的两种commit:
   1. FE 显式事务(BEGIN; INSERT ...; COMMIT;
   2. BE 直连提交(enable_stream_load_commit_txn_on_be=true 的非 MoW/non-TSO stream load )



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "yujun777 (via GitHub)" <gi...@apache.org>.
yujun777 commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6463338945

   ### TRUNCATE removes the stats record without journaling the deletion, so the record a load creates now diverges between the master and its followers
   
   This is the one removal site that is not ordered with a journal entry, and it is the scenario this PR is about.
   
   **The gap.** A whole table `TRUNCATE` drops the record in memory only — `InternalCatalog.java:3930` calls `AnalysisManager.removeTableStats(olapTable.getId())` and never calls `logDeleteTableStats`, and `InternalCatalog.replayTruncateTable` (`InternalCatalog.java:3998`) only replaces the partitions and fixes up the `TabletInvertedIndex`, so a follower replaying `OP_TRUNCATE_TABLE` keeps the record. The other removals this PR orders are all paired with something the followers replay: a whole table `DROP STATS` journals `DELETE_TABLE_STATS` (now via `removeTableStatsAndLog`), and a schema change is paired with the `FINISHED` entry through `EditLog.java:972`. `TRUNCATE` has no counterpart at all.
   
   **Why it becomes a defect here.** Before this change a follower could only ever hold a record that an analyze had produced, so an unjournaled removal merely left both sides with different *contents* of an old record. Now `replayUpdateRowsRecord` creates the record of a table that has none, and the rows it accumulates are journaled: `TransactionState.tableIdToTabletDeltaRows` is `@SerializedName("deltaRows")` (`TransactionState.java:289`), so `replayUpsertTransactionState` -> `updateCatalogAfterVisible` recreates the record on every follower as well. The master's record is therefore no longer the one the followers have.
   
   For a table that was never analyzed, following the reproduction in the PR description:
   
   | step | master | follower |
   | --- | --- | --- |
   | load 50,000,000 rows | record created: `rowCount=0`, `updatedRowsBase=0`, `updatedRows=5e7` | same (replayed) |
   | `TRUNCATE TABLE t` | record removed, **nothing journaled** | record kept, `updatedRows=5e7` |
   | load 3 rows | record recreated: `updatedRows=3` -> scan at 3 | accumulates -> `updatedRows=50000003` -> scan at 50,000,003 |
   
   Before this PR both frontends agreed on the (wrong) one-row estimate for such a table; now the master is right and every follower plans a three-row table as if it still held the rows the truncation removed.
   
   **Impact.** A follower coordinates and plans queries on its own, so in a multi-FE cluster the same statement gets a different join order and a different broadcast/shuffle choice depending on which frontend it lands on, for up to `tablet_stat_update_interval_second` while the new tablets are unreported. The residue outlives that window: `updatedRows` is only rebased by an analyze, so the follower keeps reporting the pre-truncate counter as `Statistics.deltaRowCount`, which `FilterEstimation.java:94` uses as the population to retry a zero estimate with, until a whole table `ANALYZE` converges the two sides (`replayUpdateTableStatsStatus` replaces the record). If the follower is promoted, its divergent record becomes authoritative. Row visibility and delete bitmaps are unaffected — this is a cardinality-only defect.
   
   **Suggested fix.** `truncateTable` already holds the table write lock from before the removal until after `logTruncateTable`, which is exactly the critical section this PR requires a removal to live in, so it should go through the new helper:
   
   ```java
   // InternalCatalog.truncateTable, the truncateEntireTable branch
   Env.getCurrentEnv().getAnalysisManager().removeTableStatsAndLog(olapTable);
   ```
   
   The `writeLock()` inside the helper is reentrant, and a follower replaying `DELETE_TABLE_STATS` for a record it does not have is a no-op `remove`. The journal order then becomes `[VISIBLE create] -> [DELETE] -> [VISIBLE create]`, which matches the order the master applies them in.
   
   For coverage, `AnalysisManagerTest#testCloudRowUpdateIsSerializedWithTheTableLock` is the shape to copy — assert the removal and its journal entry appear paired on the truncate path. `Alter.java:858` looks like another removal of this class (the original table of an alter that does not swap) and may be worth checking in the same pass.
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6319685936

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 28634 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit 9de2254950e70969946a99e8ce2b9a69e1d9bf6f, data reload: false
   
   ------ Round 1 ----------------------------------
   ============================================
   q1	17589	5137	5126	5126
   q2	2117	302	257	257
   q3	10251	1357	818	818
   q4	4681	448	332	332
   q5	7413	787	511	511
   q6	177	185	150	150
   q7	725	784	585	585
   q8	9246	1273	1068	1068
   q9	5399	4168	4158	4158
   q10	6824	1281	1012	1012
   q11	413	250	237	237
   q12	638	394	279	279
   q13	18106	3532	2946	2946
   q14	274	267	237	237
   q15	q16	715	707	649	649
   q17	1170	745	570	570
   q18	6909	6162	6106	6106
   q19	1097	957	568	568
   q20	377	325	226	226
   q21	4493	2524	2524	2524
   q22	376	328	275	275
   Total cold run time: 98990 ms
   Total hot run time: 28634 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   ============================================
   q1	5876	5830	5850	5830
   q2	696	610	551	551
   q3	5529	5699	5232	5232
   q4	2039	2146	1559	1559
   q5	5967	5672	5735	5672
   q6	241	187	145	145
   q7	2206	2008	1794	1794
   q8	2983	2566	2559	2559
   q9	7841	7533	7432	7432
   q10	3634	3564	3146	3146
   q11	578	404	376	376
   q12	654	656	504	504
   q13	3234	3555	3004	3004
   q14	278	283	255	255
   q15	q16	649	680	612	612
   q17	7721	6997	6863	6863
   q18	12989	12194	12866	12194
   q19	867	762	753	753
   q20	2156	2130	1934	1934
   q21	5597	4575	4663	4575
   q22	488	447	417	417
   Total cold run time: 72223 ms
   Total hot run time: 65407 ms
   ```
   </details>
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6319998889

   
   <details>
   <summary>ClickBench: <b>Total hot run time: 24.32 s</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
   ClickBench test result on commit 9de2254950e70969946a99e8ce2b9a69e1d9bf6f, data reload: false
   
   query1	0.01	0.00	0.00
   query2	0.10	0.05	0.05
   query3	0.26	0.09	0.09
   query4	1.61	0.09	0.10
   query5	0.23	0.24	0.23
   query6	1.20	0.69	0.69
   query7	0.03	0.01	0.00
   query8	0.06	0.04	0.04
   query9	0.40	0.34	0.33
   query10	0.61	0.58	0.61
   query11	0.20	0.14	0.14
   query12	0.18	0.14	0.14
   query13	0.51	0.50	0.50
   query14	1.02	1.03	1.00
   query15	0.62	0.60	0.61
   query16	0.34	0.35	0.34
   query17	1.09	1.12	1.12
   query18	0.19	0.19	0.19
   query19	2.13	2.02	2.02
   query20	0.02	0.01	0.02
   query21	15.48	0.18	0.14
   query22	4.92	0.06	0.05
   query23	16.31	0.32	0.13
   query24	3.24	0.56	0.40
   query25	0.11	0.06	0.04
   query26	1.44	0.22	0.16
   query27	0.05	0.04	0.05
   query28	2.75	0.69	0.36
   query29	12.48	4.15	3.23
   query30	0.29	0.13	0.10
   query31	2.78	0.55	0.32
   query32	3.22	0.59	0.51
   query33	3.23	3.28	3.19
   query34	15.41	3.98	3.41
   query35	3.32	3.31	3.32
   query36	0.54	0.42	0.41
   query37	0.09	0.07	0.06
   query38	0.05	0.04	0.03
   query39	0.04	0.03	0.03
   query40	0.17	0.15	0.15
   query41	0.09	0.03	0.03
   query42	0.04	0.04	0.03
   query43	0.04	0.04	0.03
   Total cold run time: 96.9 s
   Total hot run time: 24.32 s
   ```
   </details>
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6345986179

   # FE Regression Coverage Report
   Increment line coverage `  47.39% (100/211)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/69028_70184f06f222076c5ba0b094186ccf27d09fc006_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/69028_70184f06f222076c5ba0b094186ccf27d09fc006_merge_fe/report/index.html)
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6358947109

   run buildall


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6262443385

   run p0


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6258748700

   /review


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6264549195

   run compile


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6271372147

   # FE Regression Coverage Report
   Increment line coverage `  74.36% (87/117)` :tada:
   [Increment coverage report](http://coverage.selectdb-in.cc/coverage/69028_c56107d36aec1854e5431cd7336d253041520d9b_merge_fe/increment_report/index.html)
   [Complete coverage report](http://coverage.selectdb-in.cc/coverage/69028_c56107d36aec1854e5431cd7336d253041520d9b_merge_fe/report/index.html)
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4344419105


##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/AnalysisManager.java:
##########
@@ -1366,52 +1405,52 @@ public void replayUpdateRowsRecord(UpdateRowsEvent event) {
         // Record : TableId -> (TabletId -> update rows)
         if (event.getTabletRecords() != null) {
             for (Entry<Long, Map<Long, Long>> record : event.getTabletRecords().entrySet()) {
-                TableStatsMeta statsStatus = idToTblStats.get(record.getKey());
-                if (statsStatus != null) {
-                    Optional<Database> dbOption = catalog.getDb(event.getDbId());
-                    if (!dbOption.isPresent()) {
-                        LOG.warn("Database {} does not exist.", event.getDbId());
-                        continue;
-                    }
-                    Database database = dbOption.get();
-                    Optional<Table> tableOption = database.getTable(record.getKey());
-                    if (!tableOption.isPresent()) {
-                        LOG.warn("Table {} does not exist in DB {}.", record.getKey(), event.getDbId());
-                        continue;
-                    }
-                    Table table = tableOption.get();
-                    if (!(table instanceof OlapTable)) {
-                        continue;
-                    }
-                    OlapTable olapTable = (OlapTable) table;
-                    short replicaNum = olapTable.getTableProperty().getReplicaAllocation().getTotalReplicaNum();
-                    Map<Long, Long> tabletRows = record.getValue();
-                    if (tabletRows == null || tabletRows.isEmpty()) {
-                        LOG.info("Tablet row count map is empty");
-                        continue;
-                    }
-                    long rowsForAllReplica = 0;
-                    for (Entry<Long, Long> entry : tabletRows.entrySet()) {
-                        rowsForAllReplica += entry.getValue();
-                        if (LOG.isDebugEnabled()) {
-                            LOG.debug("Table id {}, tablet id {}, row count {}",
-                                    record.getKey(), entry.getKey(), entry.getValue());
-                        }
-                    }
-                    long tableUpdateRows = rowsForAllReplica / replicaNum;
-                    LOG.info("Update rows for table {} is {}, replicaNum is {}, "
-                            + "rows for all replica {}, tablets count {}",
-                            olapTable.getName(), tableUpdateRows, replicaNum, rowsForAllReplica, tabletRows.size());
-                    statsStatus.updatedRows.addAndGet(tableUpdateRows);
-                    if (StatisticsUtil.enablePartitionAnalyze()) {
-                        updatePartitionRows(olapTable, tabletRows, statsStatus, replicaNum);
+                Optional<Database> dbOption = catalog.getDb(event.getDbId());
+                if (!dbOption.isPresent()) {
+                    LOG.warn("Database {} does not exist.", event.getDbId());
+                    continue;
+                }
+                Database database = dbOption.get();
+                Optional<Table> tableOption = database.getTable(record.getKey());
+                if (!tableOption.isPresent()) {
+                    LOG.warn("Table {} does not exist in DB {}.", record.getKey(), event.getDbId());
+                    continue;
+                }
+                Table table = tableOption.get();
+                if (!(table instanceof OlapTable)) {
+                    continue;
+                }
+                OlapTable olapTable = (OlapTable) table;
+                TableStatsMeta statsStatus = findOrCreateTableStats(olapTable);

Review Comment:
   drop stats 需要加锁



##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/AnalysisManager.java:
##########
@@ -1276,6 +1276,45 @@ public TableStatsMeta findTableStatsStatus(long tblId) {
         return idToTblStats.get(tblId);
     }
 
+    /**
+     * The record of the table, created in the state of a table whose row count has never been collected when
+     * it doesn't exist yet. A truncation, a whole table DROP STATS and a schema change remove the record, so
+     * the rows which are loaded afterwards can only be turned into the row count of the table while the
+     * record exists: the planner falls back to it exactly while the backends have not reported the row count
+     * of the new tablets yet, which is the interval right after those operations.
+     *
+     * <p>Loads run this from the transaction visibility of the load, both on the master
+     * ({@code DatabaseTransactionMgr.updateCatalogAfterVisible}) and while a follower replays the transaction
+     * state, so the record exists on every frontend without a journal entry of its own.
+     *
+     * @return the existing or the created record, or null when the table cannot be resolved any more
+     */
+    private TableStatsMeta findOrCreateTableStats(InternalCatalog catalog, long dbId, long tableId) {
+        TableStatsMeta tableStats = idToTblStats.get(tableId);
+        if (tableStats != null) {
+            return tableStats;
+        }
+        Optional<Database> dbOption = catalog.getDb(dbId);
+        if (!dbOption.isPresent()) {
+            return null;
+        }
+        Optional<Table> tableOption = dbOption.get().getTable(tableId);
+        if (!tableOption.isPresent() || !(tableOption.get() instanceof OlapTable)) {
+            return null;
+        }
+        return findOrCreateTableStats((OlapTable) tableOption.get());
+    }
+
+    private TableStatsMeta findOrCreateTableStats(OlapTable table) {
+        TableStatsMeta tableStats = idToTblStats.get(table.getId());
+        if (tableStats != null) {
+            return tableStats;
+        }
+        TableStatsMeta created = new TableStatsMeta(table);
+        TableStatsMeta existing = idToTblStats.putIfAbsent(table.getId(), created);

Review Comment:
   drop stats 需要加锁



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4344413029


##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/AnalysisManager.java:
##########
@@ -1276,6 +1276,45 @@ public TableStatsMeta findTableStatsStatus(long tblId) {
         return idToTblStats.get(tblId);
     }
 
+    /**
+     * The record of the table, created in the state of a table whose row count has never been collected when
+     * it doesn't exist yet. A truncation, a whole table DROP STATS and a schema change remove the record, so
+     * the rows which are loaded afterwards can only be turned into the row count of the table while the
+     * record exists: the planner falls back to it exactly while the backends have not reported the row count
+     * of the new tablets yet, which is the interval right after those operations.
+     *
+     * <p>Loads run this from the transaction visibility of the load, both on the master
+     * ({@code DatabaseTransactionMgr.updateCatalogAfterVisible}) and while a follower replays the transaction
+     * state, so the record exists on every frontend without a journal entry of its own.
+     *
+     * @return the existing or the created record, or null when the table cannot be resolved any more
+     */
+    private TableStatsMeta findOrCreateTableStats(InternalCatalog catalog, long dbId, long tableId) {
+        TableStatsMeta tableStats = idToTblStats.get(tableId);
+        if (tableStats != null) {
+            return tableStats;
+        }
+        Optional<Database> dbOption = catalog.getDb(dbId);
+        if (!dbOption.isPresent()) {
+            return null;
+        }
+        Optional<Table> tableOption = dbOption.get().getTable(tableId);
+        if (!tableOption.isPresent() || !(tableOption.get() instanceof OlapTable)) {
+            return null;
+        }
+        return findOrCreateTableStats((OlapTable) tableOption.get());
+    }
+
+    private TableStatsMeta findOrCreateTableStats(OlapTable table) {
+        TableStatsMeta tableStats = idToTblStats.get(table.getId());
+        if (tableStats != null) {
+            return tableStats;
+        }
+        TableStatsMeta created = new TableStatsMeta(table);
+        TableStatsMeta existing = idToTblStats.putIfAbsent(table.getId(), created);

Review Comment:
   drop stats 需要加锁



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4345353934


##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/AnalysisManager.java:
##########
@@ -1276,6 +1276,45 @@ public TableStatsMeta findTableStatsStatus(long tblId) {
         return idToTblStats.get(tblId);
     }
 
+    /**
+     * The record of the table, created in the state of a table whose row count has never been collected when
+     * it doesn't exist yet. A truncation, a whole table DROP STATS and a schema change remove the record, so
+     * the rows which are loaded afterwards can only be turned into the row count of the table while the
+     * record exists: the planner falls back to it exactly while the backends have not reported the row count
+     * of the new tablets yet, which is the interval right after those operations.
+     *
+     * <p>Loads run this from the transaction visibility of the load, both on the master
+     * ({@code DatabaseTransactionMgr.updateCatalogAfterVisible}) and while a follower replays the transaction
+     * state, so the record exists on every frontend without a journal entry of its own.
+     *
+     * @return the existing or the created record, or null when the table cannot be resolved any more
+     */
+    private TableStatsMeta findOrCreateTableStats(InternalCatalog catalog, long dbId, long tableId) {
+        TableStatsMeta tableStats = idToTblStats.get(tableId);
+        if (tableStats != null) {
+            return tableStats;
+        }
+        Optional<Database> dbOption = catalog.getDb(dbId);
+        if (!dbOption.isPresent()) {
+            return null;
+        }
+        Optional<Table> tableOption = dbOption.get().getTable(tableId);
+        if (!tableOption.isPresent() || !(tableOption.get() instanceof OlapTable)) {
+            return null;
+        }
+        return findOrCreateTableStats((OlapTable) tableOption.get());
+    }
+
+    private TableStatsMeta findOrCreateTableStats(OlapTable table) {
+        TableStatsMeta tableStats = idToTblStats.get(table.getId());
+        if (tableStats != null) {
+            return tableStats;
+        }
+        TableStatsMeta created = new TableStatsMeta(table);
+        TableStatsMeta existing = idToTblStats.putIfAbsent(table.getId(), created);

Review Comment:
   fix: 将这段代码放在 synchronized (idToTblStats) { ...} 里



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6335203324

   /review


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "hello-stephen (via GitHub)" <gi...@apache.org>.
hello-stephen commented on PR #69028:
URL: https://github.com/apache/doris/pull/69028#issuecomment-6341984689

   
   <details>
   <summary>TPC-H: <b>Total hot run time: 28860 ms</b></summary>
   
   ```
   machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
   scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
   Tpch sf100 test result on commit 70184f06f222076c5ba0b094186ccf27d09fc006, data reload: false
   
   ------ Round 1 ----------------------------------
   ============================================
   q1	17613	5208	5091	5091
   q2	2189	308	263	263
   q3	10201	1384	817	817
   q4	4684	461	345	345
   q5	7430	814	530	530
   q6	183	185	156	156
   q7	749	808	611	611
   q8	9256	1267	1072	1072
   q9	5702	4206	4204	4204
   q10	6832	1305	1043	1043
   q11	423	249	229	229
   q12	638	406	281	281
   q13	18100	3672	3047	3047
   q14	279	274	252	252
   q15	q16	729	713	666	666
   q17	1190	751	578	578
   q18	7082	6143	6090	6090
   q19	1115	972	600	600
   q20	380	337	221	221
   q21	5654	2481	2499	2481
   q22	389	323	283	283
   Total cold run time: 100818 ms
   Total hot run time: 28860 ms
   
   ----- Round 2, with runtime_filter_mode=off -----
   ============================================
   q1	6036	5935	6134	5935
   q2	702	606	579	579
   q3	5538	5758	5310	5310
   q4	2042	2171	1586	1586
   q5	5792	5829	5546	5546
   q6	237	185	147	147
   q7	2196	2101	1743	1743
   q8	2976	2639	2571	2571
   q9	7882	7888	7518	7518
   q10	3654	3580	3130	3130
   q11	582	411	394	394
   q12	672	664	499	499
   q13	3343	3671	3061	3061
   q14	293	282	257	257
   q15	q16	680	681	628	628
   q17	7724	7029	6860	6860
   q18	12945	12190	12819	12190
   q19	902	817	805	805
   q20	2154	2145	1902	1902
   q21	5821	4664	4720	4664
   q22	488	469	408	408
   Total cold run time: 72659 ms
   Total hot run time: 65733 ms
   ```
   </details>
   


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "github-actions[bot] (via GitHub)" <gi...@apache.org>.
github-actions[bot] commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4310028634


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/stats/StatsCalculator.java:
##########
@@ -384,11 +384,19 @@ public Statistics visitLogicalFilter(LogicalFilter<? extends Plan> filter, Void
      * returns the sum of deltaRowCount for all selected partitions or for the table.
      */
     private long computeDeltaRowCount(OlapScan olapScan) {
+        OlapTable olapTable = olapScan.getTable();
+        // The delta rows are the rows loaded into the base index which the collected base index row count
+        // doesn't include yet. They are not the rows of an index which aggregates them: such an index has its
+        // own (usually much smaller) row count, so the delta only applies to a scan of an index which keeps
+        // one row per base row.
+        if (!TableStatsMeta.keepsOneRowPerBaseRow(olapTable, olapScan.getSelectedIndexId())) {
+            return 0;

Review Comment:
   [P2] Preserve stale-range recovery for non-row-preserving indexes
   
   `computeDeltaRowCount()` also supplies `Statistics.deltaRowCount`, which `FilterEstimation.estimate()` uses to retry zero estimates with unknown column statistics. For an AGG table with rollup `r1(k1,v)`, load key 1, run FULL ANALYZE, then load 100 distinct keys 100..199 without reanalyzing. The plan `Filter(k1 >= 100) -> OlapScan(t INDEX r1)` still reads cached max=1. This check sets delta=0, so the filter stays at zero although 100 rows match; previously delta=100 triggered the retry and a nonzero estimate. This occurs even after BE reports the index row count and distorts join/distribution costing. Keep stale-statistics recovery available for these indexes independently of whether base-row deltas may be added to their row counts, and cover the analyze/load/out-of-range-filter case.



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4312279362


##########
fe/fe-core/src/main/java/org/apache/doris/statistics/analysis/TableStatsMeta.java:
##########
@@ -194,6 +273,21 @@ public void update(AnalysisInfo analyzedJob, TableIf tableIf) {
         if (tableIf != null) {
             if (tableIf instanceof OlapTable) {
                 OlapTable olapTable = (OlapTable) tableIf;
+                // The collected row counts published below already include the rows which had been loaded when
+                // the job was built, remember how many they were, they are not delta rows. The baseline may
+                // only advance together with the collected base index row count, an analysis of another index
+                // (a materialized view) doesn't touch it.
+                // NOTICE: the baseline is published before the collected counts. They are both read without
+                // the table lock by the planner, and a reader which pairs the collected count of this analysis
+                // with the baseline of the previous one counts the rows loaded since that previous analysis
+                // twice.
+                if (analyzedJob.indexesRowCount.containsKey(olapTable.getBaseIndexId())) {
+                    // A row count supplied by the user replaces the collected one and describes the table as
+                    // of now, so the rows loaded so far are part of it and the baseline has to move to the
+                    // current value. Keeping the older baseline would count those rows again once DROP STATS
+                    // clears userInjected.
+                    updatedRowsBase.set(analyzedJob.userInject ? updatedRows.get() : analyzedJob.updateRows);

Review Comment:
   这是老代码固有问题, 设计使用deltaRow时就已知有这个缺陷. 提了新issue 跟踪 https://github.com/apache/doris/issues/69280



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly commented on code in PR #69028:
URL: https://github.com/apache/doris/pull/69028#discussion_r4355526082


##########
fe/fe-core/src/main/java/org/apache/doris/statistics/repository/StatisticsCleaner.java:
##########
@@ -140,6 +140,8 @@ private void clearTableStats() {
                 }
                 LOG.info("Table {}.{}.{} with id {} not exist, remove its table stats record.",
                         stats.ctlName, stats.dbName, stats.tblName, id);
+                // No table lock: the table is gone, so no load can recreate the record of this id while the

Review Comment:
   已在 4c792cff11f 修复。
   
   cleaner 现在对“还在回收站里”的表,会取**该回收站表对象的表写锁**(`RECOVER TABLE` 注册回的就是同一个实例,也正是 load 在 `finishTransaction` 里一直持有到记录重建完成的那把锁),把「删除记录 + 写 `DELETE_TABLE_STATS` journal」放进同一个临界区,并在锁内重新查一次 catalog:
   
   - 删除落在 RECOVER/LOAD 之前 → load 建记录对应的 journal 排在这次删除之后,重放顺序与 master 一致;
   - load 已经建好记录 → 删掉的就是这条新记录,重放时同样会删掉 → 两边一致;
   - 如果在检查与加锁之间表被 RECOVER 回来 → 不再当孤儿,保留它的统计。
   
   表彻底不在回收站(不可恢复)时仍保留原来的无锁删除,因为这种 id 之后不会有 load 再建记录。另外新增 `CatalogRecycleBin#getRecycledTable(long)` 暴露回收站里的实例。之前那句“表已经没了,不会有 load 重建记录,所以不需要表锁”的注释是错的:`RECOVER TABLE` 会用原 id 重新注册,“不在 catalog”并不等于“这个 id 不会再产生记录”。
   
   测试:
   
   - `StatisticsCleanerTest#testRecycledTableStatsAreRemovedUnderTheTableLock`:删除与 journal 写严格落在 `writeLock` 与 `writeUnlock` 之间;
   - `StatisticsCleanerTest#testTableRecoveredBeforeTheRemovalKeepsItsStats`:锁内发现表已被恢复则不动记录、不写 journal;
   - `StatisticsCleanerTest` 3/3,checkstyle 通过。
   



-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org


Re: [PR] [fix](statistics) Create the table stats record when a load finishes [doris]

Posted by "englefly (via GitHub)" <gi...@apache.org>.
englefly merged PR #69028:
URL: https://github.com/apache/doris/pull/69028


-- 
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: commits-unsubscribe@doris.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org