andygrove commented on PR #2216:
URL: 
https://github.com/apache/datafusion-ballista/pull/2216#issuecomment-5226692503

   This is still a draft so please feel free to disregard any or all of this, I 
know you're mid iteration. I had it open anyway and found one thing I thought 
was worth passing along early rather than sitting on.
   
   First the good parts. The statistical tests against the DataSketches 
empirical rank error bound are a real upgrade over what was in `kll.rs` before, 
and they're all seeded so they won't flake. The deferred optimizations section 
with the struck through items and measured numbers next to each is unusually 
easy to follow. And reporting the 5.7x `OwnedRow` regression plainly instead of 
burying it is the right call.
   
   ## CI
   
   Three jobs are red. `test linux ballista` is the `ballista-chaos` cluster 
startup flake and has nothing to do with this PR. The other two are real:
   
   ```
   test kll::tests::absorb_sorted_slice_debug_asserts_on_unsorted - should 
panic ... FAILED
   ```
   
   The cause is that `[profile.ci]` in the root `Cargo.toml` sets 
`debug-assertions = false`, and CI runs `cargo test --profile ci`. So 
`debug_assert!` never fires under CI and a `#[should_panic]` test on one can't 
pass.
   
   ## The thing I'd actually push on
   
   That failure isn't just a broken test, I think it's the API telling on 
itself. `absorb_sorted_slice` is a safe `pub fn` whose own docs say that 
violating the precondition silently corrupts the sketch, and the only guard is 
a `debug_assert!` that the project's test profile compiles out. So the 
protection doesn't exist in the configuration we test and ship.
   
   What I'd suggest is verifying sortedness unconditionally and falling back to 
`absorb_slice` when the input isn't sorted. Checking costs O(n) comparisons 
against the O(n log n) sort you're skipping, so on the 1M row f64 case that's 
about a million `<=` comparisons against a 7.3 ms budget. Most of the win 
survives, the footgun goes away entirely, and the `should_panic` test turns 
into a "falls back correctly" test that passes under any profile.
   
   I care about this more than I normally would for an unchecked precondition 
because of who the caller is. The invariant is a plan shape one, that a 
`SortExec` sits above `RuntimeStatsExec`. If some later optimizer change moves 
or drops that sort, the sketch quietly returns wrong quantiles, and those 
become wrong partition boundaries. Silent wrong answers from a plan shape 
assumption is a bad failure mode to design in.
   
   ## The sortedness invariant itself looks right
   
   I went through this carefully since it's the risky part of the change. Every 
`levels[...]` mutation site on main is covered, `merge`, `insert`, and the 
three inside `compact_level`. `compact_all` only compacts levels at or over 
capacity and consumes no RNG when it no ops, so the "bit identical to insert in 
a loop" claim for `absorb` holds up. The merge branch in `compact_level` 
maintains the flag correctly, and the conservative `sorted[h] = false` in cross 
sketch `merge` is safe.
   
   Two things I'd tighten:
   
   - `absorb_sorted_slice` degrades silently when `sorted[0]` is false on 
entry. If you mix it with `insert` or `absorb_slice` on the same sketch, 
`merge_sorted` interleaves into an unsorted level 0. It stays correct, since 
the flag stays false and compaction sorts, and it self heals after the first 
compaction. But the doc's claim that level 0 stays permanently sorted only 
holds for a sketch used exclusively through this one method. A sort and set at 
the top would make the doc true.
   - This PR changes the output of the existing `insert` path. `compact_level` 
now merge extends where it used to extend then `sort_unstable`. With ties a 
different set of duplicate copies survives. Values and weights are unchanged so 
quantiles are identical for any `T` where equal items are interchangeable, 
which covers every current use, but the PR reads as purely additive and this 
part isn't. Worth a line in the description.
   
   ## Smaller
   
   - `absorb_sorted_slice` allocates twice per chunk, once for `to_vec()` and 
once for `merge_sorted`'s output, and drops `existing`. At level 0 chunk sizes 
across 1M rows that's a lot of small allocations. Already fast at 7.3 ms so not 
urgent, but an in place merge would probably widen the gap further.
   - `datafusion-functions-aggregate-common` went into `[dependencies]` but is 
only used by the bench. Looks like it belongs in `[dev-dependencies]`.
   - `ordered-float = "4"` is a raw version rather than `{ workspace = true }`. 
It isn't in `[workspace.dependencies]` yet, and we manage versions there.
   
   No concerns on the persona side. The `absorb*` methods are additive on an 
existing public type and nothing is wired into `RuntimeStatsExec` yet.
   


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