Skip to content

Flink: Avoid per-record Set allocation in the dynamic sink forward check - #17618

Open
sqd wants to merge 1 commit into
apache:mainfrom
sqd:oss_is_forward_eligible_micro_opt
Open

Flink: Avoid per-record Set allocation in the dynamic sink forward check#17618
sqd wants to merge 1 commit into
apache:mainfrom
sqd:oss_is_forward_eligible_micro_opt

Conversation

@sqd

@sqd sqd commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

DynamicRecordProcessor.collect calls isForwardEligible for every record. That call resolves to DynamicSinkUtil.resolveEqualityFieldNames, which for a record with no user-supplied equality fields returns Schema.identifierFieldNames(), which uses Java Stream API to allocate and build a new HashSet. This HashSet is immediately discarded after isEmpty(). So each record on the hottest and commonest path (a table without identifier fields) allocates a set purely to ask whether it is empty.

Ask the same question without materialising anything: test the user-supplied equality fields directly, then consult Schema.identifierFieldIds(), which Schema already memoizes as an ImmutableSet. Behaviour is unchanged and test still covers every branch.

DynamicRecordProcessor.collect calls isForwardEligible for every record.
That call resolves to DynamicSinkUtil.resolveEqualityFieldNames, which
for a record with no user-supplied equality fields returns
Schema.identifierFieldNames(), which uses Java Stream API to allocate
and build a new HashSet. This HashSet is immediately discarded after
isEmpty(). So each record on the hottest and commonest path (a table
without identifier fields) allocates a set purely to ask whether it is
empty.

Ask the same question without materialising anything: test the
user-supplied equality fields directly, then consult
Schema.identifierFieldIds(), which Schema already memoizes as an
ImmutableSet. Behaviour is unchanged and test still covers every
branch.
@sqd

sqd commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Benchmarks:

JMH, average time, JDK 21, 2 forks x 5 x 1 s, -prof gc, on an Apple M1 Max, against a 60-column schema:

identifier fields before after
0 34.6 ns / 352 B 3.5 ns / 0 B
2 70.0 ns / 496 B 4.2 ns / 0 B

For scale, an async-profiler CPU profile of a saturated TaskManager (AMD EPYC 9654P) running the dynamic sink in forward mode attributed 9.5% of the samples on the sink's task threads to this one check.

@github-actions github-actions Bot added the flink label Aug 12, 2026

@anoopj anoopj left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Code change looks right to me, and looks like there is coverage from existing tests.

@Guosmilesmile Guosmilesmile left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. This preserves the existing forward eligibility logic while avoiding the per-record set allocation. Thanks for the PR!

@uros-b

uros-b commented Aug 13, 2026

Copy link
Copy Markdown
Member

Thank you @sqd!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants