Skip to content
Published on

デュラブル実行 & ワークフローエンジン 2026 — Temporal・Inngest・Trigger.dev・Hatchet・Restate・DBOS・Step Functions 徹底比較(長時間実行AIエージェント時代のインフラ)

Authors

プロローグ — 8時間死なない関数

2026年の春、あるAIスタートアップのエンジニアがSlackに投稿する。

「エージェントがPRレビューを50件処理して、14件目でOpenAI 429に当たった。コンテナはOOMで落ちた。再実行すると1番から13番をもう一度やる。我々のLLMコストは二倍になる。」

この一文が2026年のワークフローエンジン・ルネサンスの要約である。AIエージェントは長時間実行だ。 一つのタスクが8時間走り、その途中で外部APIを100回呼び、そのうち一つが失敗する。コンテナはOOM、再デプロイ、preemption — いつでも死ぬ。それでもビジネスは「1番からやり直すな、14番から再開してくれ」と要求する。

この要求に応える技術がデュラブル実行(durable execution) である。関数が死んでも、最後のステップの状態をディスクが覚えていて、再起動時にその地点から決定論的にリプレイする。2016年にUber Cadenceが先駆け、2019年にTemporalがフォークしてオープンソースで爆発させ、2023〜2025年にInngest、Trigger.dev、Hatchet、Restate、DBOSがそれぞれ別の角度からこの市場に参入した。

この記事は2026年現在のワークフローエンジンの地図を描く。デュラブル実行の意味論から、Temporal・Inngest・Trigger.dev・Hatchet・Restate・DBOSのアーキテクチャの差、AWS Step Functions・Airflow・Prefect・Dagsterの位置づけ、サガパターンと冪等性と厳密に一回の意味論、そして韓国・日本の実例まで。


第1章 · デュラブル実行とは何か — 一文の定義

まず一文で。デュラブル実行とは、関数の実行状態が外部の永続ストレージに保存され、プロセスが死んでも最後の進捗地点から決定論的に再開される実行モデルである。

これを開くと三つの柱が見える。

  1. イベントソーシング — 関数が一ステップ実行するたびに、その結果をイベントログに追記する。関数自体のコードではなく、関数の実行の歴史が真実の出所である。
  2. 決定論的リプレイ — 関数が死ぬと、新しいワーカーが同じコードを最初から実行する。ただし外部呼び出しは本当には呼び出さず、イベントログから結果を取得する。外部に副作用なしで状態だけ復元する形だ。
  3. 冪等性 — 同じ作業を二回実行しても結果が同じでなければならない。決済のような外部副作用は冪等キーで保護する。

この三つが揃うと「関数が死んでも死んでいないふりをする」魔法が可能になる。だからデュラブル実行を「仮想マシンのように振る舞う関数」とも呼ぶ。実際のマシンは死ぬが、仮想マシンは永遠に生きる。

ここで決定的な制約が伴う。ワークフロー関数は決定論的でなければならない。 Math.random()Date.now()、環境変数の読み出し、外部APIの直接呼び出しのような非決定的演算は、SDKが提供する決定論的ラッパーを経由しなければならない。さもないとリプレイ時に別の枝へ分岐してしまう。


第2章 · ワークフローエンジンの四大分類 — 2026年の地図

2026年のワークフロー市場は大きく四つの枝に分かれる。

  • デュラブル実行SDK — Temporal、Cadence、Restate、DBOS。ワークフローをコードで書き、決定論的リプレイで永続化する。長時間実行・複雑な分岐に強い。
  • イベント駆動の関数プラットフォーム — Inngest、Trigger.dev。イベントが関数を呼び出し、関数内の step.run がデュラブル境界になる。DXに優しく、AIエージェント・SaaSワークフローに強い。
  • タスクキュー + ワークフロー — Hatchet、Celery+orchestration。Postgres・Redisの上に組んだキューで、単純なキューの進化形。単純さと運用コストが強み。
  • DAGオーケストレーション(バッチETL) — Airflow、Prefect、Dagster、Argo Workflows。データパイプライン中心、スケジュールされたDAG実行。デュラブル実行とは別の位置づけ。

これ以外にBPMNベースのエンジン(Camunda)とマイクロサービス・オーケストレーション(Netflix Conductor)が別の席を占める。AWS Step Functionsはステートマシン DSLベースで、「デュラブルだがコードではなくJSON」という独特の位置だ。

分類代表エンジンワークフロー定義永続化強み
デュラブル実行SDKTemporal・Cadence・Restate・DBOSコード(TS・Go・Java・Python)Cassandra・Postgres・MySQL長時間実行・複雑分岐
イベント関数Inngest・Trigger.devコード(TSメイン)マネージドDX・AIエージェント
PostgresキューHatchetコード(TS・Python・Go)Postgres単純・低運用
DAGオーケストレーションAirflow・Prefect・Dagsterコード(Python)Postgres・SQLiteバッチETL・データ

第3章 · Temporalアーキテクチャ — Uber Cadenceの進化

Temporalは2019年にUber Cadenceチーム(Maxim Fateev、Samar Abbas)がフォークして作った会社かつオープンソースエンジンだ。2026年現在、デュラブル実行分野の事実上の標準となった。

Temporalサーバーは四つのコアサービスから構成される。

  • Frontend — すべてのクライアント・ワーカーRPCの入口。gRPCゲートウェイ。
  • History — ワークフロー実行イベントログの保存と更新。最も重いコンポーネント。
  • Matching — タスクキューのマッチング(どのワーカーがどのタスクを受けるか)。
  • Worker — システムワークフロー(スケジューリング・アーカイブ)を回す内部ワーカー。

