zhuxiangyi opened a new pull request, #9657:
URL: https://github.com/apache/paimon/pull/9657

   ### Purpose
   
   Scan metrics today tell the user *how much was read*, but not *whether that 
amount is
   reasonable*. Two concrete gaps:
   
   **1. Manifest pruning efficiency cannot be computed — the numerator is 
reported without the denominator.**
   
   `lastScannedManifests` reports `manifestsResult.filteredManifests.size()`, 
i.e. the count
   *after* manifest level filtering. The total (`allManifests.size()`) sits on 
the very same
   object but is never reported, so `scannedManifests = 200` is ambiguous:
   
   | | total manifests | scanned | what it means |
   |---|---|---|---|
   | A | 1000 | 200 | partition pruning works well |
   | B | 200 | 200 | **pruning did not happen at all** |
   
   Case B is a common production problem (a predicate that fails to push down 
because of a
   function or a type mismatch on the partition column), and it is exactly the 
case the current
   metrics cannot distinguish.
   
   **2. Only file counts are reported, no bytes and no records.**
   
   `resultedTableFiles = 3000` does not answer "how much data will this query 
read". In Paimon
   the relation between file count and data volume is unstable: a frequently 
written table may
   have 3000 files holding 2 GB, while the same table after compaction may have 
300 files
   holding 20 GB. Deciding whether a query is slow because of data volume, or 
whether a table
   needs compaction, requires bytes and records. Both `DataFileMeta#fileSize` 
and
   `DataFileMeta#rowCount` are already carried by the entries in the scan 
result.
   
   There is also an asymmetry with the write side: `CommitStats` already 
reports record level
   counters (`deltaRecordsAppended`, `changelogRecordsAppended`), while the 
scan side reports
   file counts only.
   
   #### Changes
   
   Three fields are added to `ScanStats` and three gauges to `ScanMetrics`:
   
   | metric | meaning |
   |---|---|
   | `lastScanSkippedManifests` | `allManifests.size() - 
filteredManifests.size()` |
   | `lastScanResultedTableFilesSize` | total size in bytes of the files to be 
read |
   | `lastScanResultedRecordCount` | total number of records in the files to be 
read |
   
   Together with the existing metrics this completes two axes that are 
currently incomplete:
   
   - pruning efficiency: `scannedManifests`, **`skippedManifests`**, 
`skippedTableFiles`
   - scan cost: `resultedTableFiles`, **`resultedTableFilesSize`**, 
**`resultedRecordCount`**
   
   They are computed at the single existing reporting site in 
`AbstractFileStoreScan#plan`,
   inside the existing `if (scanMetrics != null)` block, so there is no cost 
when metrics are
   disabled:
   
   ```java
   // for DELTA and CHANGELOG scan modes the result contains both ADD and 
DELETE entries,
   // only ADD entries will actually be read, so size and record count only 
count them
   long resultedTableFilesSize = 0L;
   long resultedRecordCount = 0L;
   for (ManifestEntry entry : result) {
       if (entry.kind() == FileKind.ADD) {
           resultedTableFilesSize += entry.file().fileSize();
           resultedRecordCount += entry.file().rowCount();
       }
   }
   ```
   
   Note the deliberate asymmetry: size and record count only cover 
`FileKind.ADD` entries,
   because for `DELTA` / `CHANGELOG` scan modes the result also carries 
`DELETE` entries whose
   bytes will never be read. The existing `resultedTableFiles` keeps its 
current semantics (all
   kinds), so no existing metric changes value.
   
   The three metrics are also exposed as Spark custom metrics (`PaimonMetrics`,
   `SparkMetricRegistry`, `PaimonBaseScan#supportedCustomMetrics`); the size 
one uses
   `PaimonSizeSumMetric` so the SQL tab renders `18.2 GiB` rather than a raw 
byte count. The
   Flink side needs no change, `FlinkMetricRegistry` forwards the new gauges 
automatically.
   
   This PR also fixes a typo in the `planningDuration` metric description 
(`planing` ->
   `planning`), which is in the same block of code.
   
   #### Compatibility
   
   No format change. Both source values are already persisted fields
   (`ManifestFileMeta#_NUM_ADDED_FILES`, `DataFileMeta#_FILE_SIZE`); nothing 
new is written and
   the write path is never entered, so old tables, old readers and rolling 
Flink upgrades are
   unaffected. The new gauges are purely additive — no existing metric name or 
semantic changes,
   so existing dashboards keep working.
   
   For reference, Iceberg reports the equivalent counters in its `ScanMetrics`:
   `TOTAL_DATA_MANIFESTS`, `SKIPPED_DATA_MANIFESTS` and 
`TOTAL_FILE_SIZE_IN_BYTES`.
   
   #### Follow-ups (not in this PR)
   
   - Break `scanDuration` down into manifest IO vs. filtering, so a slow plan 
can be attributed.
   - Merge-on-read cost (deletion vectors / delete rows), which the scan file 
count does not reflect.
   - `resultedLevel0Files`, to show how much un-compacted data a query has to 
read.
   
   ### Tests
   
   - `ScanMetricsTest` — extended to cover the three new gauges (registration, 
initial values and
     values after each report).
   - `PaimonMetricTest` — `checkMetrics` now also asserts `resultedRecordCount` 
and that
     `resultedTableFilesSize > 0`, plus a new assertion that a scan without any 
filter cannot
     prune any manifest (`skippedManifests == 0`). Passes under both `-Pspark3` 
(Scala 2.12) and
     `-Pspark4` (Scala 2.13).
   - `FileStoreSourceMetricsTest` — unchanged and still passing, confirming the 
Flink side needs
     no change.
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   
   https://claude.ai/code/session_01TQTKK2tcvNikAuUUDfbsaT
   


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

To unsubscribe, e-mail: [email protected]

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

Reply via email to