Skip to content
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

[VL] Fix NullPointerException when collect_list / collect_set are partially fallen back #5655

Merged
merged 21 commits into from
May 11, 2024

Conversation

zhztheplayer
Copy link
Member

@zhztheplayer zhztheplayer commented May 8, 2024

Edit: Fixes #5649. Added vanilla implementation of velox_collect_list and velox_collect_set.

Edit: (2024/12/13) Why doing the expression replacement on logical plan: The replacement changes the intermediate data type so will change partial aggregation's output schema if it's against physical plan. So we adjust the logical plan directly.

Velox backend's collect_list / collect_set implementations require for ARRAY intermediate data however Spark uses BINARY. To address this we did some tricks to forcibly modify the physical plan to change the output schema of partial aggregate operator to align with Velox, but that way the actual information for the two functions in Velox backend is still hidden from query plan so advanced optimizations or compatibility checks are made difficult during planning phase.

This patch adds new functions velox_collect_list / velox_collect_set to correctly map to Velox backend's implementation for the two functions and does essential code cleanup and refactors.

  1. Add functions velox_collect_list / velox_collect_set.
  2. Remove physical rule RewriteCollect / RewriteTypedImperativeAggregate, add logical rule CollectRewriteRule to incorporate functionalities of the formers.
  3. CollectRewriteRule will replace collect_list / collect_set with velox_collect_list / velox_collect_set.
  4. Since velox_collect_list / velox_collect_set becomes DeclarativeAggregate, some UTs should be disabled as they do some plan checks for existence of ObjectHashAggregateExec.
  5. Some optimizations for UT facility code.

Copy link

github-actions bot commented May 8, 2024

Run Gluten Clickhouse CI

1 similar comment
Copy link

github-actions bot commented May 8, 2024

Run Gluten Clickhouse CI

@zhztheplayer zhztheplayer force-pushed the wip-rework-collect branch from adb07c7 to a199917 Compare May 8, 2024 06:47
Copy link

github-actions bot commented May 8, 2024

Run Gluten Clickhouse CI

2 similar comments
Copy link

github-actions bot commented May 8, 2024

Run Gluten Clickhouse CI

Copy link

github-actions bot commented May 8, 2024

Run Gluten Clickhouse CI

@zhztheplayer
Copy link
Member Author

/Benchmark Velox

@GlutenPerfBot
Copy link
Contributor

===== Performance report for TPCH SF2000 with Velox backend, for reference only ====

query log/native_5655_time.csv log/native_master_05_07_2024_254d62e72_time.csv difference percentage
q1 34.16 34.69 0.528 101.55%
q2 22.17 23.52 1.349 106.08%
q3 36.72 39.12 2.402 106.54%
q4 38.01 38.99 0.982 102.58%
q5 69.14 70.47 1.331 101.93%
q6 5.85 7.99 2.145 136.68%
q7 82.92 82.58 -0.335 99.60%
q8 84.23 85.88 1.654 101.96%
q9 124.80 125.69 0.890 100.71%
q10 46.97 44.17 -2.799 94.04%
q11 20.58 19.52 -1.062 94.84%
q12 27.60 25.07 -2.524 90.85%
q13 53.40 54.78 1.379 102.58%
q14 18.46 16.46 -1.993 89.20%
q15 31.18 29.15 -2.027 93.50%
q16 13.60 14.27 0.674 104.96%
q17 102.39 104.34 1.951 101.91%
q18 148.53 144.89 -3.634 97.55%
q19 13.42 15.16 1.737 112.95%
q20 27.16 26.39 -0.771 97.16%
q21 289.52 283.25 -6.273 97.83%
q22 16.11 14.47 -1.643 89.80%
total 1306.89 1300.85 -6.038 99.54%

Copy link

github-actions bot commented May 8, 2024

Run Gluten Clickhouse CI

3 similar comments
Copy link

github-actions bot commented May 8, 2024

Run Gluten Clickhouse CI

Copy link

github-actions bot commented May 8, 2024

Run Gluten Clickhouse CI

Copy link

github-actions bot commented May 8, 2024

Run Gluten Clickhouse CI

@zhouyuan
Copy link
Contributor

zhouyuan commented May 8, 2024

CC @zhli1142015

@zhztheplayer zhztheplayer force-pushed the wip-rework-collect branch from bd6849e to 76d3634 Compare May 9, 2024 00:30
Copy link

github-actions bot commented May 9, 2024

Run Gluten Clickhouse CI

@zhztheplayer zhztheplayer force-pushed the wip-rework-collect branch from 76d3634 to 0eb763c Compare May 9, 2024 00:54
Copy link

github-actions bot commented May 9, 2024

Run Gluten Clickhouse CI

@zhztheplayer zhztheplayer force-pushed the wip-rework-collect branch from 0eb763c to b9f6db3 Compare May 9, 2024 06:28
Copy link

github-actions bot commented May 9, 2024

Run Gluten Clickhouse CI

