[FLINK-39986][table-planner][python] Support Python UDF deduplication in projection and condition - #28638
[FLINK-39986][table-planner][python] Support Python UDF deduplication in projection and condition#28638raoraoxiong wants to merge 1 commit into
Conversation
|
hi @liuyongvs @snuyanzin ,please take a look. |
|
01bb759 to
219c281
Compare
|
@flinkbot run azure |
|
@flinkbot re-run azure |
59c030c to
949b109
Compare
@snuyanzin Hi, I've update commits and fixed the CI failure, please take a look if you have a time, thanks |
|
@snuyanzin @liuyongvs Hi, I've update commits and fixed the CI failure, please take a look if you have a time, thanks |
|
would be great to see feedback from someone with python expertise may be @dianfu |
5e41422 to
5c792ba
Compare
dianfu
left a comment
There was a problem hiding this comment.
@raoraoxiong Thanks for this work! The overall direction LGTM.
Could you split this PR to ease the review? For example, at least two PRs: one for cross-Calc reuse and another for the duplication inside one Python operator.
| .cursor | ||
| .claude | ||
| .worktrees | ||
| .codebuddy |
There was a problem hiding this comment.
Unnecessary changes. Please submit in a separate PR.
5c792ba to
1116818
Compare
1116818 to
2518a3b
Compare
… in projection and condition Deduplicates Python UDF calls on the JVM side to reduce cross-process (JVM <-> Python worker) invocation overhead. This covers two scenarios: - Top-level projection duplicates: identical deterministic calls in the projection (e.g. SELECT udf(a), udf(a)) are sent to the Python worker only once; a codegen expansion projection maps the deduplicated results back to the original output schema - Condition-projection sharing: after RemoteCalcSplitConditionRule splits a Calc with Python UDFs in its condition, the new RemoteCalcConditionProjectionCseRule rewrites the top Calc so that projections reuse the Python UDF results already computed for the WHERE condition (e.g. SELECT udf(a) + 1 FROM T WHERE udf(a) > 0), including calls nested inside Java expressions Non-deterministic calls are never deduplicated and are always evaluated independently. This is a pure JVM-side change: no protocol or Python worker changes are involved. Deduplication of Python UDF calls nested inside other Python UDF calls (e.g. udf(udf(a))) will be addressed in a follow-up. Key changes: - Add PythonCallDeduplicator and PythonCallCseResult for structural deduplication of top-level projection calls - Append a CSE expansion projection in CommonExecPythonCalc when duplicated results need to be restored to the output schema - Add ProjectionCodeGenerator#generateProjectionOperator for the codegen column-mapping projection operator - Add RemoteCalcConditionProjectionCseRule and register it in stream and batch rule sets after SPLIT_CONDITION - Add unit tests, plan tests and integration tests Generated-by: Claude-4.6-Opus
2518a3b to
26333c9
Compare
|
@raoraoxiong Hey, thanks for the update. Regarding to the latest PR, it addresses two problems, for problem: |
What is the purpose of the change
This PR is the first of two PRs implementing common sub-expression elimination (CSE) for Python UDFs (FLINK-39986). Each invocation of a Python UDF involves cross-process communication between the JVM and the Python worker, so duplicated calls are significantly more expensive than duplicated Java expressions.
This PR deduplicates Python UDF calls on the JVM side only (no protocol or Python worker changes), covering two scenarios:
SELECT udf(a), udf(a)) are sent to the Python worker only once; a codegen expansion projection maps the deduplicated results back to the original output schema.RemoteCalcSplitConditionRulesplits a Calc with Python UDFs in its condition, the newRemoteCalcConditionProjectionCseRulerewrites the top Calc so that projections reuse the Python UDF results already computed for the WHERE condition (e.g.SELECT udf(a) + 1 FROM T WHERE udf(a) > 0), including calls nested inside Java expressions.Non-deterministic calls are never deduplicated and are always evaluated independently.
Deduplication of Python UDF calls nested inside other Python UDF calls (e.g.
udf(udf(a))), which requires extending the JVM-to-worker protocol with result references, is addressed in the follow-up PR #28998.Brief change log
PythonCallDeduplicatorandPythonCallCseResultfor structural deduplication of top-level projection calls (deduplication byRexCallstructural equivalence, which correctly distinguishes calls that differ only in return type)CommonExecPythonCalcwhen duplicated results need to be restored to the output schemaProjectionCodeGenerator#generateProjectionOperatorfor the codegen column-mapping projection operatorRemoteCalcConditionProjectionCseRuleand register it in stream and batch rule sets afterSPLIT_CONDITIONVerifying this change
This change added tests and can be verified as follows:
CommonExecPythonCalcCseTest: parameterized unit tests for the deduplication logic (deterministic/non-deterministic/mixed/return-type-sensitive cases) and the expansion projection detail namePythonCalcConditionCseTest(+ plan XML): plan tests verifying condition-projection deduplication, including negative cases (different UDFs are not deduplicated)test_python_local_ref_reuseinflink-python/pyflink/table/tests/test_udf.py: end-to-end tests verifying deterministic calls are reused and non-deterministic calls are not (UUID suffix in UDF output proves whether calls were deduplicated)Does this pull request potentially affect one of the following parts:
@Public(Evolving): (no)Documentation
AI Usage Disclosure
Generated-by: Claude-4.6-Opus