Skip to content

fix(streamer): bound sample-writes record-size sampling by serialized size - #19550

Open
lokeshj1703 wants to merge 1 commit into
apache:masterfrom
lokeshj1703:fix-sample-writes-rpc-message-size
Open

fix(streamer): bound sample-writes record-size sampling by serialized size#19550
lokeshj1703 wants to merge 1 commit into
apache:masterfrom
lokeshj1703:fix-sample-writes-rpc-message-size

Conversation

@lokeshj1703

Copy link
Copy Markdown
Collaborator

Describe the issue this Pull Request addresses

Closes #19549.

hoodie.streamer.sample.writes.enabled (default true) estimates average record size on the first commit by sampling up to hoodie.streamer.sample.writes.size records (default 25000) and shipping them to a single Spark task via jsc.parallelize(samples, 1). When the average record size is large, the serialized task exceeds spark.rpc.message.maxSize (default 128 MiB) and estimation fails, falling back to the default record-size estimate.

Summary and Changelog

Bound the sample by total serialized size in addition to record count:

  • Sampling now runs on the executor via mapPartitions on the single coalesced partition and stops once the accumulated serialized size would exceed 0.5 × spark.rpc.message.maxSize. This keeps both the driver collect and the downstream single-partition write under the RPC frame limit.
  • At least one record is always retained, so a single oversized record still yields an estimate.
  • Serialized size is measured with SerializationUtils.serialize(...).length, which reflects the bytes actually shipped in the task (for the Avro record type the payload holds recordBytes, so the schema is not counted per record).
  • The record-count cap (hoodie.streamer.sample.writes.size) still applies; the byte cap only engages where the previous code would have failed, so behavior is unchanged for tables whose sample already fits under the budget.
  • No copied code.

Added TestSparkSampleWritesBounding covering the count bound, the byte-budget boundary, monotonicity of the sample size with the budget, and the always-retain-one guarantee.

Impact

No config or public API change. For large-record tables the first-commit record-size estimate now succeeds instead of silently falling back to the default estimate; subsequent commits already derive record size from real commit metadata.

Risk Level

low

Sampling-only change. Existing sample-writes tests are unchanged and new unit tests were added for the bounding logic.

Documentation Update

none

Contributor's checklist

  • Read through contributor's guide
  • Enough context is provided in the sections above
  • Adequate tests were added if applicable

🤖 Generated with Claude Code

@hudi-agent hudi-agent 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.

⚠️ 🤖 This review was generated by an AI agent and may contain mistakes. Please verify any suggestions before applying.

Thanks for working on this! This PR bounds the sample-writes record-size sampling by total serialized bytes (in addition to the existing record-count cap), moving the sampling onto the executor via mapPartitions on a single coalesced partition so the downstream single-task parallelize/bulkInsert stays under spark.rpc.message.maxSize. I traced the new takeBoundedSample logic (count vs. byte bounds, the always-retain-one guarantee, lazy early termination, and overflow), the resolveMaxSampleBytes derivation, and the downstream jsc.parallelize(samples, 1) write path, and everything holds up. No correctness issues found. A few style/readability suggestions in the inline comments. Please take a look, and this should be ready for a Hudi committer or PMC member to take it from here. One small naming inconsistency to address — MB vs MiB in a constant name — otherwise the code and tests are clean and well-documented.

cc @yihua

/** Spark config bounding the maximum RPC frame size; a serialized task may not exceed it. */
private static final String RPC_MESSAGE_MAX_SIZE_CONF = "spark.rpc.message.maxSize";

/** Spark's default value for {@link #RPC_MESSAGE_MAX_SIZE_CONF}, in MiB (128 MiB). */

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.

🤖 nit: 1048576 bytes is one mebibyte (MiB), not one megabyte (MB = 1 000 000 bytes), and the Javadoc on the field directly above already says "in MiB". Could you rename this to BYTES_PER_MIB so the constant name stays consistent with the surrounding docs?

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

@github-actions github-actions Bot added the size:M PR with lines of changes in (100, 300] label Aug 7, 2026
… size

hoodie.streamer.sample.writes.enabled (default true) estimates average
record size on the first commit by sampling up to
hoodie.streamer.sample.writes.size records (default 25000) and shipping
them to a single Spark task via jsc.parallelize(samples, 1). When the
average record size is large, the serialized task exceeds
spark.rpc.message.maxSize (default 128 MiB) and estimation fails, falling
back to the default record-size estimate.

