AI

タスクスケジューラOSS比較:BullMQ vs Temporal vs Inngest でバックグラウンドジョブを管理する

オープンソースラボ編集部2026年6月13日

タスクスケジューラOSS比較:BullMQ vs Temporal vs Inngest でバックグラウンドジョブを管理する

cronジョブのタイムアウト・失敗時のリトライ・長時間ワークフローの管理に悩んでいませんか?BullMQTemporalInngestはジョブキュー・ワークフローエンジン・イベント駆動型バックグラウンド処理をOSSで実現します。

バックグラウンドジョブ管理が必要な場面

  • 重いAPI処理のオフロード: 画像変換・PDF生成・AI推論など時間のかかる処理をキューに入れてAPIを即座にレスポンスしたい
  • 失敗時の自動リトライ: メール送信・外部APIコールが失敗したら指数バックオフで自動再試行したい
  • 長時間ワークフロー: 注文→決済→発送→通知の複数ステップを管理して途中失敗でも再開したい
  • cronジョブ管理: 毎日0時のバッチ処理を確実に1回だけ実行して失敗時に通知したい
  • イベント駆動処理: ユーザー登録・支払い完了などのイベントに反応してバックグラウンド処理を起動したい

主要ツールの概要

BullMQ

Node.js + Redisベースのジョブキューライブラリです。GitHubスター6k+で最も広く使われているNode.jsのジョブキュー。シンプルなキューから優先度付きジョブ・遅延実行・cronジョブ・フロー(DAG)まで対応します。

// BullMQのセットアップ(Next.js APIルート + Worker)
// lib/queue.ts
import { Queue, Worker, QueueEvents } from "bullmq";
import { Redis } from "ioredis";

const connection = new Redis(process.env.REDIS_URL!, {
  maxRetriesPerRequest: null,
});

// キューの定義
export const emailQueue = new Queue("email", { connection });
export const imageQueue = new Queue("image-processing", { connection });

// ジョブの追加
export async function scheduleEmail(payload: {
  to: string;
  subject: string;
  body: string;
}) {
  await emailQueue.add("send-welcome", payload, {
    attempts: 3,             // 最大3回リトライ
    backoff: {
      type: "exponential",
      delay: 1000,           // 1秒→2秒→4秒のバックオフ
    },
    removeOnComplete: 100,   // 完了済みジョブを100件まで保持
    removeOnFail: 200,       // 失敗ジョブを200件まで保持
  });
}

// cronジョブ(毎日9時にデイリーサマリー送信)
await emailQueue.add(
  "daily-summary",
  { type: "daily" },
  {
    repeat: { cron: "0 9 * * *", tz: "Asia/Tokyo" },
    attempts: 3,
  }
);
// app/api/send-email/route.ts - APIルートからジョブを追加
import { NextRequest, NextResponse } from "next/server";
import { scheduleEmail } from "@/lib/queue";

export async function POST(req: NextRequest) {
  const { to, subject, body } = await req.json();

  // ジョブをキューに追加してすぐにレスポンスを返す
  await scheduleEmail({ to, subject, body });

  return NextResponse.json({ queued: true }, { status: 202 });
}
// workers/email.worker.ts - バックグラウンドワーカー
import { Worker, Job } from "bullmq";
import { Redis } from "ioredis";
import { Resend } from "resend";

const connection = new Redis(process.env.REDIS_URL!);
const resend = new Resend(process.env.RESEND_API_KEY);

const emailWorker = new Worker(
  "email",
  async (job: Job) => {
    const { to, subject, body } = job.data;

    console.log(`Processing job ${job.id}: sending to ${to}`);

    await resend.emails.send({
      from: "no-reply@yoursite.com",
      to,
      subject,
      html: body,
    });

    console.log(`Job ${job.id} completed`);
    return { sent: true, to };
  },
  {
    connection,
    concurrency: 10,  // 同時処理数
  }
);

emailWorker.on("completed", (job) => {
  console.log(`Email sent: ${job.id}`);
});

emailWorker.on("failed", (job, err) => {
  console.error(`Email failed: ${job?.id}`, err);
});
# BullMQ用Redisのdocker-compose設定
version: "3"
services:
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes

  # BullMQ Boardで管理UIを追加
  bull-board:
    image: deadly0/bull-board:latest
    ports:
      - "3001:3000"
    environment:
      REDIS_HOST: redis
      REDIS_PORT: 6379

volumes:
  redis_data:

Temporal

