---
title: "Temporal Workflowとは？仕組み・Pythonの実測結果・Cloud料金を解説"
url: "https://www.issoh.co.jp/tech/details/9090/"
published: 2025-10-01
updated: 2026-10-14
categories: ["アーキテクチャ"]
publisher: "株式会社一創"
---

# Temporal Workflowとは？仕組み・Pythonの実測結果・Cloud料金を解説

Temporal（temporal.io）は、ワークフローをYAMLやDSLではなくGo・Java・Python・TypeScriptなどの普通のコードで書き、プロセスが落ちても途中から処理を再開させる実行基盤です。各ステップの結果をイベント履歴としてサーバーに永続化し、ワーカーが再起動すると履歴を再生して状態を戻します。Temporalはこの仕組みをDurable Execution（耐久実行）と呼んでいます。

この記事では、CLI v1.9.1に同梱されたTemporal Server v1.32.0とPython SDK 1.34.0でPythonのワークフローを実際に動かし、ワーカーを強制終了しても処理が続くか、稼働中にコードを変えると何が起きるかを確かめた結果をもとに、仕組み・書き方・OSSとTemporal Cloudの違い・Airflowとの使い分けを整理します。

## まとめ：Temporalの要点

- Temporalは「コードで書いたワークフローを、障害後も途中から再開させる」基盤。外部APIやDBを触る処理はActivity、順番や分岐を決める処理はWorkflowに分けて書く。
- ワーカーを`kill -9`で止めて25秒後に再起動したところ、完了済みのActivityは再実行されず、残りの1ステップだけが実行されて完了した（本記事の実測）。
- Workflowのコードは決定的でなければならない。稼働中にActivityの呼び出し順を変えると`[TMPRL1100] Nondeterminism error`で止まり、`workflow.patched()`で新旧を分岐させると復旧した（実測）。
- OSS版のサーバーはMITライセンスで無料。マネージドのTemporal Cloudは基本月額なしの従量課金で、100万Actionあたり50ドルから。AWS東京リージョン（ap-northeast-1）で使える。
- 定期バッチやETLのDAGならAirflowやDagster、AWSの中だけで完結するならStep Functionsのほうが軽い。Temporalが効くのは、決済・注文・承認待ちのように状態を持って長く動く業務処理。

以下、仕組み、Pythonでの動かし方、実測、設計、提供形態、比較の順に説明します。

## Temporalとは：Durable Executionのワークフローエンジン