これら四サービスがCassandra・MySQL・Postgresのような永続ストアを共有する。ワークフロー実行イベント(WorkflowExecutionStartedActivityTaskCompleted 等)がHistoryに入り、ワーカーはMatchingからタスクをポーリングする。

// Temporal TypeScript SDK — 決済サガワークフロー
import { proxyActivities, sleep, defineSignal, setHandler } from '@temporalio/workflow'
import type * as activities from './activities'

const { chargeCard, reserveInventory, shipOrder, refundCard, releaseInventory } =
  proxyActivities<typeof activities>({
    startToCloseTimeout: '1 minute',
    retry: { maximumAttempts: 3, initialInterval: '1s' },
  })

export const cancelOrderSignal = defineSignal('cancelOrder')

export async function processOrder(orderId: string, amount: number): Promise<string> {
  let cancelled = false
  setHandler(cancelOrderSignal, () => { cancelled = true })

  const chargeId = await chargeCard(orderId, amount)
  try {
    const reservationId = await reserveInventory(orderId)
    if (cancelled) throw new Error('cancelled-after-reserve')
    await sleep('5 seconds') // human-in-the-loop confirmation window
    const trackingId = await shipOrder(orderId, reservationId)
    return trackingId
  } catch (err) {
    // compensating actions — saga pattern
    await releaseInventory(orderId).catch(() => {})
    await refundCard(chargeId, amount)
    throw err
  }
}

このコードの魔法は、sleep('5 seconds') が実際に5秒間ワーカーを占有しないことだ。ワークフロー状態がHistoryに永続化され、5秒後にタイマーが発火すると新しいワーカーがその地点から再開する。決済ワークフローが何日待ってもワーカーのメモリを食わない。


第4章 · 決定論的リプレイ — 関数はどうやって死んでいないふりをするか

Temporal・Cadence・Restateの核となる魔法は決定論的リプレイだ。動作を一段階ずつ見てみよう。

  1. ワークフローが最初に開始する。ワーカーがコードを実行する。
  2. chargeCard(orderId, amount) の呼び出しが発生する。SDKがそれを横取りしてHistoryに ActivityTaskScheduled イベントを記録する。
  3. Activityが完了すると、ActivityTaskCompleted + 結果がHistoryに記録される。
  4. SDKは結果をワークフローコードに返す。
  5. 次のステップへ。reserveInventory(...) も同じ仕組み。
  6. ワーカーが死ぬ。 OOM、再デプロイ、preemption。何でも。
  7. 新しいワーカーが同じワークフローIDを掴む。コードを最初から実行する。
  8. chargeCard(...) の呼び出しが発生するが、SDKはHistoryに既に結果があるのを見て、その結果を即座に返す。本当にはカードを切らない。
  9. reserveInventory(...) も同じくリプレイ。
  10. 最後の未完了ステップに到達したら、そこから本当の実行を再開する。

このシナリオが成り立つには、ワークフローコード自体が決定論的でなければならない。同じ入力 → 同じ分岐 → 同じ呼び出し順。Math.random() を直接使うとリプレイ時に別の値が出て分岐が変わる。だからTemporal SDKは workflow.uuid4()workflow.now() といった決定論的関数を提供する。

この制約こそデュラブル実行の最大の学習曲線だ。「普通にコードを書いたらダメなのか」と思って一度分岐の非決定性バグでやられたあと、なぜSDKがここまで厳しいのか理解する。


第5章 · Inngest — 「関数 is イベントハンドラ」モデル

Inngest(2021年創業、Tony Holdstock-Brown・Dan Farrelly)は別の角度からデュラブル実行を攻める。「ワークフロー」という概念を消す。 代わりに関数が一級市民で、関数内の step.runstep.sleepstep.waitForEvent がデュラブル境界となる。

// Inngest — Stripe決済後の後続処理
import { Inngest } from 'inngest'
const inngest = new Inngest({ id: 'commerce' })

export const onPaymentSucceeded = inngest.createFunction(
  { id: 'on-payment-succeeded', retries: 3 },
  { event: 'stripe/payment.succeeded' },
  async ({ event, step }) => {
    const order = await step.run('load-order', async () => {
      return await db.orders.findById(event.data.orderId)
    })

    await step.run('send-receipt', async () => {
      await emails.sendReceipt(order)
    })

    // 24時間後にレビュー依頼メール — Inngestが24時間関数を永続化
    await step.sleep('wait-1-day', '1 day')

    await step.run('send-review-request', async () => {
      await emails.sendReviewRequest(order)
    })

    // ユーザーが7日以内にレビューを残せばボーナスポイント
    const review = await step.waitForEvent('wait-for-review', {
      event: 'review/submitted',
      match: 'data.orderId',
      timeout: '7 days',
    })

    if (review) {
      await step.run('grant-bonus-points', async () => {
        await loyalty.grantPoints(order.userId, 100)
      })
    }
  }
)

Inngestの美学は二つだ。第一に、イベントが入口であること — ワークフローを明示的に開始する代わりに、イベントが自動的に関数を起こす。第二に、DXが優しいこと — step.run は普通のasync関数のように見え、決定論的制約をSDKがほぼ隠す。

代わりにトレードオフがある。Inngestはマネージド SaaSがデフォルトで(2024年にセルフホスト・オプションが追加されたが)、Temporalほど複雑な分岐・シグナル・子ワークフローは自然ではない。SaaS・AIエージェント・イベント中心のシステムに強く、巨大な決済システムのような場所にはTemporalの方が合う。


第6章 · Trigger.dev v3 — Vercelに優しいデュラブル実行

Trigger.devは2022年に始まり、2024年のv3で大幅な再設計を経た。v2までは「Vercel Cronの代替」のような位置だったが、v3で真のデュラブル実行エンジンに進化した。核はタスク(task)モデルだ — Inngestの関数に似ているが、SDKがより明示的。