Goで書かれた分散ワークフローエンジンです。GitHubスター12k+で、長時間・複数ステップのワークフロー管理に強力です。UberとCadenceチームが作り、Netflixや Stripe等が本番で使っています。ワークフローの状態を自動的に永続化し、サーバー再起動後も正確に再開できます。

// Temporal TypeScript SDK でワークフローを定義
// workflows/order-workflow.ts
import { proxyActivities, sleep, defineSignal, setHandler } from "@temporalio/workflow";
import type * as activities from "../activities/order-activities";

const { processPayment, sendConfirmationEmail, scheduleShipping, notifyWarehouse } =
  proxyActivities<typeof activities>({
    startToCloseTimeout: "5 minutes",
    retry: {
      maximumAttempts: 3,
      initialInterval: "1s",
      backoffCoefficient: 2,
    },
  });

// シグナル定義(外部からワークフローに信号を送れる)
export const cancelSignal = defineSignal<[reason: string]>("cancel");

export async function orderWorkflow(orderId: string): Promise<string> {
  let cancelled = false;
  let cancelReason = "";

  setHandler(cancelSignal, (reason) => {
    cancelled = true;
    cancelReason = reason;
  });

  if (cancelled) {
    return `Order ${orderId} cancelled: ${cancelReason}`;
  }

  // ステップ1: 決済処理
  const paymentResult = await processPayment(orderId);
  if (!paymentResult.success) {
    throw new Error("Payment failed");
  }

  // ステップ2: 確認メール送信
  await sendConfirmationEmail(orderId, paymentResult.transactionId);

  // ステップ3: 24時間待機(倉庫処理時間)
  await sleep("24 hours");

  if (cancelled) {
    return `Order ${orderId} cancelled after payment: ${cancelReason}`;
  }

  // ステップ4: 倉庫に通知
  await notifyWarehouse(orderId);

  // ステップ5: 配送スケジュール
  const trackingNumber = await scheduleShipping(orderId);

  return `Order ${orderId} shipped: ${trackingNumber}`;
}
// activities/order-activities.ts - アクティビティ(実際の処理)
export async function processPayment(orderId: string) {
  // Stripe等の決済APIを呼ぶ
  const result = await stripe.paymentIntents.confirm(orderId);
  return { success: result.status === "succeeded", transactionId: result.id };
}

export async function sendConfirmationEmail(orderId: string, transactionId: string) {
  await resend.emails.send({
    from: "orders@yoursite.com",
    to: await getOrderEmail(orderId),
    subject: "ご注文を受け付けました",
    html: `注文番号: ${orderId}<br>取引ID: ${transactionId}`,
  });
}

// ワーカーの起動
// workers/temporal.worker.ts
import { Worker } from "@temporalio/worker";
import * as activities from "./activities/order-activities";

const worker = await Worker.create({
  workflowsPath: require.resolve("./workflows/order-workflow"),
  activities,
  taskQueue: "order-processing",
});
await worker.run();

Inngest

サーバーレス環境(Vercel・Netlify)でバックグラウンドジョブとワークフローを実行できるプラットフォームです。Next.jsとの統合が特に優れており、自社サーバーを持たずにキューとワークフローを使えます。OSSのInngest Dev Serverでローカル開発もできます。

// Inngestのセットアップ(Next.jsと統合)
// lib/inngest.ts
import { Inngest } from "inngest";

export const inngest = new Inngest({ id: "my-app" });

// イベントの型定義
type Events = {
  "user/signed-up": { data: { userId: string; email: string } };
  "order/placed": { data: { orderId: string; amount: number } };
  "article/published": { data: { slug: string; title: string } };
};

export const inngestTyped = new Inngest<Events>({ id: "my-app" });
// functions/onUserSignUp.ts - ユーザー登録時のワークフロー
import { inngestTyped } from "@/lib/inngest";

export const onUserSignUp = inngestTyped.createFunction(
  {
    id: "on-user-signed-up",
    retries: 3,
  },
  { event: "user/signed-up" },
  async ({ event, step }) => {
    const { userId, email } = event.data;

    // ステップ1: ウェルカムメール送信
    await step.run("send-welcome-email", async () => {
      await resend.emails.send({
        from: "welcome@yoursite.com",
        to: email,
        subject: "ようこそ!",
        html: "アカウントが作成されました。",
      });
    });

    // ステップ2: 3日後にフォローアップメール
    await step.sleep("wait-3-days", "3 days");

    const user = await step.run("check-user-activity", async () => {
      return await db.users.findUnique({ where: { id: userId } });
    });

    if (!user?.lastActiveAt) {
      await step.run("send-followup-email", async () => {
        await resend.emails.send({
          from: "support@yoursite.com",
          to: email,
          subject: "何かお困りですか?",
          html: "サポートが必要でしたらご連絡ください。",
        });
      });
    }
  }
);