Temporalで書くワークフローは、普通の関数とほぼ同じ見た目をしています。違うのは、関数の途中で呼んだActivityの結果やタイマーの開始が1件ずつサーバーの履歴に記録される点です。ワーカーのプロセスが落ちても、別のワーカーが関数を先頭から実行し直し、記録済みの結果を履歴から読み戻すことで、落ちる直前の状態に追いつきます（[公式ドキュメントのWorkflowの説明](https://docs.temporal.io/workflows)では、これをreplayと呼んでいます）。

この仕組みのおかげで、リトライ、タイムアウト、数日から数か月に及ぶ待機、途中失敗時の巻き戻しを、アプリ側でキューや状態テーブルを作らずに書けます。逆に言えば、再実行しても同じ結果になるようにWorkflowのコードを書く制約を受け入れる必要があります。この制約は後半の実測で具体的に扱います。

### JavaScriptのTemporal APIとの違い

「Temporal」で検索すると、JavaScriptの新しい日付・時刻API（`Temporal.PlainDate`などを持つ組み込みオブジェクト）の解説も多く出てきます。こちらはTC39で標準化が進んだ言語仕様で、本記事のワークフローエンジンとは名前が同じだけの無関係な技術です。JavaScriptの日付APIを調べている場合は、[ECMAScriptの版と新機能をまとめた記事](/column/details/7574/)のほうが目的に合います。

### 開発元とCadenceからのフォーク

Temporalは、UberでワークフローエンジンCadenceを作ったMaxim FateevとSamar Abbasが2019年にUberを離れて始めたプロジェクトで、[サーバーのリポジトリ](https://github.com/temporalio/temporal)にもCadenceのフォークとして生まれたことが書かれています。ライセンスはMITです。開発元のTemporal Technologiesは、[2026年9月14日の発表](https://temporal.io/blog/temporal-raises-usd550m-series-e-at-usd12-55b-valuation-ai)で評価額125億5,000万ドル、5億5,000万ドルのシリーズEの調達を公表しました。同じ発表では、有償顧客が4,300社を超え、OSS版のインストールが4,300万件を超えたとしています。

## アーキテクチャ：Temporal Service・ワーカー・Task Queue

Temporalは、ワークフローの状態を持つ「Temporal Service（サーバー）」と、利用者が書いたコードを実行する「ワーカー」に分かれます。サーバーは利用者のコードを一切実行しません。ワーカーはアプリ側のプロセスとして自分で動かし、サーバーにTask Queueをポーリングして仕事を取りに行きます。

### サーバーの4サービスと永続化DB

サーバーは、API受付のFrontend、履歴とタイマーを持つHistory、Task Queueを管理するMatching、内部用のシステムワークフローを動かすWorkerの4サービスで構成され、それぞれ独立に台数を増やせます。状態はすべてDBに書かれるため、サーバーを再起動してもワークフローは失われません。

[公式の永続化ドキュメント](https://github.com/temporalio/documentation/blob/main/docs/encyclopedia/temporal-service/persistence.mdx)が積極的にテストしているとする版は、Cassandra 3.11・4.0・5.0.4以降、PostgreSQL 13.18・14.15・15.10・16.6、MySQL 5.7と8.0.19以降です。SQLite 3.xにも対応しますが、開発とテスト用で本番用途ではないと明記されています。属性検索を担うVisibilityの保存先にはSQLデータベースも使えます。公式資料は本番環境でElasticsearchを推奨しており、検索量や運用条件に応じて選定します。

### Task Queueとワーカーの関係

ワークフローを開始すると、サーバーは「次にやること」をTask Queueに積みます。ワーカーは空いているときだけ取りに来るので、処理が詰まってもワーカーに仕事が押し付けられることはありません。タスクはサーバー側に残るため、ワーカーが全台落ちていても、復帰した時点で続きから処理されます。処理量を増やしたいときは、同じTask Queueをポーリングするワーカーを足すだけです。

## WorkflowとActivityの役割分担

Temporalのコードを書くときに最初に決めるのは、どの処理をWorkflowに置き、どの処理をActivityに出すかです。判断基準は単純で、外の世界に触る処理（API呼び出し、DB更新、ファイルやLLMの呼び出し）はすべてActivityに出します。

### Workflow：履歴の再生で状態を戻すオーケストレーション

Workflowは、Activityを呼ぶ順番、分岐、ループ、タイマー、外部からのシグナル待ちを書く場所です。replayのたびに先頭から実行し直されるため、同じ履歴に対して毎回同じ命令を出す必要があります。現在時刻の取得、乱数、ネットワーク呼び出しをWorkflowに直接書いてはいけないのはこのためで、SDKは`workflow.now()`や`workflow.random()`のような再生しても値が変わらないAPIを用意しています。

### Activityの副作用と既定のリトライ設定

Activityは、失敗するかもしれない実処理を書く普通の関数です。[リトライポリシーの公式説明](https://docs.temporal.io/encyclopedia/retry-policies)によると、Activityには既定で初回間隔1秒、係数2.0、最大間隔は初回の100倍、最大試行回数0（無制限）のリトライが付きます。一方、Workflow自体には既定のリトライポリシーが無く、失敗したWorkflowは再試行されません。

Activityは再実行される可能性があるため、同じ注文への課金要求が複数回送られても二重課金にならないように、外部APIには冪等キーを渡す設計にしておきます。指数バックオフやリトライ回数の考え方は[リトライパターンの実装判断をまとめた記事](/tech/details/16185/)も参考になります。

## PythonでTemporalを動かす手順（CLI 1.9.1・temporalio 1.34.0）

ローカルで試す場合は、Temporal CLIに同梱された開発サーバーを1つのコマンドで起動できます。以下は2026年10月14日にIntel MacのPython 3.12で実行した手順です。

### 開発サーバーとPython SDKの導入手順

CLIはmacOSなら`brew install temporal`で入ります（Homebrewの安定版も1.9.1）。CLIには開発用サーバーが同梱されていて、今回のバイナリは`temporal version 1.9.1 (Server 1.32.0, UI 2.54.1)`と表示されました。

```
brew install temporal
temporal server start-dev --db-filename ./dev.db

# 別ターミナルで Python SDK を入れる（Python 3.10以上が必要）
python3 -m venv .venv
./.venv/bin/pip install temporalio==1.34.0
source .venv/bin/activate
```

[CLIのリファレンス](https://docs.temporal.io/cli/command-reference/server)どおり、gRPCは7233番、Web UIは8233番（gRPCのポート＋1000）で待ち受けます。`--db-filename`を付けないと状態はメモリにしか無く、サーバーを止めた時点でワークフローが消えます。手元で再起動を伴う検証をするなら付けておくのが無難です。

2026年10月時点の各SDKの最新版は次のとおりです（GitHub ReleasesとPyPIで確認）。Rust SDKは[2026年9月にGA](https://temporal.io/blog/build-durable-applications-rust-temporal-rust-sdk-now-generally-available)となった最も新しい公式SDKです。

| SDK                | 最新版     | 公開日        |
| ------------------ | ------- | ---------- |
| Go                 | v1.49.0 | 2026-09-14 |
| Java               | v1.40.0 | 2026-09-29 |
| TypeScript         | v1.24.0 | 2026-09-15 |
| Python（temporalio） | 1.34.0  | 2026-09-30 |
| .NET（C#）           | 1.20.0  | 2026-09-28 |
| Ruby               | v1.9.0  | 2026-09-14 |
| PHP                | v2.19   | 2026-09-16 |
| Rust               | v1.1.0  | 2026-10-06 |

サーバー本体の最新は[v1.32.1（2026年10月8日）](https://github.com/temporalio/temporal/releases)です。SDKはどれも同じサーバーにつながるので、ワーカーはPython、開始側はGoのように言語を混ぜても動きます。

### Workflow・Activityの実装とワーカーの起動

在庫引当→決済→20秒待機→出荷の順に進む注文処理です。決済のActivityは2回目まで失敗するようにしてあり、リトライの動きも同時に見られます。

```
import asyncio, sys
from datetime import timedelta
from temporalio import activity, workflow
from temporalio.client import Client
from temporalio.common import RetryPolicy
from temporalio.worker import Worker


@activity.defn
async def reserve_stock(order_id: str) -> str:
    return "reserved"


@activity.defn
async def charge(order_id: str) -> str:
    if activity.info().attempt <= 2:   # 2回目までは失敗させる
        raise RuntimeError("payment API timeout")
    return "charged"


@activity.defn
async def ship(order_id: str) -> str:
    return "shipped"


@workflow.defn
class OrderWorkflow:
    @workflow.run
    async def run(self, order_id: str) -> str:
        opts = dict(
            start_to_close_timeout=timedelta(seconds=10),
            retry_policy=RetryPolicy(initial_interval=timedelta(seconds=1)),
        )
        await workflow.execute_activity(reserve_stock, order_id, **opts)
        await workflow.execute_activity(charge, order_id, **opts)
        await workflow.sleep(timedelta(seconds=20))   # 耐久タイマー
        return await workflow.execute_activity(ship, order_id, **opts)


async def main():
    client = await Client.connect("localhost:7233")
    if sys.argv[1] == "worker":
        await Worker(client, task_queue="orders", workflows=[OrderWorkflow],
                     activities=[reserve_stock, charge, ship]).run()
    else:
        await client.start_workflow(OrderWorkflow.run, sys.argv[2],
                                    id=sys.argv[2], task_queue="orders")


if __name__ == "__main__":
    asyncio.run(main())
```

`python order.py worker`でワーカーを起動し、別ターミナルで`python order.py start order-3`を実行するとワークフローが始まります。進行状況は`http://localhost:8233`のWeb UIか、`temporal workflow describe -w order-3`で確認できます。

### Python SDKで最初につまずく2つの書き方

Python SDKは、Workflowのコードを決定的に保つためにサンドボックスの中でモジュールを読み込み直します。今回の検証では、この仕様に由来するエラーを2つ踏みました。

- ファイル末尾に`asyncio.run(main())`を裸で書くと、ワーカー起動時のWorkflow検証でモジュールが再読み込みされた際にも実行され、`RuntimeError: asyncio.run() cannot be called from a running event loop`に続いて`Failed validating workflow OrderWorkflow`で落ちる。`if __name__ == "__main__":`で包めば直る。
- Workflowの中で`os.environ.get()`を呼ぶと`RestrictedWorkflowAccessError: Cannot access os.environ.get from inside a workflow.`になる。このときActivityは実行済みのまま、Workflowタスクだけが失敗を繰り返して止まる。環境変数で処理を切り替えたいなら、値をActivityで読むか、Workflowの引数で渡す。

起動時の検証エラーはワーカーのログで確認します。実行中のWorkflowタスクの失敗は、Web UIのイベント履歴に`WorkflowTaskFailed`として残り、そのfailureにエラー文が入ります。ワークフローが「実行中」のまま進まないときは、この2か所を見ます。

## ワーカー強制終了後の再開結果（実測）

上の注文処理を開始し、20秒のタイマーで待っている間にワーカーを`kill -9`で強制終了しました。25秒後（タイマーはワーカー不在のまま満了済み）にワーカーを起動し直しました。各Activityの先頭に、名前と試行回数（`activity.info().attempt`）をファイルへ書き出す1行を足して記録した結果は次のとおりです。

```
reserve_stock order-3 attempt=1
charge order-3 attempt=1
charge order-3 attempt=2
charge order-3 attempt=3
--- ここでワーカーを kill -9、25秒後に再起動 ---
ship order-3 attempt=1
```

再起動後に実行されたのは出荷の1回だけで、在庫引当と決済はやり直されていません。再起動から6秒以内に`Status COMPLETED`、結果は`"shipped"`になりました。決済が3回目で通るまでのリトライ（1秒、2秒の間隔）も、アプリ側にリトライ処理を一行も書かずに起きています。

履歴を`temporal workflow show`で見ると、全30イベントのうち決済のActivityは`ActivityTaskStarted`が1件だけで、その属性が`attempt 3`でした。リトライはサーバー内で処理され、失敗した試行は履歴のイベント数を増やしません。また、タイマー満了時にワーカーがいなかったため`WorkflowTaskTimedOut`が1件記録され、その後に再スケジュールされたタスクを新しいワーカーが拾っていました。

## Nondeterminism errorの原因と直し方（実測）

Temporalで本番障害になりやすいのは、実行中のワークフローがあるのにWorkflowのコードを変えてデプロイするケースです。上の注文処理を開始してタイマー待ちの間にワーカーを止め、決済と待機の間に在庫の再確認（Activity呼び出し）を1行足したコードでワーカーを起動し直しました。

```
WARN temporalio_sdk_core::worker::workflow: Failing workflow task
  failure: "[TMPRL1100] Nondeterminism error: Activity machine does not
  handle this event: HistoryEvent(id: 17, TimerStarted)"
```

新しいコードは決済の後にActivityを出そうとしますが、履歴の17番目には旧コードが出したタイマー開始が記録されているため、食い違いを検出して止まります。このときワークフローは失敗扱いにならず、`Running`のまま`WorkflowTaskFailed`を繰り返します。気づかないと「止まっているのにエラーにならない」状態が続く点に注意が必要です。

直し方は、変更箇所を`workflow.patched()`で囲むことです。

```
await workflow.execute_activity(charge, order_id, **opts)
if workflow.patched("recheck-stock"):
    # 非リプレイ時、または同じパッチ記録を再生するときに通る
    await workflow.execute_activity(reserve_stock, order_id, **opts)
await workflow.sleep(timedelta(seconds=20))
```

このコードでワーカーを起動し直すと、止まっていた旧実行は追加分を飛ばして出荷まで進んで完了し、新しく開始した実行は在庫の再確認を含めて完了しました（履歴は旧実行31イベント、新実行36イベント）。旧コード経路を使う実行が終了し、旧履歴へのQueryやリプレイへの影響も確認できたら、`workflow.deprecate_patch("recheck-stock")`を呼び、新処理を無条件で実行する段階へ移します。

分岐をコードに残したくない場合は、Worker Deploymentsを使うWorker Versioningで、開始時のワーカー版に実行を固定する方法もあります。[2026年3月30日にGA](https://temporal.io/changelog/worker-versioning-continue-as-new-worker-controller)になりました。

非決定性を生む書き方は、ほかにも次のようなものがあります。

| 書き方             | 起きること         | 代わりに使うもの                           |
| --------------- | ------------- | ---------------------------------- |
| 乱数・UUIDの生成      | 再生で値が変わる      | workflow.random()・workflow.uuid4() |
| 現在時刻の取得         | 再生で値が変わる      | workflow.now()                     |
| API・DBの直接呼び出し   | 再生で二重に実行される   | Activityに出す                        |
| time.sleepによる待機 | Workflowで使用不可 | workflow.sleep()・asyncio.sleep()   |
| 稼働中の呼び出し順の変更    | TMPRL1100で停止  | patched()・Worker Versioning        |

Python SDKでは`asyncio.sleep()`もTemporalの耐久タイマーに置き換わるので、Workflow内で使って構いません。サンドボックスは非決定的な処理の一部を事前に止めてくれますが、呼び出し順の変更のようにコードとしては正しいものは検出できません。デプロイ前に、本番の履歴をダウンロードしてリプレイテストにかけるのが確実です。

## リトライ・補償・長時間実行の設計

### リトライを止めるときのmaximum\_attempts

Activityの`maximum_attempts`は、0が無制限（既定）、1が「1回だけ実行してリトライしない」です。0を「リトライしない」と誤解して1を指定し忘れると、恒久的なエラーでもタイムアウトまで再試行が続きます。再試行しても直らないエラー（存在しない口座など）は、Activity側で`ApplicationError(..., non_retryable=True)`を投げるか、リトライポリシーの`non_retryable_error_types`に型名を入れて、回数に関係なく即座に失敗させます。

### Sagaパターンによる補償アクションの実装

複数のサービスにまたがる処理を途中で失敗させたときは、完了済みのステップを打ち消す処理（補償）をWorkflowに書きます。次のコードは、入金先が存在しないエラーで入金に失敗したら、引き落とした分を返金します。importとWorkerの起動は前の注文処理と同じで、Workerの`workflows`と`activities`にこの3つのActivityとTransferWorkflowを登録して動かしました。

```
from temporalio.exceptions import ActivityError, ApplicationError

@activity.defn
async def withdraw(amount: int) -> str:
    return f"withdrew {amount}"

@activity.defn
async def deposit(amount: int) -> str:
    raise ApplicationError("invalid account", non_retryable=True)

@activity.defn
async def refund(amount: int) -> str:
    return f"refunded {amount}"

@workflow.defn
class TransferWorkflow:
    @workflow.run
    async def run(self, amount: int) -> str:
        opts = dict(start_to_close_timeout=timedelta(seconds=30),
                    retry_policy=RetryPolicy(maximum_attempts=5))
        await workflow.execute_activity(withdraw, amount, **opts)
        try:
            await workflow.execute_activity(deposit, amount, **opts)
        except ActivityError:
            await workflow.execute_activity(refund, amount, **opts)  # 補償
            return "compensated"
        return "done"
```

実行結果は`"compensated"`で、`non_retryable=True`のため入金は5回まで再試行されず1回で補償に移りました。返金の途中でワーカーが落ちた場合も再試行の対象になりますが、この例では最大5回の試行を使い切ると失敗します。返金の冪等性と、補償失敗を検知して復旧する手順も必要です。補償の設計そのもの（オーケストレーション型とコレオグラフィ型の違い、補償できない操作の扱い）は[Sagaパターンの補償トランザクション設計の記事](/tech/details/16175/)で詳しく扱っています。

### 履歴の上限とContinue-As-New

[公式の制限値](https://docs.temporal.io/workflow-execution/limits)では、1回のWorkflow実行の履歴は51,200イベントまたは50MBが上限で、10,240イベントまたは10MBで警告が出ます。上の実測ではActivity1回あたり履歴が6イベント（Activity系3件とWorkflowタスク系3件）増えたので、ループで同じ実行の中からActivityを呼び続ける設計なら、単純計算で8,000回台で上限に届きます。

キューを読み続ける、毎日1回の処理を何年も続けるといったワークフローは、一定回数ごとに`workflow.continue_as_new()`で新しい実行（新しいRun ID・空の履歴）に引き継ぎます。また、1つのペイロードはTemporal Cloudで2MBまで（セルフホストも既定は2MB）なので、大きなファイルはオブジェクトストレージに置き、Workflowには参照だけを渡します。

## Signal・Queryによる承認待ちワークフローの実装

人の承認や外部システムの通知を待つ処理は、Signal（外から値を送って状態を変える）とQuery（実行中の状態を読み取る）で書きます。次のコードは、承認のSignalを最大7日待つWorkflow定義の抜粋です。前例のimportに加え、ApprovalWorkflowのWorker登録とクライアント側の開始処理が必要です。

```
@workflow.defn
class ApprovalWorkflow:
    def __init__(self) -> None:
        self.approved = False
        self.status = "pending"

    @workflow.signal
    def approve(self) -> None:
        self.approved = True

    @workflow.query
    def current_status(self) -> str:
        return self.status

    @workflow.run
    async def run(self) -> str:
        try:
            await workflow.wait_condition(lambda: self.approved,
                                          timeout=timedelta(days=7))
            self.status = "approved"
        except asyncio.TimeoutError:
            self.status = "timeout"
        return self.status
```

開始直後にクライアントから`handle.query(ApprovalWorkflow.current_status)`を呼ぶと`pending`、`handle.signal(ApprovalWorkflow.approve)`を送ると結果は`approved`になりました。待機中に処理専用のスレッドを占有し続ける必要はありませんが、Workflowのキャッシュはメモリを使います。必要なワーカー台数は、キャッシュ量や承認通知の集中時の処理負荷を測って判断します。Queryのハンドラは読み取り専用で、中でActivityを呼んだり状態を書き換えたりはできません。

## OSSのセルフホストとTemporal Cloudの違い

サーバーを自分で運用するか、マネージドのTemporal Cloudを使うかで、費用の性質と運用の負担が変わります。ワーカーはどちらの場合も自分のインフラで動かします。

### セルフホスト：MITライセンス・Helm chart・DB運用

OSS版のサーバーは無料で、機能制限もありません。Kubernetesには公式の[Helm chart](https://github.com/temporalio/helm-charts)（最新はtemporal-1.7.0、2026年9月12日）があり、DBの認証情報は本番ではKubernetesのSecretで渡すよう案内されています。ライセンス料はかかりませんが、Cassandra・PostgreSQL・MySQLのいずれかを高可用構成で運用し、サーバーのバージョンアップを自分で追う必要があるため、費用はサーバー・DB・ワーカーのインフラ代に運用保守の人件費を加えて見積もります。AWSで組む場合はEKSとAmazon RDS for PostgreSQLなどのマネージドDBの組み合わせが候補になり、[AWS・Google Cloud・Azureのインフラ構築](/service/system/aws/)と同じ設計判断（冗長化、バックアップ、監視）が要ります。

### Temporal Cloudの料金と東京リージョン

Temporal Cloudは、Workflowの開始やActivityの実行、Signalなど、アプリとTemporal Cloudの間で発生する操作をActionとして数えて課金します。[料金ページ](https://temporal.io/pricing)の2026年10月時点の内容は次のとおりです。

| 項目                        | 料金                                        |
| ------------------------- | ----------------------------------------- |
| 基本月額・最低利用額（Pay-As-You-Go） | なし                                        |
| Developerサポート             | 利用額の10%                                   |
| Action                    | Developer：100万Actionsあたり50ドル／上位プラン：段階割引あり |
| Active Storage            | 0.042ドル/GB時                               |
| Retained Storage          | 0.00105ドル/GB時                             |
| Businessサポート              | 月500ドルか利用額の10%の大きい方                       |

上の注文処理は、Workflow開始1回、Activityの初回実行3回、決済の再試行2回、タイマー開始1回で計7 Actionsです。追加のQueryなどを除くと、1日1万件・30日間で210万Actionsになり、Developerプランなら約105ドルに利用額の10%のサポート料が加わります。[Actionの数え方](https://docs.temporal.io/evaluate/cloud/actions)では、Activityは再試行のたびに1 Actionと数えるので、失敗が多い処理ほど費用が増えます。正確な件数は種類ごとの数え方で変わるため、本番前にTemporal Cloudの使用量画面で1件あたりの実数を測ってから見積もってください。提供リージョンはAWSとGoogle Cloudにあり、[ステータスページ](https://status.temporal.io/)にはAWS東京リージョン（ap-northeast-1）が載っています。[AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-xx2x66m6fp2lo)からも従量課金で契約でき、AWSの請求にまとめられます。AWS自体にTemporalのマネージドサービスはありません。

## Airflow・Step Functionsなど他のワークフローエンジンとの比較

「ワークフローエンジン」と呼ばれる製品は、得意な処理の形がかなり違います。Temporalにはデータパイプラインを表すDAGの概念が無く、依存関係はコードの実行順そのものです。

| 観点    | Temporal             | Airflow      | Argo Workflows | Step Functions                     |
| ----- | -------------------- | ------------ | -------------- | ---------------------------------- |
| 定義    | コード（8言語SDK）          | PythonのDAG   | YAML（CRD）      | JSON（ASL）                          |
| 得意な処理 | 状態を持つ長時間の業務処理        | 定期バッチ・ETL    | K8s上のコンテナ並列    | AWSサービスの連結                         |
| 途中再開  | 履歴の再生で自動             | タスク単位の再実行    | ステップ単位の再実行     | Standard：状態を永続化／失敗対応はRetry等を設定     |
| 実行基盤  | セルフホストかCloud         | セルフホストかマネージド | Kubernetes必須   | AWSのみ                              |
| 課金    | OSS無料・CloudはAction従量 | OSS無料        | OSS無料          | Standard：状態遷移数／Express：実行回数・時間・メモリ |

日次のETLや、スケジュールで動くデータ処理を並べたいなら、[Apache AirflowのDAGと導入手順](/tech/details/1441/)や、データ資産を単位に管理する[Dagsterのアセット指向の仕組み](/tech/details/15948/)のほうが運用が軽く済みます。AWSのLambdaだけで長時間処理をつなぎたいなら[Lambda Durable Functions](/tech/details/10107/)、Next.jsなどのWebアプリからイベント駆動で処理を流すなら[Inngest](/tech/details/2595/)も候補です。

### Temporalを選ばない方がよい場面

処理が「受け取って数秒で終わる非同期ジョブ」だけなら、Temporalは重すぎます。サーバーとDBを運用するか、Action単位の課金を払うことになり、[Celery](/tech/details/17293/)のようなタスクキューで十分です。また、チームの誰もがWorkflowの決定性の制約を理解していない状態で導入すると、上で再現したNondeterminism errorを本番デプロイのたびに踏みます。導入するなら、最初に決済や承認のように「途中で落ちたら困る、数分以上かかる処理」を1本だけ移し、リプレイテストをCIに入れてから対象を広げる順番を勧めます。

## よくある質問

### Temporalは無料で使えますか？

使えます。サーバーはMITライセンスのOSSで、セルフホストなら機能制限なく無料です。ライセンス料はかかりませんが、サーバー・DBのインフラ代と運用の手間は自前で負担します。マネージドのTemporal Cloudは基本月額なしの従量課金で、100万Actionあたり50ドルからです。

### TemporalとAirflowはどう違いますか？

AirflowはPythonのDAGで定期バッチやETLを組むスケジューラで、タスク単位で再実行します。Temporalはコードで書いた業務処理の途中状態を履歴として保持し、障害後に止まった地点から自動で再開します。日次のデータ処理ならAirflow、決済や承認待ちのように状態を持って長く動く処理ならTemporalが向きます。

### C#（.NET）やPythonで書けますか？

書けます。公式SDKはGo・Java・TypeScript・Python・.NET（C#）・Ruby・PHP・Rustの8言語で、2026年10月時点の最新はPythonが1.34.0（Python 3.10以上）、.NETが1.20.0です。言語の違うワーカーとクライアントを同じサーバーにつないで混在させることもできます。

### TemporalはAWSで使えますか？

AWSにTemporalのマネージドサービスはありません。EKSなどにOSS版をセルフホストするか、AWS上で動くTemporal Cloud（東京リージョンあり・AWS Marketplaceで契約可）を使います。

### リトライを止めるにはどう設定しますか？

リトライポリシーの`maximum_attempts`を1にすると、1回だけ実行してリトライしません。0は無制限（既定）の意味です。特定のエラーだけ止めたい場合は、そのエラーを`non_retryable`として投げます。

### ワークフローは無期限に動かせますか？

動かせます。ただし1回の実行の履歴は51,200イベントまたは50MBが上限なので、長く動き続ける処理は`continue_as_new`で新しい実行に引き継ぎます。

## 関連記事

- [Apache Airflowとは？Jenkinsとの違い・DAGの書き方と3.3の導入手順](/tech/details/1441/)
- [Sagaパターンとは？補償トランザクション設計と実装方式の選び方を解説【2026年版】](/tech/details/16175/)
- [Lambda Durable Functionsとは？東京リージョン対応と15分制限を超える長時間ワークフロー実装](/tech/details/10107/)
- [Dagsterとは？アセット指向の仕組み・dg CLIでの実装手順とAirflow移行を解説【2026年9月版】](/tech/details/15948/)
- [Prefectとは？Pythonワークフロー管理の使い方と3系の始め方【2026年版】](/tech/details/10836/)

---

出典: [Temporal Workflowとは？仕組み・Pythonの実測結果・Cloud料金を解説](<https://www.issoh.co.jp/tech/details/9090/>)（株式会社一創）