// Trigger.dev v3 — AIエージェントタスク
import { task, logger, wait } from '@trigger.dev/sdk/v3'
import { Anthropic } from '@anthropic-ai/sdk'

const anthropic = new Anthropic()

export const reviewPullRequestTask = task({
  id: 'review-pull-request',
  maxDuration: 1800, // 30 minutes
  retry: { maxAttempts: 3 },
  run: async (payload: { repo: string; prNumber: number }, { ctx }) => {
    logger.info('Reviewing PR', payload)

    const diff = await fetchPRDiff(payload.repo, payload.prNumber)

    const review = await anthropic.messages.create({
      model: 'claude-opus-4-7',
      max_tokens: 4096,
      messages: [
        { role: 'user', content: `Review this diff:\n${diff}` },
      ],
    })

    await wait.for({ seconds: 10 }) // backoff before posting
    await postReviewComment(payload.repo, payload.prNumber, review.content)

    return { tokensUsed: review.usage.input_tokens + review.usage.output_tokens }
  },
})

Trigger.dev v3の強みはインフラが一緒についてくること — タスクをDockerコンテナ内で隔離実行し、長時間走るAIエージェントもメモリ・CPU制限が明示的だ。Vercelと相性が良く、Next.js + Trigger.devの組み合わせが2026年のインディSaaSでよく見られる。

弱みはTemporalほど分散システムの抽象化が深くないこと、セルフホストは可能だが複雑さがあること。AIエージェント・メディア処理のような「一つの作業が長いが分岐は単純」なワークロードに最も合う。


第7章 · Hatchet — Postgresネイティブキューの台頭

Hatchet(2024年創業)は2026年市場で最も興味深い新参の一つだ。メッセージはシンプル: 「Postgres一台で十分。」 Kafka・Cassandra・Redisのような別のインフラなしに、Postgresだけでデュラブルキューとワークフローを実装する。

# Hatchet Python SDK — 非同期タスクパイプライン
from hatchet_sdk import Hatchet, Context
from pydantic import BaseModel

hatchet = Hatchet()

class ProcessVideoInput(BaseModel):
    video_url: str
    user_id: str

@hatchet.workflow(on_events=["video:uploaded"], input_validator=ProcessVideoInput)
class ProcessVideo:
    @hatchet.step(timeout="5m")
    def download(self, ctx: Context) -> dict:
        input = ProcessVideoInput.model_validate(ctx.workflow_input())
        path = download_to_s3(input.video_url)
        return {"path": path}

    @hatchet.step(parents=["download"], timeout="30m")
    def transcribe(self, ctx: Context) -> dict:
        path = ctx.step_output("download")["path"]
        transcript = whisper_transcribe(path)
        return {"transcript": transcript}

    @hatchet.step(parents=["transcribe"], timeout="10m")
    def summarize(self, ctx: Context) -> dict:
        transcript = ctx.step_output("transcribe")["transcript"]
        summary = claude_summarize(transcript)
        return {"summary": summary}

Hatchetの魅力は運用の単純さだ — Postgres一つにワーカーコンテナ数個。バックアップ・複製・HAがPostgresエコシステムをそのまま活用する。2024年以降「Postgresがすべてのキューの未来」という潮流の旗手であり、同時期に登場したGraphileWorker・pg-boss・Riverqueueと同じ呼吸だ。

弱みは本当に巨大なスケール(秒間数万件)ではPostgresがボトルネックになり得ること、Temporalほど複雑なワークフローパターン(子・シグナル・クエリ)がまだ全部揃っていないこと。


第8章 · Restate — ステートフル(stateful)呼び出し

Restate(2023年創業、Stephan Ewen・Igal Shilman らApache Flink出身)はデュラブル実行にもう一つの角度を加える。ステートフル呼び出し(stateful invocation) — 関数呼び出しが自身で状態を持つオブジェクトのように振る舞う。

// Restate — 仮想オブジェクトとしての決済処理
import * as restate from '@restatedev/restate-sdk'

const paymentService = restate.object({
  name: 'payment',
  handlers: {
    process: async (ctx: restate.ObjectContext, amount: number) => {
      // オブジェクト状態 — このpaymentIdに永続化
      const status = (await ctx.get<string>('status')) ?? 'pending'
      if (status === 'completed') return { idempotent: true }

      ctx.set('status', 'processing')
      const chargeId = await ctx.run('charge', () => stripe.charge(amount))
      ctx.set('chargeId', chargeId)
      ctx.set('status', 'completed')
      return { chargeId }
    },
    refund: async (ctx: restate.ObjectContext) => {
      const chargeId = await ctx.get<string>('chargeId')
      if (!chargeId) throw new restate.TerminalError('no-charge-to-refund')
      await ctx.run('refund', () => stripe.refund(chargeId))
      ctx.set('status', 'refunded')
    },
  },
})

restate.endpoint().bind(paymentService).listen()

Restateの差別化点はオブジェクト単位の状態だ — 同じpaymentIdに対する呼び出しは自動的に直列化され、永続状態を共有する。Temporalで書くとワークフロー + シグナルの組み合わせで解くパターンが、Restateでは単なるオブジェクトメソッドだ。

もう一つの強みは運用モデルだ。Restateサーバーは単一バイナリで、自前のRocksDBで状態を永続化する。TemporalのようにCassandra・MySQLを別に立てる必要がない。小さなチームがデュラブル実行を導入する敷居が低い。


第9章 · DBOS — Postgresこそがコンピュート(Mike Stonebraker)

DBOS(2023年創業、データベースの伝説Mike Stonebraker・Matei Zahariaが共同創業)は最も急進的な立場だ。「DBがOSであるべき。」 ワークフローの状態だけでなく、ワークフロー実行自体がPostgres内部のトランザクションとして起こる。

