gortiz opened a new pull request, #19410:
URL: https://github.com/apache/pinot/pull/19410

   > **Draft until #19409 merges.** This PR is stacked on it. GitHub compares 
against `master`, so the
   > diff shown here also contains #19409's two commits. **The change under 
review here is the single
   > commit "Collect broker statistics from the ZooKeeper metadata already 
watched" (+2,238).** I will
   > rebase and mark this ready as soon as the parent lands.
   
   Contributes to #18740. **Stacked on #19409 (contracts and stores) — review 
that one first.**
   
   Part of the split that replaces #18741.
   
   Nothing here changes a query plan. This PR fills the store; nothing reads it 
until PR 4.
   
   ```text
     ┌──────────────────┐   ┌──────────────────┐   ┌──────────────────┐   
┌──────────────┐   ┌──────────────┐
     │ 1 — contracts    │──▶│ 2 — this PR      │──▶│ 3 — selection    │──▶│ 4 — 
planner  │──▶│ 5 — join     │
     │ and the stores   │   │ collection       │   │ store by name +  │   │ 
wiring       │   │ reordering   │
     │                  │   │                  │   │ purge endpoint   │   │     
         │   │              │
     └──────────────────┘   └──────────────────┘   └──────────────────┘   
└──────────────┘   └──────────────┘
   ```
   
   ## How collection works
   
   The broker already subscribes to segment ZK metadata to build routing, so 
collection rides that
   existing stream rather than adding cluster traffic: row counts, sizes and 
time boundaries are
   effectively free. It attaches through the routing manager's generic 
listener-provider seam, so the
   routing manager itself needs no knowledge of statistics.
   
   ```text
                           ┌─────────────────────────────┐
                           │ ZooKeeper segment metadata  │
                           └──────────────┬──────────────┘
                                          │  already watched to build routing
                           ┌──────────────▼──────────────┐
                           │  SegmentZkMetadataFetcher   │
                           └───────┬─────────────┬───────┘
                 existing consumer │             │ new, attached via the
                                   │             │ listener-provider seam
                 ┌─────────────────▼───┐   ┌─────▼───────────────────────┐
                 │   segment pruners   │   │   TableStatsZkListener      │
                 └─────────────────────┘   └─────────────┬───────────────┘
                                                         │ per-segment rows
                                                         │ (crc-reconciled)
                                           ┌─────────────▼───────────────┐
                                           │      StatsStore (PR 1)      │
                                           └─────────────┬───────────────┘
                                                         │
                                           ┌─────────────▼───────────────┐
                                           │ LogicalTableStatsResolver   │
                                           │ per-segment rows ──▶ the    │
                                           │ logical view of a table     │
                                           └─────────────┬───────────────┘
                                                         ┊ PR 4
                                           ┌─────────────▼───────────────┐
                                           │           planner           │
                                           └─────────────────────────────┘
   ```
   
   Reconciliation is by crc, so a restart re-collects only what actually 
changed, and segments that
   leave the online set are dropped. Statistics for a table are released when 
its routing entry goes
   away, via a new `default onRoutingRemoved()` on the listener interface — 
deliberately named for the
   event it is, since routing is also removed when a broker stops serving a 
table that still exists.
   One listener failing there cannot abort the rest of the teardown.
   
   ## How a raw doc count becomes a trustworthy statistic
   
   This is the part worth reviewing closely. Raw per-segment doc counts are 
biased for several table
   types, so rather than correcting the numbers we attach a confidence tier and 
let consumers refuse
   the ones that cannot be trusted.
   
   ```text
     stored per-segment rows
             │
             ├─ OFFLINE ──▶ sum committed segments ─────────────────────────▶ 
EXACT
             │
             ├─ REALTIME ─▶ upsert or dedup?
             │                 │
             │                 ├─ yes ──▶ physical docs over-count ─────────▶ 
LOW
             │                 │          logical rows
             │                 │
             │                 └─ no ──▶ consuming segments?
             │                              │
             │                              ├─ yes ─▶ committed counts ─────▶ 
ESTIMATED
             │                              │         undercount fresh data
             │                              │
             │                              └─ no ─────────────────────────▶ 
EXACT
             │
             └─ HYBRID ───▶ split at the live time boundary ────────────────▶ 
capped at
                            (offline below, realtime above)                   
ESTIMATED,
                                                                              
never EXACT
                                           │
                                           ▼
                                 confidence == LOW ?
                                 │                 │
                               yes                 no
                                 │                 │
                                 ▼                 ▼
                       treated as ABSENT     used for estimates
                       (planner keeps
                        today's behavior)
   ```
   
   When both realtime adjustments apply, the weaker wins.
   
   ### Unknown is not zero
   
   Splitting a hybrid table at its boundary means one side can contribute rows 
to a range while the
   other structurally cannot. A side whose sub-range is empty contributes a 
**certain zero** — no rows
   fall in an empty interval, whatever the store knows. A side that must be 
answered from statistics
   and has none is **unknown**, and reporting that as zero would hand a planner 
a confident "this table
   is empty", which is a far worse input than no estimate at all.
   
   ### The time boundary is an instant, not a number
   
   The boundary comes from `TimeBoundaryManager`, which already derives it in 
epoch milliseconds and
   owns the `DateTimeFormatSpec` needed to interpret `TimeBoundaryInfo`'s 
formatted value. Re-deriving
   it at the call site would be free to drift, and parsing the formatted value 
directly is wrong for
   any column stored in days, seconds or a numeric date pattern: it parses 
cleanly as a number that is
   not an instant.
   
   ## Known limitation, and the intended fix
   
   Upsert and dedup tables are marked `LOW` and will therefore contribute 
nothing to planning. That is
   safe but blunt: those tables get no benefit at all. Keeping honest row 
counts for them in the broker
   would mean reproducing the upsert/dedup metadata (and its expensive data 
structures) broker-side,
   which is not worth it.
   
   The intended fix, as follow-up work, is to stop deriving their statistics 
from stored metadata and
   ask the servers periodically instead — a scheduled
   `SELECT $segmentName, MIN(col), MAX(col), COUNT(col) ... GROUP BY 
$segmentName`. Query execution
   already applies the upsert view, so replaced rows are filtered out by 
valid-doc-ids and the result
   counts only **live** rows. Those values slot into the same per-segment store 
and would let these
   tables carry an honest confidence instead of `LOW`. The price is a scheduled 
scan, so it needs rate
   limiting and off-peak scheduling, and consuming segments still need a 
freshness policy.
   
   ## Reviewer notes
   
   - **One interface change:** `SegmentZkMetadataFetchListener` gains a 
`default onRoutingRemoved()`.
     Source- and binary-compatible; existing implementers keep compiling.
   - **Collection failures never fail a query.** Every store error degrades to 
no-statistics.
   - Statistics are still not reachable from a query: 
`LogicalTableStatsResolver` has no consumer until
     PR 4.
   
   ## Testing
   
   - `BrokerTableStatsManagerTest` covers collection, crc reconciliation, 
table-type confidence,
     degradation when the store errors, the reconcile that repairs a mirror 
which disagrees with the
     store, and that a committed segment whose ZooKeeper metadata lacks a row 
count contributes zero
     rather than −1.
   - `LogicalTableStatsResolverTest` covers the hybrid boundary split — 
including that a range only one
     side can serve reports *unknown* rather than zero when that side has no 
statistics — and that a
     failing provider or store costs precision rather than the query.
   - `BrokerRoutingManagerTest` covers the removal dispatch and that one 
failing listener cannot abort
     teardown.
   


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

To unsubscribe, e-mail: [email protected]

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


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

Reply via email to