Skip to content

[FLINK-40292][table-runtime] Add UdfMetrics helper for UDF metrics - #28878

Open
weiqingy wants to merge 2 commits into
apache:masterfrom
weiqingy:flink-38071-pr1-helper
Open

[FLINK-40292][table-runtime] Add UdfMetrics helper for UDF metrics#28878
weiqingy wants to merge 2 commits into
apache:masterfrom
weiqingy:flink-38071-pr1-helper

Conversation

@weiqingy

@weiqingy weiqingy commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

This is the first PR of the FLIP-485 implementation, split into a stack of small, independently reviewable PRs under the umbrella issue FLINK-38071. Landing order:

Step Sub-task Scope
PR-1 (this PR) FLINK-40292 UdfMetrics helper: registration, sampling, timing, exception counting
PR-2 FLINK-40293 Config options + sync scalar/table instrumentation
PR-3 FLINK-40294 Async scalar/table instrumentation
PR-4 FLINK-40295 User-facing documentation

FLIP-485 passed the vote on 2026-08-01. This series supersedes the single reference-implementation draft in #28692, which is closed.

What is the purpose of the change

FLIP-485 adds opt-in, per-operator observability for SQL/Table user-defined functions, so operators can see inside UDF "black boxes" when debugging latency or errors, and so autoscaling gets a reliable "the problem is in user code" signal.

This PR adds only the shared runtime helper, UdfMetrics. It owns metric registration, the sampling decision, timing, and exception counting, and it is the piece both the synchronous and the asynchronous paths use. It has no caller yet; the first caller arrives in PR-2. Keeping it separate makes the sampling and registration logic reviewable on its own.

Brief change log

  • Add UdfMetrics in flink-table-runtime (org.apache.flink.table.runtime.operators.metrics, beside SimpleGauge). It registers udfProcessingTime and udfExceptionCount under addGroup("udf", udfName) on the operator metric group, so the full identifier is <operator_name>.udf.<udf_name>.<metric>.
  • udfProcessingTime is a DescriptiveStatisticsHistogram of per-invocation nanoseconds, sampled with the same counter-based scheme as state latency tracking (FLINK-21736), including the interval == 1 "measure every call" case.
  • udfExceptionCount is a ThreadSafeSimpleCounter, incremented on every exception and not sampled.
  • The histogram is safe to update from an async callback thread; the sampling counter is only advanced on the task thread at dispatch.

Verifying this change

This change added tests and can be verified as follows:

  • UdfMetricsTest covers metric registration and naming, the sampling decision across the interval boundary, the sample-interval = 1 case, rejection of a non-positive interval, timing recorded into the histogram, exception counting, and that two UDFs registered on the same operator get independent metrics.

Does this pull request potentially affect one of the following parts:

  • Dependencies (does it add or upgrade a dependency): no
  • The public API, i.e., is any changed class annotated with @Public(Evolving): no
  • The serializers: no
  • The runtime per-record code paths (performance sensitive): no. Nothing calls this class yet; the call sites and their gating arrive in PR-2 and PR-3.
  • Anything that affects deployment or recovery: JobManager (and its components), Checkpointing, Kubernetes/Yarn, ZooKeeper: no
  • The S3 file system connector: no

Documentation

  • Does this pull request introduce a new feature? yes, as the first step of FLIP-485
  • If yes, how is the feature documented? JavaDocs here; the user-facing documentation lands in PR-4.

Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: Claude Code (Anthropic Claude Opus 4.8 and Claude Opus 5)

@weiqingy

weiqingy commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

This is the first of four PRs landing the accepted design (FLIP-485). It replaces the single reference-implementation draft in #28692, which I'm closing.

This PR adds only the UdfMetrics helper and its unit test. It has no caller yet on purpose. The first caller arrives in PR 2, which adds the two config options and instruments synchronous scalar and table UDF calls. Splitting it out keeps the sampling and registration logic reviewable on its own, since it is the piece both the sync and async paths share.

The series:

  1. UdfMetrics helper (this PR)
  2. table.exec.udf-metric-enabled / table.exec.udf-metric.sample-interval plus synchronous scalar and table instrumentation
  3. Asynchronous scalar and table instrumentation
  4. Documentation

Each PR builds and passes its own tests independently. I'll open the next one as the previous merges.

Hi @xuyangzhong @RocMarshal @HuangZhenQiu, since you voted on the FLIP, I’d appreciate it if you could take a look when you have time.

@flinkbot

flinkbot commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

CI report:

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

Add a reusable UdfMetrics helper that registers udfProcessingTime (a
DescriptiveStatisticsHistogram of per-invocation nanoseconds) and
udfExceptionCount (a ThreadSafeSimpleCounter) under udf.<udfName> on the
executing operator's metric group, and owns the sampling decision, timing,
and exception counting shared by the sync and async instrumentation paths.

Sampling follows state latency tracking (FLINK-21736), including the
interval == 1 case that measures every invocation. The histogram is safe to
update from an async callback thread; the sampling counter is only advanced
on the task thread at dispatch.

No call site is added here; the first caller arrives with the sync
instrumentation.
@weiqingy
weiqingy force-pushed the flink-38071-pr1-helper branch from 6556131 to 15bcbfa Compare August 2, 2026 23:50
* time should be measured. Must be called once per invocation, on the task thread only.
*/
public boolean shouldSample() {
if (sampleInterval == 1) {

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.

If the sampling is determined 1 / operation counts, sampleInterval is probably not the best variable name.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fair point, the name doesn't tell you that on its own.

Would it be worth keeping for consistency though? Flink's state latency tracking does the same kind of sampling with the same word: state.latency-track.sample-interval is an int, defaults to 100, and means "track the latency every 100 access requests". Its MetricsTrackingStateConfig declares the same private final int sampleInterval with the same >= 1 check. FLIP-485 followed that pattern, and the matching option here, table.exec.udf-metric.sample-interval, lands in the next PR.

What I'd worry about with a rename is the field no longer matching the option users actually set. So I've made the field say it counts invocations, not time. Does that address it, or would you still prefer a different name?

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.

Make sense. Thanks for the context.

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

Two minor comments.

…ations

State at the field declaration and in the register() @PARAM that sampleInterval
is a number of invocations rather than a duration, so a reader does not have to
infer the unit from the class javadoc.
* The two registered metrics are safe for the async completion thread to touch: the histogram
* synchronizes internally and the exception counter is {@link ThreadSafeSimpleCounter}.
*/
public final class UdfMetrics {

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.

Suggested change
public final class UdfMetrics {
public final class UDFMetrics {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the review!

Flink seems to treat acronyms as words in class names: AbstractUdfStreamOperator and UdfStreamOperatorFactory in flink-runtime, and TableAbstractCoUdfStreamOperator right next door in flink-table-runtime. The wider pattern looks the same, Sql and Json and Rest outnumber the all-caps spellings in class names by a long way.

The all-caps ones I can find are the older RuntimeUDFContext family in flink-core and a couple of test fixtures.

Happy to rename if you'd still prefer it though. It's this class plus its two tests and a few codegen references.

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.

Thanks for the clarify.
Just keep original naming.

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

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.

4 participants