# DBOS Python — トランザクションとワークフローの融合
from dbos import DBOS, WorkflowContext, TransactionContext
import psycopg

@DBOS.workflow()
def process_signup(ctx: WorkflowContext, email: str) -> str:
    user_id = create_user(ctx, email)
    send_welcome_email(ctx, user_id)
    grant_trial_credits(ctx, user_id)
    return user_id

@DBOS.transaction()
def create_user(ctx: TransactionContext, email: str) -> str:
    # この関数はPostgresトランザクション内で実行される
    cur = ctx.sql_session.execute(
        "INSERT INTO users (email) VALUES (%s) RETURNING id",
        (email,),
    )
    return cur.fetchone()[0]

@DBOS.step()
def send_welcome_email(ctx, user_id: str) -> None:
    # 外部呼び出し — 一回のみ実行保証(冪等)
    sendgrid.send_email(user_id, "Welcome!")

@DBOS.transaction()
def grant_trial_credits(ctx: TransactionContext, user_id: str) -> None:
    ctx.sql_session.execute(
        "INSERT INTO credits (user_id, amount) VALUES (%s, %s)",
        (user_id, 100),
    )

DBOSのエレガンスはトランザクションの一貫性とワークフローのデュラビリティが一つのシステムにあることだ。Temporalではactivityとトランザクションが別系統で厳密一回実行が難しいが、DBOSでは一つのトランザクション内に収まる。データベース学界の巨匠が作ったシステムだけあって意味論が綺麗だ。

代わりに新しいパラダイムなのでエコシステムが小さく(Python・TypeScript SDK中心)、すべてのワークロードがトランザクションモデルに合うわけではない。2026年には本格的なproduction利用が増えているが、Temporalほどの成熟にはまだ。


第10章 · AWS Step Functions — JSONステートマシンの席

Step Functions(2016年AWSリリース)はデュラブル実行市場の原祖の一つだが、他のツールとは性格が違う。ワークフローをAmazon States Language(ASL) というJSON DSLで書く。コードではなくステートマシン定義ファイルだ。

# Step Functions — Standardモードのサガ(YAMLで表現)
StartAt: ChargeCard
States:
  ChargeCard:
    Type: Task
    Resource: arn:aws:lambda:us-east-1:123456789012:function:chargeCard
    Retry:
      - ErrorEquals: ["States.TaskFailed"]
        IntervalSeconds: 2
        MaxAttempts: 3
    Catch:
      - ErrorEquals: ["States.ALL"]
        Next: FailOrder
    Next: ReserveInventory
  ReserveInventory:
    Type: Task
    Resource: arn:aws:lambda:us-east-1:123456789012:function:reserveInventory
    Catch:
      - ErrorEquals: ["States.ALL"]
        Next: RefundCard
    Next: ShipOrder
  ShipOrder:
    Type: Task
    Resource: arn:aws:lambda:us-east-1:123456789012:function:shipOrder
    Catch:
      - ErrorEquals: ["States.ALL"]
        Next: RefundAndRelease
    End: true
  RefundAndRelease:
    Type: Parallel
    Branches:
      - StartAt: RefundCard
        States:
          RefundCard:
            Type: Task
            Resource: arn:aws:lambda:us-east-1:123456789012:function:refundCard
            End: true
      - StartAt: ReleaseInventory
        States:
          ReleaseInventory:
            Type: Task
            Resource: arn:aws:lambda:us-east-1:123456789012:function:releaseInventory
            End: true
    Next: FailOrder
  RefundCard:
    Type: Task
    Resource: arn:aws:lambda:us-east-1:123456789012:function:refundCard
    Next: FailOrder
  FailOrder:
    Type: Fail
    Cause: OrderProcessingFailed

Step Functionsの二つのモードを区別すべきだ。

  • Standard — 永続ワークフロー。最大1年実行。状態遷移ごとに課金。決済・承認のような高価で数の少ないワークフロー。
  • Express — 短命ワークフロー。最大5分。実行時間 + メモリで課金。大量の短いイベント処理。

Step Functionsの強みはAWS統合の深さだ — Lambda・DynamoDB・SQS・EventBridgeなど約200のサービスと直接統合。弱みはJSON DSLがコードより表現力が低く、デバッグが難しく、AWSロックイン。

2026年にはAWSオンリーの組織でなければコードベースのエンジン(Temporal・Inngest・Trigger.dev)が選好される傾向だが、既にAWS上に全部あるチームにはStep Functionsが依然強力な選択肢。


第11章 · Apache Airflow vs Prefect vs Dagster — DAGオーケストレーションの席

データパイプライン側には別世界観のエンジン群がある。DAGオーケストレーションだ。

  • Apache Airflow(2014年Airbnb) — 業界標準。PythonでDAGを書く。スケジューラ + ワーカー構成。批判: マクロ環境(Jinja)依存、複雑なbackfill意味論。
  • Prefect(2018年) — Airflow批判から出発。「コードファースト」のDAG、より命令的なモデル、クラウド・オプションが強い。Prefect 2/3でflowとtaskの抽象化を導入。
  • Dagster(2019年Elementl) — データ資産(asset)中心モデル。DAGではなく「どの資産がどの資産に依存するか」を宣言する。dbt・Snowflakeのようなエコシステムと深く統合。
  • Argo Workflows — Kubernetesネイティブ。CRDでDAGを定義し、Pod単位で実行。MLパイプラインでよく使う。
ツール定義モデル主用途強み弱み
AirflowPython DAGバッチETLエコシステム・業界標準重い運用・古い意味論
PrefectPython flowデータ + 汎用ワークフロー命令的API・hybrid実行Airflowより小さなエコシステム
Dagster資産グラフモダンデータスタックdbt統合・資産抽象学習曲線・小さな採用
Argo WorkflowsYAML on K8sML・CI/CDK8sネイティブYAMLの嵩・参入障壁

核心の区別: このカテゴリはデュラブル実行SDKではない。 バッチジョブスケジューラとして出発し、一ジョブが失敗すると通常は最初から再実行する。AIエージェントのような「数時間走る一つの関数」より「毎日03:00にデータを移すジョブ100個」に合う。

2026年には領域が混ざってきている — Prefectは汎用ワークフローに拡張し、Dagsterの資産モデルはMLパイプラインで強力になりつつある。しかし決済サガのようなOLTPワークフローは依然Temporal・Inngestの領域だ。


第12章 · サガパターン — 分散トランザクションの現実的答え

マイクロサービス時代の最大の問題の一つ: 複数サービスにまたがるトランザクションをどう一貫性を保って処理するか。 一つのサービスで決済、別のサービスで在庫、もう一つで配送 — これをACIDトランザクションで括ることはできない。

答えは1987年Hector Garcia-Molinaのサガ(saga)パターンだ。大きなトランザクションを小さなローカルトランザクションのシーケンスに分け、失敗時は補償(compensating)トランザクションを逆順に実行する。

// 決済サガ — Temporalで表現
export async function orderSaga(orderId: string, amount: number) {
  const completed: Array<() => Promise<void>> = []
  try {
    const chargeId = await chargeCard(orderId, amount)
    completed.push(() => refundCard(chargeId, amount))

    const reservationId = await reserveInventory(orderId)
    completed.push(() => releaseInventory(reservationId))

    const trackingId = await shipOrder(orderId, reservationId)
    completed.push(() => cancelShipment(trackingId))

    await sendConfirmation(orderId, trackingId)
    return { chargeId, trackingId }
  } catch (err) {
    // 補償トランザクションを逆順に実行
    for (const compensate of completed.reverse()) {
      await compensate().catch((e) => console.error('compensation failed', e))
    }
    throw err
  }
}

ここで二つのサガの変種を区別すべきだ。

  • オーケストレーション・サガ — 中央コーディネータ(ワークフロー)が各ステップを呼ぶ。分岐が一箇所にあるので追跡が容易。Temporal・Inngestの自然なモデル。
  • コレオグラフィ・サガ — 各サービスがイベントを発行/購読して自律的に進む。中央コーディネータなし。Kafka・EventBridgeのようなイベントバスが必要。結合度が低いが追跡・デバッグが難しい。

2026年の合意: オーケストレーション・サガがデフォルトの選択肢だ。デュラブル実行エンジンがコーディネータを安全に動かしてくれるから。コレオグラフィは真に自律的なマイクロサービス組織でしか価値が出ず、大部分のチームには追跡負担の方が大きい。


第13章 · 冪等性と厳密に一回の意味論

分散システム書で最も危険な約束の一つが「厳密に一回(exactly-once)」だ。厳密な意味で分散システムにおける厳密一回は不可能である — メッセージが届いたかを送信者は永遠に確信できない(Two Generals Problem)。

現実的な答えは実質一回(effectively-once) だ。同じ作業を何度試みてもシステム状態は一回実行したかのように見える。これを保証する二つの道具が冪等性重複排除である。

