Uh oh!
There was an error while loading. Please reload this page.
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 9 commits into
Conversation
3f7ff57 to
e6b6dceCompareRich-T-kid
commented
Jul 1, 2026
@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?
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
@kumarUjjawal wanted to bump this 😄 |
geoffreyclaude
commented
Jul 1, 2026
run benchmark dictionary_group_values |
adriangbot
commented
Jul 1, 2026
🤖 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 |
adriangbot
commented
Jul 1, 2026
🤖 Benchmark completed (GKE) | trigger Instance: CPU Details (lscpu)DetailsResource Usagedictionary_group_values — base (merge-base)
dictionary_group_values — branch
File an issue against this benchmark runner |
770abfe to
243a557CompareRich-T-kid
commented
Jul 1, 2026
@codex review |
kumarUjjawal
commented
Jul 2, 2026
@Rich-T-kid Thank you! I have been sick so I won't be available for review. I will probably get back next week. |
Rich-T-kid
commented
Jul 2, 2026
@kumarUjjawal Sorry to hear that. I hope you feel better! no rush on the review! |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
4ee52da to
7af7080CompareUh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
152c1f0 to
f3387c5CompareRich-T-kid
commented
Jul 6, 2026
Rich-T-kid
commented
Jul 6, 2026
@geoffreyclaude could you run the benchmarks command again when you get a chance. Thanks 🚀 |
geoffreyclaude
commented
Jul 6, 2026
run benchmark dictionary_group_values |
adriangbot
commented
Jul 6, 2026
🤖 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
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
adriangbot
commented
Jul 6, 2026
🤖 Benchmark completed (GKE) | trigger Instance: CPU Details (lscpu)DetailsResource Usagedictionary_group_values — base (merge-base)
dictionary_group_values — branch
File an issue against this benchmark runner |
Rich-T-kid
commented
Jul 6, 2026
🤔 benchmarks show good improvement in every case but it should be much larger. Currently every |
41192e5 to
44ad7d1CompareRich-T-kid
commented
Aug 4, 2026
commit 44ad7d1 should address all of your comments except for the |
kumarUjjawal
commented
Aug 4, 2026
run benchmark dictionary_group_values |
adriangbot
commented
Aug 4, 2026
🤖 Benchmark running (GKE) | trigger CPU Details (lscpu)Comparing rich-T-kid/dictionary-groupValuesColumn-impl (63d4021) to 47ca490 (merge-base) diff Run configurationrun benchmark dictionary_group_valuesResults will be posted here when complete File an issue against this benchmark runner |
adriangbot
commented
Aug 4, 2026
🤖 Benchmark completed (GKE) | trigger Instance: Comparing rich-T-kid/dictionary-groupValuesColumn-impl (63d4021) to 47ca490 (merge-base) diff Run configurationrun benchmark dictionary_group_valuesCPU Details (lscpu)DetailsResource Usagedictionary_group_values — base (merge-base)
dictionary_group_values — branch
File an issue against this benchmark runner |
kumarUjjawal
left a comment
There was a problem hiding this comment.
Thank you @Rich-T-kid
This looks good. Please create a separate issue for the optimization as you mentioned.
Rich-T-kid
commented
Aug 4, 2026
created a follow up issue here #24089 thank you for the reviews @kumarUjjawal@zhuqi-lucas@jayshrivastava 🚀 |
kumarUjjawal
commented
Aug 5, 2026
I will merge this in 1-2 days in no one else has any objections. |
alamb
left a comment
There was a problem hiding this comment.
Thank you for this @Rich-T-kid and @kumarUjjawal
I think it would be really great to show that this works end to end (and improves performance) -- I suspect it does but it would be good to verify
One way we could do this is run TPCH Q1 https://github.com/apache/datafusion/blob/main/benchmarks/queries/q1.sql
You could test this locally like
Make data
tpcgen-cli tpch parquet --scale-factor=10 --tables=lineitem
lineitem [==================] (100%)
Then convert the relevant columns to dictioanry
> COPY (select arrow_cast(l_returnflag, 'Dictionary(Int32, Utf8)') as l_returnflag, arrow_cast(l_linestatus, 'Dictionary(Int32, Utf8)') as l_linestatus, l_quantity, l_extendedprice, l_discount, l_shipdate, l_tax from 'lineitem.parquet') to 'lineitem_dict.parquet';
+----------+
| count |
+----------+
| 59986052 |
+----------+
1 row(s) fetched.
Elapsed 1.767 seconds.
Then run query
>select
l_returnflag,
l_linestatus,
sum(l_quantity) as sum_qty,
sum(l_extendedprice) as sum_base_price,
sum(l_extendedprice * (1- l_discount)) as sum_disc_price,
sum(l_extendedprice * (1- l_discount) * (1+ l_tax)) as sum_charge,
avg(l_quantity) as avg_qty,
avg(l_extendedprice) as avg_price,
avg(l_discount) as avg_disc,
count(*) as count_order
from'lineitem_dict.parquet'where
l_shipdate <=date'1998-09-02'group by
l_returnflag,
l_linestatus
order by
l_returnflag,
l_linestatus;
+--------------+--------------+--------------+------------------+--------------------+----------------------+-----------+--------------+----------+-------------+
| l_returnflag | l_linestatus | sum_qty | sum_base_price | sum_disc_price | sum_charge | avg_qty | avg_price | avg_disc | count_order |
+--------------+--------------+--------------+------------------+--------------------+----------------------+-----------+--------------+----------+-------------+
| A | F | 377518399.00 | 566065727797.25 | 537759104278.0656 | 559276670892.116819 | 25.500975 | 38237.151008 | 0.050006 | 14804077 |
| N | F | 9851614.00 | 14767438399.17 | 14028805792.2114 | 14590490998.366737 | 25.522448 | 38257.810660 | 0.049973 | 385998 |
| N | O | 743124873.00 | 1114302286901.88 | 1058580922144.9638 | 1100937000170.591854 | 25.498075 | 38233.902923 | 0.050000 | 29144351 |
| R | F | 377732830.00 | 566431054976.00 | 538110922664.7677 | 559634780885.086257 | 25.508384 | 38251.219273 | 0.049996 | 14808183 |
+--------------+--------------+--------------+------------------+--------------------+----------------------+-----------+--------------+----------+-------------+4 row(s) fetched.
Elapsed 0.370 seconds.| } | ||
| }; | ||
| // TODO: Materialize dictionaries in group keys (#7647) |
alamb
commented
Aug 5, 2026
Another thing that might be valuable is to make a variant of the tpch benchmarks, where you tell the parquet reader that the low cardinality columns (that would be good for dictionary encoding) should be read as Dictionary... Arrays -- and then run the queries and see how much improvement this PR gives I actually think that would be helpful in general if your goal is to improve the overall performance of the dictionaries in DataFusion. I will file a ticket with more detail |
@alamb I think the second approach is easier to work with and less error-prone. But from my understanding, this isn't currently possible, or at least isn't exposed in DataFusion. The only config that currently exists is for writing: https://docs.rs/datafusion/latest/datafusion/config/struct.ParquetOptions.html#structfield.dictionary_enabled
I think it'd be nice to expose this as a session prop so users can configure it with SET notation in SQL: The only caveat that comes to mind is this may cause the Parquet reader to read all string columns into dictionary arrays, which isn't what we want for precision. the concept your referring to is actually quite similar to what I raised in this issue: #22891. Ideally we would be able to determine which columns are low-cardinality and change their type dictionary at physical planning time. with #23187 this should provide a nice perf boost. the only issue would be determining what threshold counts as "low cardinality". cc @asolimando |
Rich-T-kid
commented
Aug 5, 2026
So I think some nice follow ups would be
by hand we can experiment with transforming low-card string group keys into |
Rich-T-kid
commented
Aug 5, 2026
I ran TPCH Q4 against main and these were my results main cast the columns to dictionary using this branch |
Rich-T-kid
commented
Aug 9, 2026
kumarUjjawal
commented
Aug 10, 2026
63d4021 to
aa8addeCompareaa8adde to
daaae0bCompare
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.