Skip to content

Commit 310408a

Browse files
austonliclaude
andcommitted
Fix status_code casing, document the resource-exhausted metrics, restore alert 27
Audit pass. Verified every metric name, tag, tag value, SDK option, and described behavior against SDK and server source rather than against the source material, which was wrong on several of them. Blocking fixes: - status_code values are UPPER_SNAKE_CASE in every SDK. The claim that Go reports PascalCase (NotFound) was wrong and had reached the metrics reference: Go's canonicalString returns "NOT_FOUND" (sdk-go internal/common/metrics/tags.go) and Java uses code.name() (sdk-java GrpcMetricsInterceptor). A Go reader filtering status_code="NotFound" would have matched nothing. - yarn check:metrics failed on the previous commit. status_code was added to two metrics without being added to the checker's tag allowlist or to the shared tag list on the page. Both now updated, along with cause. Reference additions and corrections: - Documented request_resource_exhausted and long_request_resource_exhausted (Go), which carry a cause tag naming the reason the Temporal Service rejected the request. Both were in bin/metrics-baseline.json with an empty note, which that file defines as "not yet reviewed"; removed the entries per its instructions. The three throttling sections now point at this metric instead of asking the reader to identify the cause with no means to do so. - poller_type value is workflow_sticky_task, not sticky_workflow_task (sdk-go constants.go, sdk-java PollerTypeMetricsTag). Scope, checked against the upstream alert set: - Restored alert 27, Unregistered Activity invocation, which had been dropped entirely rather than deferred as the previous PR description claimed. Go SDK only, and labelled as such. - Restored Critical severity on Workflow Task schedule-to-start and a 1m for duration on non-determinism error, both of which had been changed without a factual basis. - Sticky cache keeps its 15m for duration, which is a deliberate deviation: the gauge is occupancy, so a shorter window fires on idle Workers. Cross-page consistency: - Both schedule-to-start sections now explain why performance-bottlenecks gives a lower threshold for the same metric (investigation, not paging) and link the ready-made queries in cloud/worker-health. - Softened the claim that every SDK supports the BENIGN failure category, which was verified in Go only, and noted that internal failures increment activity_execution_failed regardless of category. Verified: yarn build exits 0 with onBrokenLinks and onBrokenAnchors both set to throw; check:metrics, check:metrics:sdks, and check:orphans pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent fd5ba22 commit 310408a

7 files changed

Lines changed: 75 additions & 24 deletions

File tree

bin/check-metrics-reference.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@ const SDKS = ['Core', 'Go', 'Java'];
2727
// against this, so a hyphen/underscore slip in either place is a failure.
2828
const TAGS = [
2929
'activity_type',
30+
'cause',
3031
'failure_reason',
3132
'namespace',
3233
'nexus_operation',
3334
'nexus_service',
3435
'operation',
3536
'poller_type',
37+
'status_code',
3638
'task_queue',
3739
'worker_type',
3840
'workflow_type',

bin/metrics-baseline.json

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,6 @@
2626
"sdks": "Go, Java",
2727
"note": "Deprecated in both SDKs. Superseded by local_activity_execution_failed."
2828
},
29-
{
30-
"name": "long_request_resource_exhausted",
31-
"sdks": "Go",
32-
"note": ""
33-
},
34-
{
35-
"name": "request_resource_exhausted",
36-
"sdks": "Go",
37-
"note": ""
38-
},
3929
{
4030
"name": "resource_slots_cpu_pid_output",
4131
"sdks": "Core, Java",

docs/best-practices/sdk-worker-alerting.mdx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ Triage guidance: [SDK Worker capacity](/troubleshooting/sdk-worker-capacity).
9494
| [Worker Task slots exhausted](/troubleshooting/sdk-worker-capacity#worker-task-slots-exhausted) | `worker_task_slots_available` | `worker_type` in `WorkflowWorker`, `ActivityWorker`, `LocalActivityWorker` | Reaches 0 | 2m | Critical |
9595
| [All pollers disconnected](/troubleshooting/sdk-worker-capacity#all-pollers-disconnected) | `num_pollers` | `poller_type` in `workflow_task`, `workflow_sticky_task`, `activity_task` | Reaches 0 | 5m | Critical |
9696
| [Task completions dropped to zero](/troubleshooting/sdk-worker-capacity#task-completions-dropped-to-zero) | `request` | `operation` in `RespondWorkflowTaskCompleted`, `RespondActivityTaskCompleted` | Rate reaches 0 while the Task Queue has demand | 5m | Critical |
97-
| [Workflow Task schedule-to-start latency elevated](/troubleshooting/sdk-worker-capacity#workflow-task-schedule-to-start-latency-elevated) | `workflow_task_schedule_to_start_latency` | `task_queue` | p99 above 5s | 5m | Warning |
97+
| [Workflow Task schedule-to-start latency elevated](/troubleshooting/sdk-worker-capacity#workflow-task-schedule-to-start-latency-elevated) | `workflow_task_schedule_to_start_latency` | `task_queue` | p99 above 5s | 5m | Critical |
9898
| [Workflow Task schedule-to-start latency severe](/troubleshooting/sdk-worker-capacity#workflow-task-schedule-to-start-latency-elevated) | `workflow_task_schedule_to_start_latency` | `task_queue` | p99 above 30m | 5m | Critical |
9999
| [Activity schedule-to-start latency severe](/troubleshooting/sdk-worker-capacity#activity-schedule-to-start-latency-elevated) | `activity_schedule_to_start_latency` | `task_queue` | p99 above 30m | 5m | Critical |
100100
| [Sticky cache holding zero entries under load](/troubleshooting/sdk-worker-capacity#sticky-cache-holding-zero-entries-under-load) | `sticky_cache_size` | Paired with a non-zero Workflow Task rate on the same Worker | Reaches 0 | 15m | Warning |
@@ -114,11 +114,12 @@ Triage guidance: [SDK execution failures](/troubleshooting/sdk-execution-failure
114114

115115
| Failure mode | Metric | Condition | Threshold | `for` | Default severity |
116116
| --- | --- | --- | --- | --- | --- |
117-
| [Non-determinism error](/troubleshooting/sdk-execution-failures#non-determinism-error) | `workflow_task_execution_failed` | `failure_reason=NonDeterminismError` | Any occurrence | 5m | Critical |
117+
| [Non-determinism error](/troubleshooting/sdk-execution-failures#non-determinism-error) | `workflow_task_execution_failed` | `failure_reason=NonDeterminismError` | Any occurrence | 1m | Critical |
118118
| [gRPC message too large](/troubleshooting/sdk-execution-failures#grpc-message-too-large) | `workflow_task_execution_failed` | `failure_reason=GrpcMessageTooLarge` | Any occurrence | 1m | Critical |
119119
| [Workflow Task execution failures elevated](/troubleshooting/sdk-execution-failures#workflow-task-execution-failures-elevated) | `workflow_task_execution_failed` | `failure_reason=WorkflowError` | Rate above 10/s | 2m | Warning |
120120
| [Workflow Task execution latency high](/troubleshooting/sdk-execution-failures#workflow-task-execution-latency-high) | `workflow_task_execution_latency` | `task_queue`, `workflow_type` | p99 above 10s | 5m | Critical |
121121
| [Activity execution failures elevated](/troubleshooting/sdk-execution-failures#activity-execution-failures-elevated) | `activity_execution_failed` | `activity_type` | Rate above 10/s | 2m | Warning |
122+
| [Unregistered Activity invocation](/troubleshooting/sdk-execution-failures#unregistered-activity-invocation) — Go SDK only | `unregistered_activity_invocation` | `activity_type`, `task_queue`, `workflow_type` | Any occurrence | 1m | Critical |
122123
| [Local Activity latency exceeds the heartbeat timeout](/troubleshooting/sdk-execution-failures#local-activity-latency-exceeds-the-heartbeat-timeout) | `local_activity_execution_latency` | `activity_type` | p99 above 30m | 5m | Critical |
123124

124125
## Set up the alerts {/* #set-up-the-alerts */}
@@ -138,8 +139,8 @@ The names you query depend on your SDK and metrics reporter:
138139
- **Histograms** take a `_seconds_bucket` suffix on the underlying bucket series: `temporal_workflow_task_schedule_to_start_latency_seconds_bucket`.
139140
- **Gauges take no suffix.** `temporal_num_pollers`, `temporal_worker_task_slots_available`, and `temporal_sticky_cache_size` are gauges. Querying them with `_total` returns no data.
140141

141-
Tag values also vary.
142-
The `status_code` tag is `PascalCase` in the Go SDK (`NotFound`) and `UPPER_SNAKE_CASE` in the Java and Core SDKs (`NOT_FOUND`).
142+
Tag coverage also varies.
143+
`status_code` values are `UPPER_SNAKE_CASE` in every SDK, matching the gRPC status code names (`NOT_FOUND`, `RESOURCE_EXHAUSTED`), and the tag can be suppressed through Client options.
143144
Some tags are not emitted by every SDK — `sticky_cache_size`, for example, carries `namespace` only in the TypeScript and Java SDKs and `task_queue` only in TypeScript.
144145

145146
Confirm the exact names and tags in your own metrics endpoint before writing queries.

docs/references/sdk-metrics.mdx

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ Each metric may have some combination of the following [tags](/glossary#tag) att
6464
- `namespace`: Namespace the Worker is bound to
6565
- `poller_type`: One of the following:
6666
- `workflow_task`
67+
- `workflow_sticky_task`
6768
- `activity_task`
6869
- `nexus_task`
69-
- `sticky_workflow_task`
7070
- `worker_type`: One of the following:
7171
- `ActivityWorker`
7272
- `WorkflowWorker`
@@ -75,6 +75,8 @@ Each metric may have some combination of the following [tags](/glossary#tag) att
7575
- `activity_type`: The name of the Activity Function the metric is associated with
7676
- `workflow_type`: The name of the Workflow Function the metric is associated with
7777
- `operation`: RPC method name; available for metrics related to Temporal Client gRPC requests
78+
- `status_code`: gRPC status code returned by the Temporal Service, in `UPPER_SNAKE_CASE` (for example, `NOT_FOUND`); available on Temporal Client gRPC request failure metrics
79+
- `cause`: reason the Temporal Service rejected a request as resource exhausted (for example, `RESOURCE_EXHAUSTED_CAUSE_RPS_LIMIT`); available on the resource-exhausted metrics
7880

7981
Some tags may not be available in every SDK, and Histogram metrics may have different buckets in each SDK.
8082

@@ -96,6 +98,7 @@ Some tags may not be available in every SDK, and Histogram metrics may have diff
9698
| [temporal_long_request](#long_request) | Service Client | Counter | Core, Go, Java |
9799
| [temporal_long_request_failure](#long_request_failure) | Service Client | Counter | Core, Go, Java |
98100
| [temporal_long_request_latency](#long_request_latency) | Service Client | Histogram | Core, Go, Java |
101+
| [temporal_long_request_resource_exhausted](#long_request_resource_exhausted) | Service Client | Counter | Go |
99102
| [temporal_nexus_poll_no_task](#nexus_poll_no_task) | Worker | Counter | Core, Go, Java |
100103
| [temporal_nexus_task_schedule_to_start_latency](#nexus_task_schedule_to_start_latency) | Worker | Histogram | Core, Go, Java |
101104
| [temporal_nexus_task_execution_failed](#nexus_task_execution_failed) | Worker | Counter | Core, Go, Java |
@@ -106,6 +109,7 @@ Some tags may not be available in every SDK, and Histogram metrics may have diff
106109
| [temporal_request](#request) | Service Client | Counter | Core, Go, Java |
107110
| [temporal_request_failure](#request_failure) | Service Client | Counter | Core, Go, Java |
108111
| [temporal_request_latency](#request_latency) | Service Client | Histogram | Core, Go, Java |
112+
| [temporal_request_resource_exhausted](#request_resource_exhausted) | Service Client | Counter | Go |
109113
| [temporal_resource_slots_cpu_usage](#resource_slots_cpu_usage) | Worker | Gauge | Core, Java |
110114
| [temporal_resource_slots_mem_usage](#resource_slots_mem_usage) | Worker | Gauge | Core, Java |
111115
| [temporal_sticky_cache_hit](#sticky_cache_hit) | Worker | Counter | Core, Go, Java |
@@ -256,7 +260,7 @@ This number is included into the total `long_request` counter for long poll RPC
256260
- Tags: `namespace`, `operation`, `status_code`
257261

258262
The `status_code` tag carries the gRPC status code returned by the Temporal Service.
259-
Its casing differs by SDK: the Go SDK reports `PascalCase` values such as `NotFound`, while the Java and Core SDKs report `UPPER_SNAKE_CASE` values such as `NOT_FOUND`.
263+
Values are `UPPER_SNAKE_CASE`, matching the gRPC status code names: `NOT_FOUND`, `RESOURCE_EXHAUSTED`, `UNIMPLEMENTED`, `INTERNAL`, and so on.
260264
The tag can be suppressed through Client options, so confirm it is present in your own metrics endpoint before writing queries against it.
261265

262266
### `long_request_latency`
@@ -267,6 +271,18 @@ Latency of a Temporal Client gRPC long poll request.
267271
- Available in: Core, Go, Java
268272
- Tags: `namespace`, `operation`
269273

274+
### `long_request_resource_exhausted`
275+
276+
Temporal Client made an RPC long poll request that the Temporal Service rejected as resource exhausted.
277+
These failures are also counted in `long_request_failure` with `status_code=RESOURCE_EXHAUSTED`; this metric adds the reason for the rejection.
278+
279+
- Type: Counter
280+
- Available in: Go
281+
- Tags: `namespace`, `operation`, `cause`
282+
283+
The `cause` tag carries the reason the Temporal Service rejected the request, such as `RESOURCE_EXHAUSTED_CAUSE_RPS_LIMIT`, `RESOURCE_EXHAUSTED_CAUSE_CONCURRENT_LIMIT`, `RESOURCE_EXHAUSTED_CAUSE_SYSTEM_OVERLOADED`, or `RESOURCE_EXHAUSTED_CAUSE_CIRCUIT_BREAKER_OPEN`.
284+
The cause determines the remedy, so prefer this metric over `long_request_failure` when investigating throttling.
285+
270286
### `nexus_poll_no_task`
271287

272288
A Nexus Worker poll for a Nexus Task timed out, and no Nexus Task is available to pick from the Task Queue.
@@ -359,7 +375,7 @@ This number is included into the total `request` counter for RPC requests.
359375
- Tags: `namespace`, `operation`, `status_code`
360376

361377
The `status_code` tag carries the gRPC status code returned by the Temporal Service.
362-
Its casing differs by SDK: the Go SDK reports `PascalCase` values such as `NotFound`, while the Java and Core SDKs report `UPPER_SNAKE_CASE` values such as `NOT_FOUND`.
378+
Values are `UPPER_SNAKE_CASE`, matching the gRPC status code names: `NOT_FOUND`, `RESOURCE_EXHAUSTED`, `UNIMPLEMENTED`, `INTERNAL`, and so on.
363379
The tag can be suppressed through Client options, so confirm it is present in your own metrics endpoint before writing queries against it.
364380

365381
### `request_latency`
@@ -370,6 +386,18 @@ Latency of a Temporal Client gRPC request.
370386
- Available in: Core, Go, Java
371387
- Tags: `namespace`, `operation`
372388

389+
### `request_resource_exhausted`
390+
391+
Temporal Client made an RPC request that the Temporal Service rejected as resource exhausted.
392+
These failures are also counted in `request_failure` with `status_code=RESOURCE_EXHAUSTED`; this metric adds the reason for the rejection.
393+
394+
- Type: Counter
395+
- Available in: Go
396+
- Tags: `namespace`, `operation`, `cause`
397+
398+
The `cause` tag carries the reason the Temporal Service rejected the request, such as `RESOURCE_EXHAUSTED_CAUSE_RPS_LIMIT`, `RESOURCE_EXHAUSTED_CAUSE_CONCURRENT_LIMIT`, `RESOURCE_EXHAUSTED_CAUSE_SYSTEM_OVERLOADED`, or `RESOURCE_EXHAUSTED_CAUSE_CIRCUIT_BREAKER_OPEN`.
399+
The cause determines the remedy, so prefer this metric over `request_failure` when investigating throttling.
400+
373401
### `resource_slots_cpu_usage`
374402

375403
CPU usage as a value between 0 and 100. As perceived by the resource-based slots tuner, if

docs/troubleshooting/sdk-execution-failures.mdx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ A non-determinism error does not resolve on its own.
4444
1. **Read the error.** The `WorkflowTaskFailed` event in an affected Execution's Event History contains the message identifying exactly where replay diverged and which command was expected versus produced. This is the most direct signal for root cause.
4545
1. **Determine whether this is a code change or a deploy artifact.** Common causes:
4646
- A code change added, removed, or reordered commands — Activity scheduling, Timers, Signals, Child Workflows — without a versioning guard. In-flight Executions that built History under the old code fail on the new code.
47-
- A rolling restart with old and new Worker versions briefly running together. Some Executions fail transiently and recover once the rollout completes, which is why this alert needs a `for` duration longer than your deploy takes.
47+
- A rolling restart with old and new Worker versions briefly running together. Some Executions fail transiently and recover once the rollout completes. If your deploys routinely trigger this alert, lengthen its `for` duration past how long a rollout takes.
4848
- Changed Activity or Timer parameters in existing Workflow code without versioning.
4949
1. **Roll back if it is not resolving.** If the errors started after a deploy and are not clearing on their own, roll the Worker back to the previous version. Affected Executions resume on their next Workflow Task retry once compatible code is running. Then introduce a proper versioning guard before redeploying — see [Versioning Workflows](/workflow-definition#workflow-versioning) and [Worker Versioning](/production-deployment/worker-deployments/worker-versioning).
5050
1. **Watch Worker pressure.** Continuous retries put sustained load on Workflow Workers. Cross-check [Worker Task slots exhausted](/troubleshooting/sdk-worker-capacity#worker-task-slots-exhausted) for `worker_type=WorkflowWorker` and [Workflow Task execution latency high](#workflow-task-execution-latency-high) — a high volume of retries can saturate capacity and affect healthy Executions on the same Task Queue.
@@ -152,7 +152,29 @@ At scale, sustained retry bursts put significant pressure on Task matching and t
152152
1. **Determine whether this is transient or a bug.** A downstream service outage, network partition, or database timeout recovers on its own — watch whether the rate falls. A persistent code bug does not.
153153
1. **Check downstream service health.** A degraded dependency is a common cause of sustained failure bursts. If the dependency is throttling, confirm your [Retry Policy](/encyclopedia/retry-policies) has appropriate backoff — without it, retry bursts amplify the pressure you are already applying.
154154
1. **Check schedule-to-start latency.** A growing retry backlog shows up as elevated [Activity schedule-to-start latency](/troubleshooting/sdk-worker-capacity#activity-schedule-to-start-latency-elevated) even after the failure rate drops.
155-
1. **Mark expected failures as benign.** If your design intentionally fails Activities — polling patterns, Saga compensations, flow control through exceptions — mark those `ApplicationFailure` instances with category `BENIGN`. All SDKs support this and suppress this metric for them, which lets this alert track unexpected failures without per-`activity_type` threshold tuning.
155+
1. **Mark expected failures as benign.** If your design intentionally fails Activities — polling patterns, Saga compensations, flow control through exceptions — mark those `ApplicationFailure` instances with category `BENIGN`, which suppresses this metric for them and lets the alert track unexpected failures without per-`activity_type` threshold tuning. Confirm your SDK version supports the category before relying on it.
156+
157+
Note that internal failures — a context propagation error or a context timeout, rather than an Activity returning a failure — increment this counter regardless of category.
158+
159+
## Unregistered Activity invocation {/* #unregistered-activity-invocation */}
160+
161+
**Metric:** [`temporal_unregistered_activity_invocation`](/references/sdk-metrics#unregistered_activity_invocation), tagged `activity_type`, `task_queue`, and `workflow_type`
162+
163+
A Workflow scheduled an Activity that the Worker polling that Task Queue has no registered implementation for.
164+
165+
This metric is emitted by the Go SDK only.
166+
167+
**Why it matters.**
168+
The Activity cannot execute. It will keep being retried against a Worker that cannot run it until the Activity's `scheduleToClose` timeout expires, or indefinitely if no such timeout is set — so the Workflow Execution waiting on it makes no progress.
169+
170+
This is almost always a deployment error rather than a runtime condition: Workflow code that schedules an Activity the deployed Worker does not register. It does not resolve on its own.
171+
172+
**Triage.**
173+
174+
1. **Identify the Activity and the Task Queue.** The `activity_type` and `task_queue` tags name both. The `workflow_type` tag identifies which Workflow is scheduling it.
175+
1. **Check whether the Activity is registered on the right Worker.** Confirm the Worker polling that Task Queue registers that Activity type. A common cause is registering the Activity on a Worker polling a different Task Queue.
176+
1. **Check for a partial rollout.** If Workflow code that schedules a new Activity deployed ahead of the Worker that implements it, some Workers will be running without the registration. Complete the rollout.
177+
1. **Check for a renamed Activity.** Changing an Activity's registered name while Executions are in flight leaves those Executions scheduling the old name. Register both names until the in-flight Executions drain, or use a versioning guard.
156178

157179
## Local Activity latency exceeds the heartbeat timeout {/* #local-activity-latency-exceeds-the-heartbeat-timeout */}
158180

0 commit comments

Comments
 (0)