Bound the sample by total serialized size in addition to record count.
Sampling now runs on the executor via mapPartitions on the single
coalesced partition and stops once the accumulated serialized size would
exceed 0.5 of spark.rpc.message.maxSize, so neither the driver collect nor
the downstream single-partition write can exceed the RPC frame limit. At
least one record is always retained. Behavior is unchanged where the
sample already fits under the budget.
@lokeshj1703
lokeshj1703 force-pushed the fix-sample-writes-rpc-message-size branch from 42034af to f470f00 Compare August 7, 2026 10:39
@codecov-commenter

codecov-commenter commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.83%. Comparing base (65cf7e8) to head (f470f00).
⚠️ Report is 8 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19550      +/-   ##
============================================
- Coverage     76.84%   76.83%   -0.01%     
+ Complexity    32379    32377       -2     
============================================
  Files          2522     2522              
  Lines        139106   139122      +16     
  Branches      16713    16715       +2     
============================================
- Hits         106892   106891       -1     
- Misses        24621    24636      +15     
- Partials       7593     7595       +2     
Components Coverage Δ
hudi-common 83.26% <ø> (-0.01%) ⬇️
hudi-client 81.97% <ø> (-0.03%) ⬇️
hudi-flink 84.67% <ø> (-0.04%) ⬇️
hudi-spark-datasource 70.59% <ø> (-0.01%) ⬇️
hudi-utilities 73.67% <100.00%> (+0.04%) ⬆️
hudi-cli 15.32% <ø> (ø)
hudi-hadoop 63.49% <ø> (-0.02%) ⬇️
hudi-sync 75.11% <ø> (ø)
hudi-io 79.46% <ø> (ø)
hudi-timeline-service 83.44% <ø> (ø)
hudi-cloud 64.06% <ø> (ø)
hudi-kafka-connect 53.20% <ø> (ø)
Flag Coverage Δ
common-and-other-modules 50.15% <100.00%> (+0.01%) ⬆️
flink-integration-tests 48.94% <ø> (-0.02%) ⬇️
hadoop-mr-java-client 43.72% <ø> (+<0.01%) ⬆️
integration-tests 13.57% <0.00%> (-0.01%) ⬇️
spark-client-hadoop-common 49.63% <ø> (+<0.01%) ⬆️
spark-java-tests 51.56% <0.00%> (-0.02%) ⬇️
spark-scala-tests 45.97% <0.00%> (-0.01%) ⬇️
utilities 36.60% <0.00%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...udi/utilities/streamer/SparkSampleWritesUtils.java 78.72% <100.00%> (+4.36%) ⬆️

... and 11 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@hudi-bot

hudi-bot commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

CI report:

Bot commands @hudi-bot supports the following commands:
  • @hudi-bot run azure re-run the last Azure build

@hudi-agent hudi-agent 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.

⚠️ 🤖 This review was generated by an AI agent and may contain mistakes. Please verify any suggestions before applying.

Thanks for working on this! This PR bounds the sample-writes record-size sampling by total serialized size (0.5 × spark.rpc.message.maxSize) in addition to record count, moving the sampling onto the executor via mapPartitions so the single-task sample write stays under the RPC frame limit. I traced the count/byte bounds, the always-retain-one guarantee, the RPC-config resolution, and the executor-to-driver shipping path, and didn't find correctness issues in this round — notably, if the byte budget's headroom ever proved insufficient the change degrades to the pre-PR fallback rather than corrupting anything, so there's no regression risk. No new issues flagged from this automated pass — a Hudi committer or PMC member can take it from here for a final review. A single minor naming suggestion below; the fix is otherwise clean and well-documented.

cc @yihua

break;
}
samples.add(sample);
accumulatedBytes += recordBytes;

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.

🤖 nit: the "" literal carries the semantics "write to a single non-partitioned file", which the Javadoc explains but the call-site doesn't. Could you pull it out as a class-level constant (e.g. SAMPLE_WRITES_PARTITION_PATH = "") so both this line and the test's serializedSizeOfFirst helper point to the same named thing rather than two independent empty strings?

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

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

Labels

size:M PR with lines of changes in (100, 300]

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HoodieStreamer sample-writes record-size estimation fails with spark.rpc.message.maxSize for large records

4 participants