Skip to content

fix(metrics): normalize persistence metric labels for Prometheus - #8064

Merged
neil-xie merged 5 commits into
cadence-workflow:masterfrom
ribaraka:metric-label-inconsistency
Jul 20, 2026
Merged

fix(metrics): normalize persistence metric labels for Prometheus#8064
neil-xie merged 5 commits into
cadence-workflow:masterfrom
ribaraka:metric-label-inconsistency

Conversation

@ribaraka

@ribaraka ribaraka commented May 7, 2026

Copy link
Copy Markdown
Contributor

What changed?
Normalized persistence metric label sets emitted by the metered persistence wrappers so Prometheus does not reject metrics registered under the same name with different label keys.

  • Always emitting is_retry on metered persistence wrapper paths.
  • Defaulting task_category="none" on direct persistence metrics when no task category applies.
  • Renaming collision-prone persistence rollup metrics so rollups do not emit into already-used direct metric names.

Why?
#7844
Prometheus requires every metric name to use a consistent label-name set. Cadence persistence metrics could emit the same metric name from different wrapper paths with different labels, causing Prometheus reporter warnings and dropped metrics.

How did you test it?
Added a Prometheus-backed regression test that exercises persistence metered wrapper paths with different label dimensions and verifies no Prometheus registration errors are emitted.
go test ./common/persistence/wrappers/metered

Potential risks
This changes some persistence rollup metric names, which may require dashboard or alert updates for users relying on the old aggregate persistence metric names.

Release notes
This PR changes the persistence metrics contract.
Persistence rollup metrics were renamed to avoid prometheus label-set conflicts with directly emitted persistence metrics:

  • persistence_requests rollup is now persistence_requests_rollup
  • persistence_latency rollup is now persistence_latency_rollup
  • persistence_latency_ns rollup is now persistence_latency_ns_rollup

The direct persistence_requests / persistence_latency / persistence_latency_ns metrics still exist, but they are no longer reused as rollup targets. During a mixed-version rollout, dashboards may need to account for both old and new rollup names until all clusters are upgraded. This PR also normalizes labels on direct persistence metrics, adding labels like is_retry and task_category="none" where they were previously missing. Selectors by metric name should still match, but dashboards/alerts that aggregate or group by labels may need to explicitly aggregate away those new dimensions.

Documentation Changes

ribaraka added 3 commits May 8, 2026 00:43
…l() by domain tag

The metered.tmpl wrapper never captured retryCount from context,
while metered_execution.tmpl did. Both templates emit on persistence_requests_per_domain, so
Prometheus saw two series with mismatched label keys
({operation,domain,is_retry} vs {operation,domain}) and silently
rejected whichever registered second.

Capture retryCount in the non-execution template and append
metrics.IsRetryTag(retryCount > 0) to every metered emission. This
also makes is_retry truthful for retried calls, which are exercised by common/backoff retry.

Naively adding the tag would have re-routed methods whose requests
have no MetricTags() impl (CreateShard, GetShard, UpdateShard,
FetchDynamicConfig, UpdateDynamicConfig, all QueueManager methods)
from persistence_requests to persistence_requests_per_domain, because
base.call() decides the metric by len(tags) > 0 rather than by the
presence of a domain tag — a silent rename and a new collision.

Switch base.call() to route by tag content (hasDomainTag) so the
choice between persistence_requests and persistence_requests_per_domain
is governed by whether a DomainTag is present, independent of how
many auxiliary tags (is_retry, task_category, ...) accompany it.

Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
Persistence base metric names like persistence_requests are emitted directly by no-domain paths such as shard, queue, config, and history-task persistence calls.

Using those same names as per-domain rollup targets makes two different emission paths share a metric family with different label sets. Keep the aggregate behavior, but give persistence rollups explicit _rollup names to avoid descriptor collisions while leaving generic rollup behavior unchanged.

Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
  Some history-task requests add a task_category tag through MetricTags().
  When those requests emit direct persistence metrics, they produce
  persistence_requests / persistence_latency / persistence_latency_ns with
  {operation, is_retry, task_category}. Other direct persistence emissions
  use the same metric names with only {operation, is_retry}, creating
  different label keys for the same Prometheus metric family.

  Inject task_category="none" in callWithoutDomainTag and in call()'s
  no-domain branch so direct persistence_* emissions carry a uniform label
  set regardless of request type. Apply the same normalization to the
  overall scope inside callWithDomainAndShardScope because it emits the
  direct persistence_latency_ns metric.

  Per-domain (*_per_domain) and per-shard (*_per_shard) emissions are left
  unchanged. task_category does not appear on those metric paths today, and
  adding it there would be unnecessary label noise.

Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
@ribaraka
ribaraka force-pushed the metric-label-inconsistency branch from 8fcfc1b to 3317c28 Compare May 7, 2026 22:43
Comment thread common/persistence/wrappers/metered/base.go
@neil-xie