@zhztheplayer
Copy link
Member Author

/Benchmark Velox

@apache apache deleted a comment from github-actions bot May 9, 2024
Copy link

github-actions bot commented May 9, 2024

Run Gluten Clickhouse CI

Copy link

Run Gluten Clickhouse CI

1 similar comment
Copy link

Run Gluten Clickhouse CI

@zhztheplayer
Copy link
Member Author

@zhztheplayer Do you think this approach is better, or is it better for us to convert it to binary in the project during row extraction after partial agg?

I am not 100% sure but seems to be that the PR's approach looks more portable? Row extraction would work but if it's done at operator level, the function still outputs ARRAY intermediate data which doesn't match Spark's CollectList / CollectSet definition.

Copy link

Run Gluten Clickhouse CI

1 similar comment
Copy link

Run Gluten Clickhouse CI

override def defaultResult: Option[Literal] = Option(Literal.create(Array(), dataType))
}

case class VeloxCollectSet(override val child: Expression) extends VeloxCollect {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use ArrayUnion for updateExpressions and mergeExpressions ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It internally uses Array. A single de-dup operation should cost ~O(n). I would do a distinct at end of aggregation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's mainly for reduce shuffle data if the partial mode aggreagte fallback.

Copy link
Member Author

@zhztheplayer zhztheplayer May 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it costs more space. May be we can use Spark Map type for collect_set?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to fully copy Collect code and change serialize, deserialize to adapt datatype ? It seems we can re-point binary to a UnsafeArrayData and then getArray.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't binary data only able to obtain when it's a ImperativeAggregate? Would you elaborate on the suggestion?

case class VeloxCollectSet(override val child: Expression) extends VeloxCollect {
override def prettyName: String = "velox_collect_set"

override def nullable: Boolean = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we preserve the comment ?

// We should mark attribute as withNullability since the collect_set and collect_set
// are not nullable but velox may return null. This is to avoid potential issue when
// the post project fallback to vanilla Spark.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some comment to VeloxCollectSet.

For now we don't need the withNullability part of the comment.

case w: Window =>
w.transformExpressions {
case func @ WindowExpression(ToVeloxCollectSet(newAggFunc), _) =>
val out = ensureNonNull(func.copy(newAggFunc))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shall we ensureNonNull for newAggFunc rather than window func ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't work. Spark has some checks in checkAnalysis to enforce "WindowExpression(WindowFunction, ...)" pattern.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure adding ensureNonNull for window expression is been actually evaluated. It seems window operator only collects the window expression to eval. Is there a test for window + collect_set with null input ?

Copy link
Member Author

@zhztheplayer zhztheplayer May 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's evaluated with the project created in WindowExecBase#createResultProjection. Velox's window implementation doesn't support collect_set yet. I'd add a case for vanilla Spark + velox_collect_set if you are concerned about this part of code.

agg.transformExpressions {
case ToVeloxCollectSet(newAggFunc) =>
val out = ensureNonNull(newAggFunc)
out
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnecessary variable

Copy link
Member Author

@zhztheplayer zhztheplayer May 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find a proper way for placing breakpoint on return value for debugging code without doing this. Do you know some?

If there is not a good way, Personally I would keep something like this and it doesn't increase code complexity.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually dig the internal method returned value.

withSQLConf(
GlutenConfig.EXPRESSION_BLACK_LIST.key -> "collect_set"
) {
CollectRewriteRule.forceEnableAndRun {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to force enable the rule ? Should it it already enabled if have collect_set ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remove this API and the two tests. They are not useful for now.

if (out.fastEquals(plan)) {
return plan
}
spark.sessionState.analyzer.checkAnalysis(out)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this check for ? I did not see Spark call it at optimizer phase...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try removing this. It should not be required with current approach either.

Copy link

Run Gluten Clickhouse CI

1 similar comment
Copy link

Run Gluten Clickhouse CI

Copy link

Run Gluten Clickhouse CI

Copy link

Run Gluten Clickhouse CI

2 similar comments
Copy link

Run Gluten Clickhouse CI

Copy link

Run Gluten Clickhouse CI

}
}

private def has[T <: Expression: ClassTag]: Boolean = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After droping test, do we still need this check ? It seems this rule is only added by velox backend and the same is VeloxCollectSet. So when we going to this rule, there must have VeloxCollectSet ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with removing the check. Although we can do that later in some version...

For example VeloxCollectSet is still expensive than vanilla Spark so removing this check may cause a user who disabled collect_set experience performance regression.

BTW I'll raise some optimizations on VeloxCollectSet after this patch is merged. When I am confident with vanilla Spark + velox_collect_list / velox_collect_set being used in production enough, I'll remove this check.

Copy link
Contributor

@JkSelf JkSelf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks.

@zhztheplayer zhztheplayer merged commit 2efa2e6 into apache:main May 11, 2024
43 checks passed
@zhztheplayer
Copy link
Member Author

Thank you all for reviewing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants