Temporal 応用 #5: 本番運用の勘所
基本編・応用編の前回までで「作り方」はだいぶ手に入ったはずなので、最終回は「育て方」をやりましょう。
Temporal は“ワークフローという名の長距離列車”を走らせる仕組みなんですよね。開発環境では駅員が目視で何とかなっても、本番になると列車の本数も距離も桁が変わります。なので メトリクス・監視・運用の型がないと、駅が一瞬でパンクします。
この記事のゴールは「どこを見て、どうアラートを作り、詰まったらどの順に崩すか」を腹落ちさせることです。
1. 監視の全体像:Temporal は「3つの箱」を見る
まず、観測ポイントを3つに分けると整理が楽ですよ。
[Worker(あなたのアプリ)] <--gRPC--> [Temporal Server] <---> [DB/ES等]
| SDK metrics/logs/traces | server metrics | infra metrics
- Worker(SDK側): 取りこぼし・詰まり・リトライ嵐が最初に出る場所
- Temporal Server: タスクキューの滞留、スケジューリング遅延、内部エラー
- 周辺基盤: DB 遅延・枯渇、ネットワーク、ノード不調
この記事は「Worker(SDK)と運用手順」を中心に書きます(自前ホスティングの深掘りはしません)。
2. SDK メトリクスと Prometheus 連携(Go)
Temporal の運用で一番効くのは「Worker をメトリクスでしゃべらせる」ことです。
Worker は、駅員が見ている 改札の通過人数・ホームの混雑・遅延を全部知ってる存在なので、ここを黙らせないのが大事です。
2.1 Prometheus エクスポート(最小構成)
Go SDK では tally を使ってメトリクススコープを渡します(Temporal SDK が内部でメトリクスを発火します)。
import (
"github.com/prometheus/client_golang/prometheus/promhttp"
"net/http"
"github.com/uber-go/tally/v4"
"github.com/uber-go/tally/v4/prometheus"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/worker"
)
func main() {
reporter := prometheus.NewReporter(prometheus.Options{
Registry: promhttp.Handler().(prometheus.Gatherer), // 例示:実際は専用Registry推奨
}, prometheus.DefaultSanitizer)
scope, _ := tally.NewRootScope(tally.ScopeOptions{
Prefix: "temporal_worker",
Reporter: reporter,
}, 1)
c, _ := client.Dial(client.Options{
HostPort: "temporal:7233",
MetricsHandler: scope, // ここが肝
})
w := worker.New(c, "payment-task-queue", worker.Options{
MetricsHandler: scope,
})
go func() {
http.Handle("/metrics", promhttp.Handler())
_ = http.ListenAndServe(":2112", nil)
}()
_ = w.Run(worker.InterruptCh())
}
実運用では Prometheus の
Registryを明示し、アプリのメトリクスと混ざり方を設計するのが吉です(名前衝突・ラベル設計の事故が減ります)。
2.2 何のメトリクスを見るべき?
SDK/Worker 側でまず見たいのは、ざっくりこの4カテゴリです。
- タスク処理のスループット(処理できているか)
- レイテンシ(処理に時間がかかってないか)
- 失敗率/リトライ率(燃えてないか)
- ポーリング関連(タスクキューから取れているか)
Temporal のメトリクス名はバージョンで揺れることもあるので、最初は「メトリクス名を暗記」よりも、ダッシュボードで“形”を掴むのが効きます。
具体的には:
- Worker が生きてるのに ポーリングが減る → 取りに行けてない(ネットワーク、権限、スロット枯渇)
- 処理数が横ばいなのに タスクキュー滞留が増える → 供給過多か Worker 容量不足
- 失敗率が増える → Activity の依存先が死んでる、Non-Determinism、タイムアウト設定ミス
3. アラート設計のポイント:「症状」と「原因」を分ける
アラートは「鳴らすこと」より「鳴った後に動けること」が大事ですよね。
Temporal では特に、症状アラートと原因アラートを分けると運用が安定します。
3.1 症状アラート(ユーザー影響に近い)
- Workflow 完了までの遅延が急増
- Task Queue の backlog が増え続ける(一定時間で戻らない)
- Workflow/Activity の failure が増加
これは「列車が遅れてます」系。オンコールがまず見るやつです。
3.2 原因アラート(調査をショートカットする)
- Worker のポーリングエラー増加(gRPC エラー、認証、名前解決)
- Worker の concurrency 枯渇(処理スロット不足)
- 依存先(DB、外部API)のエラー・タイムアウト増加
これは「信号機が赤のまま」「線路が詰まってる」系。原因に近いので復旧が早くなります。
4. Namespace 設計と管理:運用の“防火区画”を作る
Namespace は「同じ建物の中の別フロア」みたいなもので、運用上の境界線になります。
Temporal ではこの境界の引き方が、監視・権限・変更の安全性に直結します。
4.1 どう分けるか(現場でよく効く切り方)
- 環境で分ける:
dev/stg/prod
→ 監視とアラートを綺麗に分離できます - 組織/ドメインで分ける:
payments/fulfillment
→ 権限と責任範囲が揃います - 大規模なら「環境×ドメイン」:
prod-paymentsなど
→ “同じ本番”でも爆発範囲を絞れます
Task Queue 名で頑張るより、Namespace で防火区画を作る方が、運用の事故が減りやすいです。
4.2 ライフサイクル管理のコツ
- **保持期間(retention)**を用途に合わせる
監査・調査が必要なドメインは長め、そうでないなら短め - Namespace 単位で アラートの閾値を変える
“バッチ系”と“同期系”を同じ遅延SLOで縛ると誤検知が増えがちです
5. よくあるトラブルと対処法(手順つき)
ここが一番欲しいところですよね。
Temporal のトラブル対応は「ログを眺める」より、状態を特定してから手を打つのが早いです。
5.1 スタックした Workflow(進まない / 終わらない)
「止まってる」にはだいたい3種類あります。
(1) Activity待ち (2) Timer待ち (3) Signal待ち
まず見る(Web UI)
- Workflow の Event History で最後のイベントを確認
ActivityTaskScheduledの後にActivityTaskStarted/Completedがあるか- Timer/Signal の待ち状態になっていないか
よくある原因と打ち手
- Activity が開始されない
- Task Queue 名違い、Worker がそのキューを poll してない
- Worker が落ちてる / 過負荷で concurrency 枯渇
→ Worker 数を増やす、concurrency 設定を見直す、Task Queue を分割
- Activity がリトライ地獄
- 依存先が落ちている・レート制限
→ Retry Policy をドメインに合わせる(最大試行回数・バックオフ・non-retryable を整理)
- 依存先が落ちている・レート制限
- Signal 待ちが永遠
- 呼び出し側が signal を送ってない / 送る相手を間違えてる
→ 送信側の監視(Signal 成功率)を作る、Correlation ID を徹底
- 呼び出し側が signal を送ってない / 送る相手を間違えてる
たとえると「料理が出ない」問題で、(1)厨房が止まってる、(2)オーブンのタイマー待ち、(3)注文が通ってない、のどれかをまず切り分ける感じです。
5.2 Non-Determinism エラー(再現しにくい、でも致命傷)
Non-Determinism は「過去のイベント履歴を再生したら、別の分岐に行ってしまった」状態です。
Temporal はワークフローを イベントソーシング的にリプレイするので、ここがズレると破綻します。
典型原因
- Workflow 内で
time.Now()や乱数、外部API呼び出しなど、非決定的な値を直接使う - コード変更で
if/elseの条件やSelectorの分岐が変わった - ループの回数が履歴と一致しなくなった
まずやること
- Web UI で該当 Workflow の failure を開いて、どのイベントで崩れたかを確認
- 直近のデプロイ差分を見て、Workflow 定義の変更点を洗い出す
対処の基本方針
- Workflow のバージョニング(
GetVersionなど)で履歴互換を維持する - 非決定的なものは Activity 側に逃がす(Workflow は“台本”、Activity は“現場”と割り切る)
5.3 リトライ嵐で負荷が雪だるま式に増える
Temporal は賢くリトライしてくれる反面、設定次第で「全員が一斉にリトライ」という事故が起きます。
例えるなら、出口が詰まってるのに「全員いったん外に出て並び直してね」を繰り返す感じです。
見るべき観点:
- 失敗の種類が 永続的エラー(バリデーション、権限)なのか 一時的エラー(タイムアウト)なのか
- backoff が短すぎないか(スパイクを作っていないか)
打ち手:
- Non-retryable を明確化(エラー型、原因コード)
- backoff と最大試行回数をドメイン要件に合わせる
- 外部APIに対してはレート制限/サーキットブレーカも併用(Temporal だけで守り切らない)
6. Temporal Web UI / tctl の活用:現場で役立つ“3点セット”
Web UI は「今なにが起きてるか」を見るのに強く、tctl は「一括・自動化」に強いです。
両方使えると運用の解像度が上がります。
6.1 Web UI で見るべきポイント
- Workflow の Status / History / Pending Activities
- Retry の回数、最後の failure、次の retry 時刻
- Task Queue の滞留状況(環境によって見え方は変わります)
「まず Web UI で個体を診察 → まとめて対処は tctl」みたいに使うと迷いが減ります。
6.2 tctl でよく使う操作(例)
コマンドはバージョンで差があるので、手元の
tctl --helpを正にしましょう。ここでは“運用での使い所”を優先して例を載せます。
- Workflow の一覧・詳細確認
- 特定条件での検索(Workflow ID / Run ID / 時刻 / ステータス)
- 問題のある Workflow の terminate/cancel(影響を理解した上で慎重に)
運用で効くのは「検索条件をチームでテンプレ化」することです。
「障害時に誰が打っても同じ情報に辿り着ける」状態が作れます。
7. 仕上げ:本番運用チェックリスト(最小)
最後に、運用の“型”として持っておくと効くものを置いておきます。
- SDK メトリクスを Prometheus に出している(Worker 側)
- 症状アラート(遅延/滞留/失敗)と原因アラート(poll/依存先/枯渇)を分離
- Namespace を環境・責務境界として設計し、retention と権限を揃えている
- スタック時の一次切り分け手順(Activity/Timer/Signal)をチームで共有
- Non-Determinism の回避策(決定性・バージョニング)を開発ルール化
- Web UI と tctl の「よく使う操作」をテンプレ化(オンコールの迷子防止)
おわりに
Temporal は「正しく作れば壊れにくい」一方で、「観測できないと直しにくい」タイプの道具なんですよね。
なので本番では、ワークフローの設計と同じ熱量で メトリクス・アラート・調査導線を設計しておくのが効きます。
シリーズはここで一区切りです。次にやるなら、あなたのサービスの1つのドメインを選んで「SLO → 監視 → 失敗の分類 → リトライ設計」を1周回してみるのが一番伸びます。技術というより“運用の筋トレ”ですね。