Feat: add dictionaries as a supported group column type - #23187
Feat: add dictionaries as a supported group column type#23187Rich-T-kid wants to merge 6 commits into
Conversation
3f7ff57 to
e6b6dce
Compare
|
@kumarUjjawal could you run the dictionary benchmarks on this PR? Thx |
| } | ||
| } | ||
| DataType::Dictionary(key_dt, value_dt) => { | ||
| let new_field = Field::new("", *value_dt.clone(), true); |
There was a problem hiding this comment.
Since this field is never read again it may be fine to ignore the name field.
should be weary of similar issues to #21765 (comment)
There was a problem hiding this comment.
Kind of annoying that make_group_column takes a field instead of a DataType. Maybe we can change that in a follow up PR?
|
@kumarUjjawal wanted to bump this 😄 |
|
run benchmark dictionary_group_values |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing rich-T-kid/dictionary-groupValuesColumn-impl (eb41915) to 01bf68c (merge-base) diff using: dictionary_group_values File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: CPU Details (lscpu)Details
Resource Usagedictionary_group_values — base (merge-base)
dictionary_group_values — branch
File an issue against this benchmark runner |
770abfe to
243a557
Compare
|
@codex review |
@Rich-T-kid Thank you! I have been sick so I won't be available for review. I will probably get back next week. |
@kumarUjjawal Sorry to hear that. I hope you feel better! no rush on the review! |
4ee52da to
7af7080
Compare
152c1f0 to
f3387c5
Compare
|
@geoffreyclaude could you run the benchmarks command again when you get a chance. Thanks 🚀 |
|
run benchmark dictionary_group_values |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing rich-T-kid/dictionary-groupValuesColumn-impl (3d1e1c9) to 01bf68c (merge-base) diff using: dictionary_group_values File an issue against this benchmark runner |
Rich-T-kid
left a comment
There was a problem hiding this comment.
Revision #4 Last revision before making this open for review
| cached_combined: Option<ArrayRef>, | ||
| /// Per-call group equality cache for the low-cardinality path. `Mutex` | ||
| /// because `vectorized_equal_to` takes `&self`; always uncontended. | ||
| group_eq_cache: Mutex<Vec<Option<bool>>>, |
There was a problem hiding this comment.
This is a work around needed because vectorized_equal_to takes in a &self as opposed to &mut self. This means the alternative is needing to allocate target_batch_size arrays for each intern() call instead of re-using the vector.
Originally planned to use RefCell but its not Send + Sync
There was a problem hiding this comment.
this shouldn't incur any overhead since there no contention but I'm happy to explore other ideas.
|
🤖 Benchmark completed (GKE) | trigger Instance: CPU Details (lscpu)Details
Resource Usagedictionary_group_values — base (merge-base)
dictionary_group_values — branch
File an issue against this benchmark runner |
|
🤔 benchmarks show good improvement in every case but it should be much larger. Currently every |
kumarUjjawal
left a comment
There was a problem hiding this comment.
@Rich-T-kid I have left some comments for your consideration.
| let old_inner_len = self.inner.len(); | ||
| let all_inner_values = self.inner.take_n(old_inner_len); | ||
|
|
||
| let emitted = |
There was a problem hiding this comment.
take_n emits all stored dictionary values, even when only a small group batch is requested, then copies the remaining values back into the builder. Repeated partial output can become O(groups² / batch_size) and can double peak memory.
There was a problem hiding this comment.
This is true but its a constraint of the GroupColumn trait. to keep this type agnostic we can only look at the actual values through GroupColumn::take_n()/GroupColumn::build(). The remaining groups' inner slots are not a contiguous prefix of inner, they can be scattered at any position, so there is no way to partially drain inner without losing values still needed. We must drain everything into a plain array first, then re-insert only what the remaining groups still reference.
This is also a known pattern:
|
@kumarUjjawal I've added test to address your comments in 7566a13 Let me know if you have any other suggestions |
|
@zhuqi-lucas @alamb do you have any thoughts on this? |
|
run benchmark dictionary_group_values |
|
Benchmark for this request failed. Last 20 lines of output: Click to expandFile an issue against this benchmark runner |
|
Some regressions before, need to verify again: dict_repeated_intern_emit/repeated_intern_emit/size_65536_card_65536_null_0.10 1.00 17.3±0.17ms 14.4 MElem/sec 1.27 22.0±0.25ms 11.4 MElem/sec
dict_repeated_intern_emit/repeated_intern_emit/size_8192_card_8192_null_0.10 1.00 1459.5±8.00µs 21.4 MElem/sec 1.18 1721.9±6.11µs 18.1 MElem/sec |
|
run benchmark dictionary_group_values |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing rich-T-kid/dictionary-groupValuesColumn-impl (38d656f) to e8a65f2 (merge-base) diff using: dictionary_group_values File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: CPU Details (lscpu)Details
Resource Usagedictionary_group_values — base (merge-base)
dictionary_group_values — branch
File an issue against this benchmark runner |
|
Latest run, still one regression need to investigate: dict_repeated_intern_emit/repeated_intern_emit/size_65536_card_65536_null_0.10 1.00 16.8±0.07ms 14.9 MElem/sec 1.19 19.9±0.10ms 12.5 MElem/sec |
38d656f to
fb60ab3
Compare
|
thank you for the review @zhuqi-lucas, the two latest PR's should address your comments + test. @kumarUjjawal @zhuqi-lucas can we run the benchmarks one more time to make sure theres no meaningful regressions |
|
run benchmark dictionary_group_values |
|
Benchmark for this request failed. Last 20 lines of output: Click to expandFile an issue against this benchmark runner |
|
run benchmark dictionary_group_values |
|
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing rich-T-kid/dictionary-groupValuesColumn-impl (e994180) to bb75d92 (merge-base) diff using: dictionary_group_values File an issue against this benchmark runner |
|
🤖 Benchmark completed (GKE) | trigger Instance: CPU Details (lscpu)Details
Resource Usagedictionary_group_values — base (merge-base)
dictionary_group_values — branch
File an issue against this benchmark runner |
|
nice no regressions 🚀 |

Which issue does this PR close?
Rationale for this change
This PR introduces a specialized
GroupColumnimplementation for dictionary-typed columns insideGroupValuesColumn, allowing dictionary columns to participate in the columnar, vectorized aggregation path instead of the row-based fallback.The Implementation is only about 175+ lines of code. the remaining LOC is adding extensive test at the
GroupColumntrait level as well as testing theGroupValuesColumnGroupValues trait and how it inter-opts with multi-dictionary group by's.What changes are included in this PR?
DictionaryGroupValueBuilderstruct implementing theGroupColumntrait forDictionary-typed group-by columns, supporting a configurable subset of value typesGroupValuesColumn::try_new(thematches!block) to acceptDictionary(_, value_type)wherevalue_typeis already supported.emitAre these changes tested?
yes. a majority of this PR is test
Are there any user-facing changes?
no. this is a pure perf boost for users.