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

   Contributes to #18740 (umbrella: cost-based optimization for the multi-stage 
query engine).
   
   Together with the two PRs stacked on it, this replaces #18741, which carried 
all of Phase 1 as one
   7.2k-line change that two reviewers independently asked to see split up.
   
   **Nothing in this PR is reachable from a query.** It defines what a 
statistic is and where one is
   stored. Nothing produces or consumes a statistic until the next PR.
   
   ```text
     ┌──────────────────┐   ┌──────────────────┐   ┌──────────────────┐   
┌──────────────┐   ┌──────────────┐
     │ 1 — this PR      │──▶│ 2 — collection   │──▶│ 3 — selection    │──▶│ 4 — 
planner  │──▶│ 5 — join     │
     │ contracts and    │   │ read segment ZK  │   │ store by name +  │   │ 
wiring       │   │ reordering   │
     │ the two stores   │   │ metadata         │   │ purge endpoint   │   │     
         │   │              │
     └──────────────────┘   └──────────────────┘   └──────────────────┘   
└──────────────┘   └──────────────┘
   ```
   
   ## What a statistic is
   
   `TableStatistics` and `ColumnStatistics` are the values; `StatConfidence` is 
the trust tier carried
   by each one. Confidence is attached **per statistic**, not per table, so a 
source that can only
   estimate some values does not devalue the rest. Consumers are expected to 
treat a low-confidence
   statistic as *absent* rather than trusting it — that is what will keep table 
types with biased raw
   counts (upsert, dedup, consuming segments) on today's behavior instead of 
producing confidently
   wrong plans once a consumer exists.
   
   `StatsAggregations` holds the rollup semantics every store must share, so 
two implementations
   cannot disagree about what the same stored rows mean: time-overlap 
interpolation, document-weighted
   averages that exclude the "unknown" sentinel rather than averaging it in as 
a measurement, and
   min/max folded under the ordering the column actually has.
   
   That ordering is **recorded per row** as a `ColumnValueType` rather than 
guessed from the text,
   because guessing is wrong in both directions: a string column holding `"9"` 
and `"10"` orders
   lexically in Pinot but would compare numerically, and a long beyond 2^53 
loses digits as a double —
   in the direction that *narrows* the range, which would exclude rows that 
exist. When the ordering is
   unknown, or segments disagree about it, both bounds are reported as absent: 
a bound folded under two
   different orderings is neither a true minimum nor a true maximum, and there 
is no honest way to
   describe it as merely untrusted.
   
   ## Where statistics are stored
   
   The default is an embedded SQLite database — WAL, a small read-connection 
pool, drop-and-rebuild
   recovery if the file is unreadable. The reason is **heap, not durability**: 
statistics for hundreds
   of thousands of segments must not compete with query execution for JVM heap. 
Surviving a restart is
   a side benefit; the file also lets the broker skip re-collecting segments 
whose crc has not changed.
   
   There is no migration framework. SQLite's own `user_version` pragma carries 
the schema version, and
   a store whose version does not match is **discarded and rebuilt** rather 
than migrated. Every row is
   derived from ZooKeeper metadata the broker re-reads at startup, so a rebuild 
costs nothing that was
   not already being read — and it puts schema change, corruption and an 
unreadable file on one
   recovery path.
   
   `InMemoryStatsStore` is for brokers that cannot or should not write a file. 
Both serve table-level
   reads from a rollup recomputed only after a write, since a planner asks for 
these on every compile
   while writes arrive at segment-push cadence. A version stamp is what makes 
that cache safe without
   holding the write lock: a rollup computed from rows that changed underneath 
it is used for that call
   but never published.
   
   ## Reviewer notes
   
   - **One new dependency**, broker-only: `org.xerial:sqlite-jdbc` (13.5 MB, 
bundling native libraries
     for 17 platform/arch combinations). `LICENSE-binary` updated — it is 
dual-licensed Apache-2.0 and
     BSD-2-Clause, and both are recorded.
   - **New SPI package** `org.apache.pinot.query.planner.spi.stats`, which 
`PluginManager` already
     exports to plugin realms.
   - **Nothing calls this code yet.** That is the point of the split: the 
vocabulary and the storage
     land before anything depends on them, so the SPI shape can be argued about 
on its own.
   - No wire format, no serialization change, no mixed-version concern: 
everything here is
     broker-local.
   
   ## Testing
   
   - A shared `StatsStoreContractTest` (26 cases) runs against **both** stores, 
because the optimizer
     must not behave differently depending on which store an operator 
configured. Each implementation
     adds only what is specific to it: durability and corruption recovery for 
SQLite, starting-empty
     for in-memory.
   - `StatsAggregationsTest` and `ColumnValueTypeTest` cover the rollup 
semantics in the module that
     owns them: averages that exclude the unknown sentinel, bounds dropped when 
the ordering is unknown
     or segments disagree, and every ordering path including the 
malformed-value fallbacks — which must
     degrade rather than throw, since they will sit on the query-planning path.
   


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