// app/api/inngest/route.ts
import { serve } from "inngest/next";
import { inngest } from "@/lib/inngest";
import { onUserSignUp } from "@/functions/onUserSignUp";

export const { GET, POST, PUT } = serve({
  client: inngest,
  functions: [onUserSignUp],
});

機能比較表

比較項目BullMQTemporalInngest
ライセンスMITMITApache 2.0
インフラ要件RedisTemporal Server + DBクラウドサービス or Dev Server
実装言語TypeScript/Node.jsGo(多言語SDK)TypeScript/Python/Go
ジョブキュー
cronジョブ
長時間ワークフロー✅ 得意
ステップ実行・再開
サーバーレス対応❌(Redisが必要)❌(サーバー必要)✅ Vercel等で動く
シグナル・クエリ
管理UI✅ Bull Board✅ Web UI✅ Inngest Cloud
セットアップの簡単さ★★★★☆★★☆☆☆★★★★★
スケーラビリティ中規模大規模クラウド依存
GitHub Stars6k+12k+4k+

バックグラウンドジョブとDevOpsの関連ツールはdevopsカテゴリ(/categories/devops)にまとめています。CI/CDパイプラインとの統合についてはCI/CDセルフホスト比較(/categories/devops)も参照してください。

FAQ

Q. BullMQとBullの違いは何ですか?

A. BullMQはBull(前世代のRedisジョブキュー)の後継ライブラリです。主な違い: ①BullMQはTypeScriptネイティブで型安全、②Redisの最新機能(Redis Streams)を活用して信頼性が向上、③フロー機能(親ジョブ→子ジョブのDAG)が追加、④ワーカーの並行処理モデルが改善されてメモリリークが減少。現時点ではBullMQを使うことを推奨します。BullからBullMQへの移行は比較的簡単で、APIが似ているためコードの変更量が少なく済みます。

Q. TemporalをVercelやサーバーレス環境で使えますか?

A. Temporalはステートフルなサーバー(Temporal Server)が必要なため、純粋なサーバーレス環境では動きません。対策: ①Temporal Cloudを使う(マネージドサービス、$25/月〜)、②Fly.ioやRenderにTemporal Serverをデプロイする(Dockerfile提供あり)、③Vercelで使う場合は「Inngest」や「Trigger.dev」(類似ツール)を検討する。サーバーレスでTemporalライクな体験を求めるならInngestが最も近いです。

Q. BullMQのRedisはどのくらいのリソースが必要ですか?

A. ジョブ量に依存しますが、中小規模のWebアプリなら512MB〜1GBのRedisで十分です。ジョブデータはRedisのメモリに保存されるため、大量のジョブや大きなペイロードは注意が必要です。対策: ①removeOnCompleteremoveOnFailオプションで完了・失敗ジョブを定期削除する(設定しないとメモリが溢れる)、②ジョブのペイロードは最小限に(大きなデータはS3/Supabase Storageに保存してIDだけキューに入れる)、③Upstash Redis(サーバーレスRedis)を使えばVercel等でも手軽にBullMQが使えます(データ量課金)。

Q. Inngestのセルフホストは可能ですか?

A. Inngest Dev Serverは無料のローカル開発用ツールとして提供されています(npx inngest-cli@latest devで起動)。本番環境ではInngest Cloudを使うのが基本ですが、2024年以降にセルフホスト版(Inngest Community Edition)の提供も進んでいます。セルフホストには PostgreSQL が必要です。InngestはVercelのような外部からWebhookを受信できる環境が前提のため、プライベートネットワーク内のみの環境では動作しません。完全にオンプレミスでクラウドに依存しない構成が必要ならBullMQ + Redisが適切です。

まとめ

ユースケース推奨ツール
シンプルなジョブキュー + RedisBullMQ
長時間ワークフロー + エンタープライズTemporal
Vercel/Next.js + サーバーレスInngest
cronジョブ管理 + 可視化UIBullMQ + Bull Board

関連外部リソース

他の記事も読む

Let's Build Together

OSS導入、自社だけで悩まない。

ツール選定から構築・運用・AI活用まで、オープンソースラボ運営元のClasslessが伴走します。初回のご相談は無料です。