-- Postgres冪等キーテーブル — 決済重複防止
CREATE TABLE payment_attempts (
  idempotency_key TEXT PRIMARY KEY,
  charge_id TEXT,
  amount BIGINT NOT NULL,
  status TEXT NOT NULL CHECK (status IN ('processing', 'succeeded', 'failed')),
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- 冪等性保証の決済 — 同じキーで二回呼ばれても一回だけ課金
CREATE OR REPLACE FUNCTION charge_with_idempotency(
  p_idempotency_key TEXT,
  p_amount BIGINT
) RETURNS TABLE(charge_id TEXT, was_new BOOLEAN) AS $$
DECLARE
  v_existing payment_attempts%ROWTYPE;
  v_charge_id TEXT;
BEGIN
  -- 既に処理済みか確認
  SELECT * INTO v_existing FROM payment_attempts
   WHERE idempotency_key = p_idempotency_key FOR UPDATE;

  IF FOUND THEN
    RETURN QUERY SELECT v_existing.charge_id, FALSE;
    RETURN;
  END IF;

  -- 初回なら実際の決済後に記録
  INSERT INTO payment_attempts(idempotency_key, amount, status)
       VALUES (p_idempotency_key, p_amount, 'processing');

  v_charge_id := stripe_charge(p_amount);  -- 外部API呼び出し

  UPDATE payment_attempts
     SET charge_id = v_charge_id, status = 'succeeded', updated_at = NOW()
   WHERE idempotency_key = p_idempotency_key;

  RETURN QUERY SELECT v_charge_id, TRUE;
END;
$$ LANGUAGE plpgsql;

デュラブル実行エンジンはこの冪等性を自動で扱ってくれる。Temporalのactivityは ActivityId で識別され、Historyに結果があればリプレイ時に再実行しない。外部呼び出し(Stripe・SendGridなど)の冪等性はユーザーの責任だ — Stripe APIの Idempotency-Key ヘッダ、SendGridの X-Idempotency-Key。デュラブル実行エンジンが外部システムの保証まで代行するわけではない。

核となる原則: 外部副作用のあるすべての呼び出しに冪等キーを付けろ。 デュラブル実行 + 冪等キー = 実質一回。どちらか一方だけでは安全でない。


第14章 · 長時間実行AIエージェント — デュラブル実行の新たなキラーアプリ

2026年のデュラブル実行ルネサンスの最大の動力は長時間実行AIエージェントだ。LLMエージェントの特性を見よう。

  • 一つのタスクが数十分から数日走る(コードレビュー・リサーチ・自動化)。
  • 外部APIを数十〜数百回呼ぶ(OpenAI・Anthropic・各種ツール)。
  • コストが単一タスクあたり $0.10 から $10 までかかり得る。
  • 途中で人の承認を待つ(human-in-the-loop)。
  • 失敗時に最初からやり直すとコストが二倍。

これらすべてがデュラブル実行に完璧に合う。

// AIエージェント + デュラブル実行 — Trigger.dev v3
import { task, wait, logger } from '@trigger.dev/sdk/v3'
import Anthropic from '@anthropic-ai/sdk'

const claude = new Anthropic()

export const researchAgent = task({
  id: 'research-agent',
  maxDuration: 7200, // 2時間
  run: async (payload: { question: string }, { ctx }) => {
    const tools = [searchWebTool, readPageTool, summarizeTool]
    const messages: Anthropic.MessageParam[] = [
      { role: 'user', content: payload.question },
    ]

    for (let iteration = 0; iteration < 50; iteration++) {
      const response = await claude.messages.create({
        model: 'claude-opus-4-7',
        max_tokens: 4096,
        tools,
        messages,
      })

      messages.push({ role: 'assistant', content: response.content })

      if (response.stop_reason === 'end_turn') {
        return { finalAnswer: extractText(response.content), iterations: iteration }
      }

      // ツール呼び出しの処理 — 各ツール呼び出しがデュラブル境界
      const toolResults = await Promise.all(
        response.content
          .filter((b) => b.type === 'tool_use')
          .map(async (tool) => {
            return await executeTool(tool.name, tool.input)
          })
      )

      messages.push({ role: 'user', content: toolResults })

      // 5回ごとに小休止 — レート制限回避
      if (iteration % 5 === 4) await wait.for({ seconds: 30 })
    }

    throw new Error('agent-did-not-converge')
  },
})

このパターンが2026年AIエージェントインフラの標準になりつつある。エージェント自体は単純なループ — LLM呼び出し → ツール実行 → またLLM呼び出し。デュラブル実行がそのループの各ステップを永続化する。14回目で死んでもコストを二倍払わない。

Anthropicが2024〜2025に発表したデュラブルなコンテキストキャッシュ(prompt caching)、VercelのAI SDK + Inngest統合、LangGraphのチェックポイントモデル — すべて同じ方向を指す。LLMエージェントはデュラブル実行なしにproductionに入れない。


第15章 · トス決済ワークフロー事例

韓国フィンテックのトス(Toss)は2018年以降、決済処理インフラでデュラブル実行パターンを採用してきた(公開情報ベース。直接Temporalを使うかは別問題)。決済は本質的にデュラブル実行が適合する領域である。

トス決済の特性を見よう。

  • 一回の決済がカード会社・VAN・PG・精算システムを経る。
  • どの段階でも失敗し得て、部分失敗が普通だ。
  • 決済一件が二度処理されてはいけない(冪等性)。
  • 返金は決済の鏡 — 補償トランザクション。
  • 紛争発生時に全段階の監査ログが必要。

この領域でサガパターンとイベントソーシングは自然だ。トスのような韓国フィンテックは独自のワークフローエンジンを作るかKafkaベースのサガを組んだ事例が多く、2024年以降はTemporal導入事例も増えている。決済領域の複雑度がデュラブル実行エンジンの価値を最もよく示す事例だ。

似た流れでカカオペイもマイクロサービス間の決済サガを独自フレームワークで実装してきた。カカオペイの技術ブログは「分散トランザクションをサガで解き、補償トランザクションを明示的にコード化する」というパターンを共有したことがある。決済・送金・ペイバックのような多段階ワークフローはデュラブル実行のキヤノン事例だ。


第16章 · 日本事例 — メルカリ・楽天・サイボウズ

日本でもデュラブル実行の導入が早い。

メルカリはマイクロサービス環境の決済・精算をサガパターンで解いてきた。メルペイ(Merpay)は分散トランザクション問題を回避するために明示的なサガコーディネータを置き、各段階を冪等性で保護する。2024年以降は一部領域にTemporalを導入したという発表があった。

楽天は広範な事業(EC・金融・通信・旅行)間のワークフロー量が巨大だ。独自の社内ワークフローエンジンとともにApache Airflowをデータパイプラインに、AWS Step Functionsを一部領域に、CamundaをBPMNワークフローに使う多エンジン環境だ。楽天規模では一つのエンジンに統一する方がむしろ難しい。

サイボウズ(kintone開発元)はクラウドサービスの非同期処理に早くからメッセージキュー + ワーカーパターンを扱ってきて、2020年代にはいって一部領域にデュラブル実行パターンを適用した。日本のSaaSではデュラブル実行はユーザーデータの安全な移行・アカウント整理・サブスクリプション変更のような所に自然に適用される。

日本市場の興味深い点は自前実装の比重が高いことだ。NTT・SoftBank・Nintendoのような巨大組織は自前のワークフローエンジンを運用する場合が多く、新ツール導入のスピードは米国より遅い。代わりに一度導入すれば深く使う。


第17章 · Cadence — Uberが作った原祖

Cadence(2017年Uberオープンソース)はTemporalの直系の祖先だ。Maxim Fateev・Samar Abbasら核となるチームが2019年にUberを離れてTemporalを創業し、核となるアーキテクチャはほぼ同じだ。違いは時間が経つにつれSDK・APIが分かれてきた点。

2026年現在、CadenceはUber内部で依然大規模に運用されている — 毎日数千万件のワークフロー。外部利用者も一部いるが、Temporalの方に重心が移った。新プロジェクトならTemporalを選ぶのが自然だ。Cadenceは「Temporal以前の時代の運用資産」の席だ。

興味深い分岐の流れがある。CadenceとTemporalは同じ根から出発したが、意味論・SDK・運用モデルが少しずつ離れている。Cadenceのロングポーリング・モデル、TemporalのWorkflow Updateのような新機能 — 一方にはあって他方にない機能が増えていく。二エンジン間の互換はもはや保証されない。


第18章 · Camunda・Conductor — BPMNとマイクロサービス・オーケストレーション

コード中心のデュラブル実行の隣には別の伝統のエンジン群がある。

Camunda(ドイツ、2008年創業)はBPMN(Business Process Model and Notation)標準ベースのワークフローエンジンだ。ビジネスアナリストがグラフィカルに描いたBPMNダイアグラムがそのまま実行可能なワークフローになる。銀行・保険・通信のようなドメインで強い。Camunda 8(2022年発表)でCloud Nativeの再設計が行われ、Kubernetes上でマイクロサービス・オーケストレーションも扱う。

Netflix Conductor(2016年Netflixオープンソース)はマイクロサービス・オーケストレーション・エンジンだ。JSON DSLでワークフローを定義し、複数言語のワーカーがタスクをポーリングする。Netflix内部の大規模ビデオ処理・コンテンツメタデータ・パイプラインを回す。2022年にOrkesが会社としてspin-offされ、商用サポートを提供する。

エンジン定義形式強み弱み
CamundaBPMN(グラフィカル)ビジネス/IT協業・標準XMLベース・複雑
ConductorJSON DSL多言語ワーカー・Netflix検証コード表現力低い
Temporalコード(TS・Go・Java・Python)表現力・決定論的リプレイ学習曲線
Step FunctionsJSON(ASL)AWS統合DSL限界

核となる区別: コード vs DSL。ビジネスアナリストがワークフローを定義する事が多いならBPMN/Camunda。エンジニアが全ワークフローを書くならTemporal・Inngest。AWSロックインがOKならStep Functions。


第19章 · セルフホスト vs SaaS — コスト・運用のトレードオフ

ワークフローエンジンの大きな決断の一つはセルフホストかSaaSか

エンジンセルフホストSaaSSaaS価格モデル
Temporal可能(運用重い)Temporal Cloudアクション・イベント単位
Inngest可能(2024年〜)Inngest Cloud(基本)実行単位
Trigger.dev可能(Docker)Trigger.dev Cloud実行 + 時間
Hatchet可能(Docker)Hatchet Cloud実行単位
Restate可能(単一バイナリ)ベータTBD
DBOS可能(Postgres + ワーカー)DBOS CloudTBD
Step Functions不可(AWS専用)AWS状態遷移単位
Airflow可能Astronomer・MWAAインスタンス時間
Prefect可能Prefect Cloud実行 + ユーザー
Dagster可能Dagster Cloud実行 + ユーザー

セルフホストの真実: Temporalをproduction水準でセルフホストするコストは少なくない。 Cassandra/MySQL運用、四サービスのHA、メトリクス/モニタリング、アップグレード。小さなチームにはSaaSがほぼ常に安い。

代わりにデータ主権・コンプライアンス・VPC内のみで動く要求があるならセルフホストが必須だ。韓国・日本の金融機関はほぼ常にセルフホスト。米国インディSaaSはほぼ常にSaaS。

興味深い傾向: ハイブリッドモデル。Inngest・Trigger.devはコントロールプレーンはSaaS、実際の実行ワーカーはユーザーインフラに置ける。データは漏れず、運用負担はSaaS側。2026年にますます一般化するパターン。


第20章 · どのエンジンをどこに使うか — 一行ガイド

ツール選択ガイド。単純化するとこうなる。

  • 決済・フィンテックサガ・複雑な分岐 → Temporal。事実上の標準。
  • AIエージェント・SaaS自動化・DX最優先 → Inngestまたは Trigger.dev v3。どちらでも、好み。
  • 単純なキューの進化・Postgresだけで済ませたい → Hatchet。運用が軽い。
  • オブジェクト単位のステートフル呼び出し・小さな運用フットプリント → Restate。新しい、ベット。
  • トランザクション一貫性が重要・Postgresネイティブ → DBOS。学術的根幹。
  • 既にAWS上にすべてある・AWSロックインOK → Step Functions。
  • データETLパイプライン → Airflow(伝統)・Prefect(モダン)・Dagster(資産中心)。
  • KubernetesネイティブML/CI → Argo Workflows。
  • BPMN標準・ビジネス/IT協業 → Camunda。
  • Netflixスタイルのマイクロサービス・オーケストレーション → Conductor。

混ぜて使うのも普通だ。 決済はTemporal、データはAirflow、AIはInngest — よくあるセットアップ。一つのエンジンで全ワークフローを統一しようとする試みは大抵失敗する。各エンジンが得意な領域が違い、その差がツール選択の本質だ。


第21章 · 罠 — デュラブル実行を導入する時にハマる5つ

  1. 決定論を守らない — ワークフローコード内で Math.random()Date.now()・環境変数を直接使用。最初のリプレイで別の道に分岐してデバッグ地獄。常にSDKの決定論的関数を使え。

  2. 巨大なワークフローペイロード — ワークフローの入出力に100MBを入れる。Historyが爆発して性能が死ぬ。大きなデータはS3に入れて参照だけ渡せ。

  3. ActivityとWorkflowの責任の混同 — ワークフローで直接DBクエリ・HTTP呼び出し。すべての副作用はActivity内に隔離。ワークフローは純粋なオーケストレーション。

  4. バージョニングの無視 — ワークフローが数日走っている間にコードを変えると、進行中の実行が新コードと互換性がない。TemporalのpatchedやInngestの関数バージョニングのような道具を学ぶべきだ。

  5. 冪等キーを付けない — Activityが外部呼び出し(Stripe・SendGrid)するときに冪等キーがない。Activity再試行時に二回課金される。デュラブル実行 + 冪等キー = 実質一回。どちらも必須。

この五つがデュラブル実行導入の本物の学習曲線だ。ツールを入れたら終わりではなく、決定論・隔離・バージョニング・冪等性をコードパターンとして体得しなければならない。二〜三週かかる。


第22章 · コストモデル — ワークフローあたりの本当のコスト

ワークフローエンジン導入時によく見落とされるのがコストモデルだ。SaaS価格は小さく見えるが、単位が違うとショックが大きい。

おおよその2026年価格帯(公式価格、実際の交渉価格は異なる)。

  • Temporal Cloud$200 程度から、アクション(イベント)単位。大きなトラフィックでは月数万ドル。
  • Inngest — 無料5万実行/月、Pro $50/月 で25万実行。実行単位。
  • Trigger.dev — 無料1万実行/月、Pro $50/月 で10万実行 + 時間。
  • Hatchet — 無料1千ワークフロー/月、Proから使用量ベース。
  • Step Functions Standard — 状態遷移千件あたり $0.025。小さな作業には高く見え、大きなワークフローには意外に安い。
  • Step Functions Express — 実行千件あたり $1、メモリ・時間追加。

直感チェック用。AIエージェントが一回のワークフローで平均20ステップを走り、月10万回回ると仮定。

  • Inngest Pro 25万実行に収まる — 月 $50
  • Step Functions Standard — 200万状態遷移 = 約月 $50
  • Temporal Cloud — 200万アクション → 別途見積もり、大きな差で高くなり得る。
  • セルフホストHatchet — Postgres一台 + ワーカー = インフラコストのみ。

ワークフローあたりの単価がLLMコストに対していくらかが本物の信号だ。一つのエージェント実行が $1 のLLMを焚くなら、ワークフローエンジン $0.001 は事実上タダ。逆にメール送信のような単純なバックグラウンドジョブに高価なエンジンを使うとコストが速く累積する。


第23章 · 移行 — 一つのエンジンから別のエンジンへ

ワークフローエンジンは一度導入すると入れ替えが難しい。ワークフローコードと永続状態が強く結合しているからだ。

移行戦略を見よう。

  1. 新しいワークフローは新しいエンジンへ — 既存はそのまま残し、新しく作るワークフローだけ新エンジンに書く。最もよくあるパターン、時間が経てば自然移行。

  2. ドレインモデル — 既存エンジンに新規実行をブロックし、進行中の実行が終わるまで待つ。ワークフローが数日走るなら数か月かかり得る。

  3. イベント再放送 — イベントソーシングベースなら、イベントログを新エンジンに再放送。理論的には可能だが意味論の差で難しい。

  4. 手動状態移行 — 進行中ワークフローの状態を抽出して新エンジンのワークフローとして復元。最も難しく危険。

大部分のチームは1を選ぶ。時間が味方だ。新エンジンが導入価値あるなら6〜12か月後には既存エンジンの比重が自然に減る。


第24章 · 2026年のトレンド — どこへ向かうか

最後に2026年時点の大きな潮流をまとめる。

  • AIエージェントがデュラブル実行を強制した — LLMコスト・長さ・信頼性の問題でデュラブル実行なしのproductionエージェントは事実上不可能。Temporal/Inngest/Trigger.devの売上カーブはこの傾向を正確に追う。
  • Postgresがインフラの中心へ — Hatchet・DBOS・GraphileWorker・Riverqueue・pg-boss。Kafkaなしでも Postgresで十分だという認識が広がる。「Postgresがキューも、ワークフローエンジンも、コンピュートも。」
  • ハイブリッド・セルフホスト — コントロールプレーンはSaaS、実行はユーザーVPC内。データ主権と運用の便利さの折衷。
  • ワークフロー・バージョニングの成熟 — TemporalのWorkflow Update、Inngestの関数バージョニング。数日走るワークフローのコード変更の意味論がますます精緻化する。
  • イベント関数モデルの一般化 — Inngest・Trigger.devスタイルの「イベントが関数を起こす」モデルがSaaS・インディ開発者の標準になる。別途のワークフロー定義なしに関数自体がワークフロー。

もう一つの流れはCDC + ワークフローの結合だ。Postgres CDC(Debezium・Sequin)でイベントを抜いてワークフローエンジンを起こすパターン。データベースの変更がそのままワークフローの開始点になる。2026年後半により一般化すると見られる。


エピローグ — 死なない関数の時代

一文の要約: 2026年、ワークフローエンジンはインフラの中核レイヤーになった。理由は二つ — AIエージェントがデュラブル実行を強制し、Postgresがワークフローのバックボーンになりつつあるから。

15年前の分散システム書のサガパターンが学術的だったとすれば、2026年にはそれがすべてのSaaSバックエンドの基礎技能だ。決済・送金・予約・メッセージング・AIエージェント — すべてサガの変種だ。そしてそのサガはデュラブル実行エンジンの上で回る。

次の10年のバックエンド・エンジニアリングでは「どのデータベースを使うか」と同じくらい「どのワークフローエンジンを使うか」が重要になる。そしてそのエンジンの選択はビジネスドメインの本質に依る — 決済はTemporal、AIはInngest、ETLはAirflow。一つのツールですべてを解くことはできない。

最後に一言: コンテナは死ぬ。しかしワークフローは死んではならない。 それがデュラブル実行の約束だ。

「プロセスは永遠ではない。しかし関数の意味は永遠であり得る。それがデュラブル実行だ。」

— デュラブル実行 & ワークフローエンジン 2026、終わり。


参考 / References