Skip to content

fix(metrics): do not drop the whole CloudWatch batch on one unmappable metric name - #19476

Open
rangareddy wants to merge 2 commits into
apache:masterfrom
rangareddy:fix-12182-cloudwatch-batch-loss
Open

fix(metrics): do not drop the whole CloudWatch batch on one unmappable metric name#19476
rangareddy wants to merge 2 commits into
apache:masterfrom
rangareddy:fix-12182-cloudwatch-batch-loss

Conversation

@rangareddy

@rangareddy rangareddy commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Describe the issue this Pull Request addresses

Closes #12182 and #13051. Both report the same thing: CloudWatch metrics are enabled, the job succeeds, and
no metrics arrive at all, with this in the logs every interval:

ERROR ScheduledReporter: Exception thrown from CloudWatchReporter#report. Exception was suppressed.
java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
	at org.apache.hudi.aws.cloudwatch.CloudWatchReporter.stageMetricDatum(CloudWatchReporter.java:281)
	at org.apache.hudi.aws.cloudwatch.CloudWatchReporter.processGauge(CloudWatchReporter.java:250)
	at org.apache.hudi.aws.cloudwatch.CloudWatchReporter.report(CloudWatchReporter.java:189)

stageMetricDatum takes the CloudWatch Table dimension from the part of the metric name before the first
dot, so a name with no dot cannot be mapped. HUDI-9068 (#12873) turned that ArrayIndexOutOfBoundsException
into an IllegalArgumentException, but it still throws, and the throw is what produces the reported symptom.
report() stages every gauge, counter, histogram, meter and timer into one list before calling
putMetricData, so throwing part-way through means the request is never sent: one unmappable name costs
every metric in that interval. ScheduledReporter then suppresses the exception, so the user sees a log line
and an empty dashboard.

Dotless names still reach the reporter on current master. HoodieMetadataMetrics#setMetric calls
metrics.registerGauge(action, value) with no prefix at all (HoodieMetadataMetrics.java:176), unlike
Metrics#registerGauges, which applies hoodie.metrics.reporter.metricsname.prefix. Two that are registered
on normal paths:

  • lookup_meta_index_bloom_filters_file_count -- BaseTableMetadata.java:212, on the bloom-index read path
  • <partition>_bootstrap_error -- HoodieBackedTableMetadataWriter.java:482

There is a second way in, which review turned up. The prefix defaults to "" and Metrics#registerGauges
still joins it with a dot (Metrics.java:157), so a name can arrive as .foo. That splits into two parts and
passed the old length check, then asked CloudWatch for an empty Table dimension value. CloudWatch rejects
the whole PutMetricData request for that, so the batch is lost again by a different route.

So any table with the metadata table enabled and CLOUDWATCH selected loses all of its metrics.

Summary and Changelog

  • stageMetricDatum skips a metric it cannot map instead of throwing, so the rest of the batch is still
    published. Both a name with no dot and a name whose first segment is empty are skipped.
  • The skipped name is logged at warn level once per name rather than once per interval, so a persistent
    offender does not spam the log every reporting cycle.

This replaces the checkArgument added by #12873, so to be explicit about why: the goal there -- do not
report a metric under a wrong or missing table dimension -- is preserved, because the metric is still not
reported and is now named in a warning. What changes is that it no longer takes the other metrics down with
it. Fail-fast was not reachable here in any case, since ScheduledReporter catches and suppresses everything
report() throws, so the throw could never surface to a caller -- it could only delete the batch. The
substantive half of #12873, the TABLE_SERVICE_EXECUTION_* prefixing in HoodieBackedTableMetadataWriter,
is untouched.

Verification

testReportOnMetricsWithoutTableName, which asserted the throw, is replaced by four tests in
TestCloudWatchReporter:

  • testReportSkipsMetricsWithoutTableNameAndPublishesTheRest -- a batch holding
    lookup_meta_index_bloom_filters_file_count plus a well-formed gauge publishes exactly one datum,
    testPrefix.gauge2, carrying Table=testTable.
  • testReportSkipsMetricsWithAnEmptyTableName -- the same for .gauge1.
  • testReportSendsNothingWhenEveryMetricIsUnmappable -- an interval with nothing mappable never calls
    putMetricData, rather than sending an empty request that AWS would reject.
  • testUnmappableMetricIsLoggedOncePerName -- two report() calls over the same registry produce exactly one
    WARN, captured with an AbstractAppender on the CloudWatchReporter logger.

Each one fails with its corresponding production change reverted, so none of them passes vacuously.
checkstyle:check and apache-rat:check are clean, and the rest of hudi-aws is unaffected.

Why the fix is reporter-side

Both prior fixes for this symptom went producer-side, so it is worth saying why this one does not.
1a5a9f7f03ec (HUDI-4439, #6164) fixed it by pushing the table name into the metadata table's
HoodieMetricsConfig. 100e9ac47590 (HUDI-9068, #12873) fixed two producer names and added the
checkArgument this PR replaces -- so that guard was a detector for producer bugs, not the fix.

The producer fix is the right one and is filed as #19507. HoodieMetadataMetrics already receives a
HoodieMetricsConfig (HoodieMetadataMetrics.java:91) and discards it, so retaining the prefix would fix
both the dotless names and the wrong-Table names described under Impact, for every reporter at once. It
renames metrics, which breaks existing Graphite, Prometheus, JMX and Datadog dashboards and needs a release
note, so it is not bundled here. This PR is explicitly the stop-the-bleeding half.

This is also the only place in the codebase where a reporter derives a dimension by splitting the metric
name, so the guard does not set a precedent other reporters have to follow. The closest analogue is
MetricUtils, used by DatadogReporter and PushGatewayReporter, and its convention is the one adopted
here: missing optional structure degrades gracefully, while genuinely malformed input throws. "No dot" is
missing structure, not malformed input.

Impact

CloudWatch users whose tables register an unmappable metric name go from receiving no metrics to receiving
the well-formed ones.

They also start receiving metrics under a wrong Table dimension, and being billed for them. Names that
contain a dot but no table pass the guard and are reported with the action as the table: <action>.count and
<action>.totalDuration give Table="initialize" and Table="lookup_partitions", while the
metadata-partition stats give Table="files" and Table="column_stats". Roughly 18 such metrics exist. This
was already broken, but invisible to exactly these users, because the batch died before anything was sent.
CloudWatch bills per unique metric name plus dimension set, so those become billable custom metrics the
moment this merges. #19507 is the producer-side fix; this PR deliberately does not rename metrics.

No effect on any other reporter, no effect on a CloudWatch setup that never produces such a name, and no API,
config or table format change.

Risk Level

low. One branch in one reporter, scoped to a case that currently throws. Verified that the well-formed
metrics still publish with the correct dimensions and that the rest of hudi-aws is unaffected.

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
  • CI passes on my PR

…e metric name

stageMetricDatum derives the CloudWatch Table dimension from the part of the metric
name before the first dot, so a name without one cannot be mapped. It threw for
that case, and report() stages every metric into one list before calling
putMetricData, so throwing part-way through meant the request was never sent: one
unmappable name cost every metric in the interval. ScheduledReporter then
suppresses the exception, so the user saw a log line and an empty dashboard.

Such names still reach the reporter on master. HoodieMetadataMetrics#setMetric
registers gauges with no prefix, unlike Metrics#registerGauges, so getStats
contributes a bare partitionCount and BaseTableMetadata a bare
lookup_meta_index_bloom_filters_file_count.

Skip the metric that cannot be mapped and publish the rest, logging the name once
rather than every interval. The intent of the previous check is kept - the metric is
still not reported under a wrong table, and is now named in a warning - without
taking the other metrics down with it. Fail-fast was never reachable here anyway,
since ScheduledReporter suppresses whatever report() throws.

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

test

cc @yihua

@codecov-commenter

codecov-commenter commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 76.97%. Comparing base (d98f2f1) to head (6d89b89).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...udi/aws/metrics/cloudwatch/CloudWatchReporter.java 80.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff            @@
##             master   #19476   +/-   ##
=========================================
  Coverage     76.96%   76.97%           
- Complexity    33850    33858    +8     
=========================================
  Files          2575     2575           
  Lines        143372   143382   +10     
  Branches      17572    17574    +2     
=========================================
+ Hits         110349   110366   +17     
+ Misses        24758    24752    -6     
+ Partials       8265     8264    -1     
Components Coverage Δ
hudi-common 82.26% <100.00%> (-0.01%) ⬇️
hudi-client 81.82% <ø> (-0.01%) ⬇️
hudi-flink 84.04% <ø> (+0.07%) ⬆️
hudi-spark-datasource 75.09% <ø> (-0.02%) ⬇️
hudi-utilities 73.67% <ø> (+0.04%) ⬆️
hudi-cli 15.32% <ø> (ø)
hudi-hadoop 63.49% <ø> (ø)
hudi-sync 70.87% <ø> (ø)
hudi-io 79.60% <100.00%> (ø)
hudi-timeline-service 83.44% <ø> (-0.30%) ⬇️
hudi-cloud 64.02% <80.00%> (+0.02%) ⬆️
hudi-kafka-connect 53.20% <ø> (ø)
Flag Coverage Δ
common-and-other-modules 49.53% <80.00%> (+<0.01%) ⬆️
flink-integration-tests 48.83% <0.00%> (+0.03%) ⬆️
hadoop-mr-java-client 43.75% <ø> (-0.01%) ⬇️
integration-tests 13.58% <0.00%> (-0.01%) ⬇️
spark-client-hadoop-common 48.67% <ø> (-0.01%) ⬇️
spark-java-tests 51.26% <0.00%> (-0.08%) ⬇️
spark-scala-tests 47.41% <0.00%> (-0.03%) ⬇️
utilities 36.58% <0.00%> (-0.01%) ⬇️

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

Files with missing lines Coverage Δ
...udi/aws/metrics/cloudwatch/CloudWatchReporter.java 91.66% <80.00%> (-0.58%) ⬇️

... and 20 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.

@github-actions github-actions Bot added the size:S PR with lines of changes in (10, 100] label Aug 3, 2026

@voonhous voonhous 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.

Reviewed the fix against master. The approach is right and the bug is real -- comments inline, ordered correctness first, then cleanliness nits marked optional.

…the log-once path

Review feedback.

The headline example in this PR was unreachable and I have replaced it. partitionCount
comes from HoodieMetadataMetrics.getStats only when detailed == true, while the
gauge-registering path calls getStats(false, ...); the only detailed=true caller is
HoodieBackedTableMetadata.stats(), whose sole consumer prints the map in hudi-cli and
registers nothing. The fixture and javadoc now use
lookup_meta_index_bloom_filters_file_count, which BaseTableMetadata registers on the
normal bloom-index read path.

An empty first segment was still losing the batch, which is the same bug class this PR
exists to fix. hoodie.metrics.reporter.metricsname.prefix defaults to "" and
Metrics#registerGauges still joins it with a dot, so ".foo" splits into two parts, passed
the length check, and asked CloudWatch for an empty Table dimension value - which it
rejects for the entire PutMetricData request. The guard now also rejects an empty first
segment.

The warning no longer claims a <table>.<metric> convention that Hudi does not follow: no
metadata metric carries a table name, and an operator has no knob that changes the names
being skipped. It now names the prefix config and points at apache#19507 for the producer side.

Three tests added: the empty-table-name case, an interval where every metric is
unmappable asserting that no empty PutMetricData request is sent, and one that reports
twice and asserts a single WARN, so the once-per-name set is no longer uncovered. Both new
guards fail the suite when reverted.

@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 changes the CloudWatch reporter so that an unmappable metric name (no dot, or an empty leading segment) is skipped and logged once rather than throwing mid-batch, which previously caused ScheduledReporter to suppress the exception and drop every metric in the interval. I traced the report path (empty metric data correctly sends no request), confirmed the unmappable-name set is bounded by a fixed vocabulary of dotless metric constants and is thread-safe, and verified the new tests recreate the reporter per test so the logged-once assertion is not order-sensitive. No issues flagged from this automated pass — a Hudi committer or PMC member can take it from here for a final review.

cc @yihua

@hudi-bot

hudi-bot commented Aug 4, 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

@github-actions github-actions Bot added size:M PR with lines of changes in (100, 300] and removed size:S PR with lines of changes in (10, 100] labels Aug 4, 2026

@voonhous voonhous 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.

LGTM

@voonhous voonhous closed this Aug 4, 2026
@voonhous voonhous reopened this Aug 4, 2026
@voonhous
voonhous enabled auto-merge (squash) August 4, 2026 11:59
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.

[SUPPORT] Hudi CloudWatchReporter error after 0.15.0 upgrade

5 participants