Copy link
Copy Markdown
Member

Hey, the code changes look good to me, I am checking we will need to do with the metrics rename internally since we have bunch of alerts/dashboards based on it, and our deployment will take time to roll out to all production clusters

@ribaraka

Copy link
Copy Markdown
Contributor Author

@neil-xie Thanks for checking.

I'll add this clearly to the release notes so the rollout impact is visible.

@neil-xie neil-xie 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 for fixing it.
As discussed, I will merge when we make the changes internally since it will impact existing dashboards/alerts

ribaraka added 2 commits June 23, 2026 20:41
Apply the is_retry metered template to historytaskdlq_generated.go so the
generated persistence wrappers are in sync after merging master.

Signed-off-by: Stanislav Bychkov <stanislav.bychkov@netapp.com>
@ribaraka
ribaraka force-pushed the metric-label-inconsistency branch from 6d60683 to 9f415a6 Compare July 1, 2026 21:50
@gitar-bot

gitar-bot Bot commented Jul 1, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 1 resolved / 1 findings

Normalizes persistence metric labels and renames rollup metrics to resolve Prometheus label-set conflicts. The previously missing task category tag in the call() path is also addressed, resulting in consistent metric reporting.

✅ 1 resolved
Bug: Per-domain path in call() still lacks ensureTaskCategoryTag

📄 common/persistence/wrappers/metered/base.go:164-168 📄 common/persistence/wrappers/metered/base.go:217
In call(), when perDomain == true (line 165-168), ensureTaskCategoryTag is NOT applied to tags. This means PersistenceRequestsPerDomain / PersistenceLatencyPerDomain metrics can still be emitted with inconsistent label sets:

  • GetHistoryTasks with a domain provides task_category via MetricTags() → emits with {domain, is_retry, task_category}
  • GetWorkflowExecution with a domain does NOT provide task_category → emits with {domain, is_retry}

Similarly, in callWithDomainAndShardScope(), domainMetricsScope (line 217) uses raw additionalTags without ensuring task_category, so PersistenceRequestsPerDomain emitted there will also have inconsistent labels depending on whether the request's MetricTags() includes task_category.

This is the same class of Prometheus registration error this PR aims to fix, but for the per-domain metric path.

Rules ✅ All requirements met

Repository Rules

GitHub Issue Linking Requirement: The PR description includes a valid link to a cadence-workflow issue (#7844).
PR Description Quality Standards: The PR description follows the required structure and guidance, providing clear technical rationale, reproduction steps for tests, and well-detailed release notes.

2 rules not applicable. Show all rules by commenting gitar display:verbose.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@neil-xie
neil-xie merged commit f4c639a into cadence-workflow:master Jul 20, 2026
64 of 65 checks passed

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

metered_execution.tmpl is not updated

Shaddoll added a commit that referenced this pull request Jul 22, 2026
<!-- If you are new to contributing or want a refresher, please read
./pull_request_guidance.md -->
**What changed?**
Normalized persistence metric label sets emitted by the metered
persistence wrappers so Prometheus does not reject metrics registered
under the same name with different label keys.

- Defaulting task_category="none" on
`persistence_response_payload_size`, `persistence_response_row_size` and
`persistence_empty_response` when no task category applies.

related to #7844

**Why?**
follow up of #8064
Prometheus requires every metric name to use a consistent label-name
set. Cadence persistence metrics could emit the same metric name from
different wrapper paths with different labels, causing Prometheus
reporter warnings and dropped metrics.

**How did you test it?**
Added a Prometheus-backed regression test that exercises persistence
metered wrapper paths with different label dimensions and verifies no
Prometheus registration errors are emitted.
go test ./common/persistence/wrappers/metered

**Potential risks**
No.

**Release notes**
This change removes domain label/tag from
`persistence_response_payload_size`, `persistence_response_row_size` and
`persistence_empty_response` metrics and add `task_category` label/tag
to these metrics with default value "none". Selectors by metric name
should still match, but dashboards/alerts that aggregate or group by
labels may need to explicitly aggregate away those new dimensions.


**Documentation Changes**
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.

3 participants