-
Notifications
You must be signed in to change notification settings - Fork 2k
feat: simplify count distinct logical plan #15867
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: simplify count distinct logical plan #15867
Conversation
fca72c2 to
191d7cf
Compare
5383a27 to
bf0bb3a
Compare
| for e in args { | ||
| fields_set.insert(e); | ||
| } | ||
| distinct_func = Some(Arc::clone(func)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can record the name instead of the whole func
|
clickbench Q42 fail |
this query is in clickbench.slt, and sqllogictest doesn't fail. I cannot reproduce it in my env. |
|
My bad, it seems like the casting error is allowed and the log is removed in #15858 |
|
Q14 This query is 2x slower, we need to find the reason and optimize it. |
|
Sorry, not Q14 is Q13 |
|
@jayzhan211 after profiling, I found that most time is costed in hashset operator. for primative type, hashset may not a good option. |
fn state(&mut self) -> Result<Vec<ScalarValue>> {
let scalars = self.values.iter().cloned().collect::<Vec<_>>();
let arr =
ScalarValue::new_list_nullable(scalars.as_slice(), &self.state_data_type);
Ok(vec![ScalarValue::List(arr)])
}Currently, we clone the HashSet from the partial aggregation and convert it into a List for final aggregation. I see two potential solutions:
|
|
BTW, if we want to run count distinct in big data scenario, we have to use two-step process. so I think we have to add an configure to toggle this optimization. |
We can control the skip partial aggregation ratio, the lower the ration the more likely the partial aggregation is skipped, so we probably don't need another switch to control two-step or single step. |
|
I tested shared concurrent hashset(DashSet) to avoid clone, but no performance gain. something like static global_values: LazyLock<DashSet<i64, std::hash::RandomState>> = LazyLock::new(|| DashSet::new());
#[derive(Debug)]
pub struct PrimitiveDistinctCountAccumulatorI64
{
data_type: DataType,
values: &'static DashSet<i64, std::hash::RandomState>
}
impl PrimitiveDistinctCountAccumulatorI64
{
pub fn new(data_type: &DataType) -> Self {
Self {
values: &global_values,
data_type: data_type.clone(),
}
}
} |
|
I tried to do single thread distinct, but no performance gain. something like: |
|
@jayzhan211 I think the root cause of poor performance is that, orginal plan can count parallelly, but current plan is actually blocked on final aggregation. I'm trying to modify physical plan to make count distinct more efficient. |
|
I found we actually didn't have distinct count group accumulator I try one and the performance is much better now, but still slightly left behind the main branch. But |

Which issue does this PR close?
select count(distinct ..)query doesn't go to the specialized distinct accumulator #15850.Rationale for this change
select count(distinct ..) query doesn't go to the specialized distinct accumulator.
single_distinct_to_groupby is used to optimize queries with multiple distincts.
e.g.
but if we only have one count(distinct), we should goto specialized distinct accumulator.
there's also another old issue #4082
What changes are included in this PR?
disable single_distinct_to_groupby optimzation for count distinct.
Are these changes tested?
UT
Are there any user-facing changes?
No