よくある光景:ユーザーの待機と504 Gateway Timeoutエラー
Next.jsを使用しているなら、こんな状況に遭遇したことがあるはずです。ユーザーが「登録」ボタンを押し、サーバーがメール送信からStripeアカウント作成、HubSpotとの同期まで一連のタスクを処理し始めます。ローディングアイコンは延々と回り続け、Vercelではこの処理が10秒(Hobbyプラン)または60秒(Proプラン)を超えると、即座に504エラーが返されます。
1日5,000人以上のユーザーが登録する実際のプロジェクトで、私たちのチームはPromise.all()に苦しめられました。StripeのようなサードパーティAPIのレスポンスが少し遅れるだけで、リクエスト全体が失敗してしまいます。その際、メールが送信されたのか、データベースがどこまで更新されたのかを追跡するのは、まさに悪夢でした。
なぜサーバーレス時代において、RedisとBullMQは最適解ではなくなったのか?
BullMQとRedisは、従来のNode.jsエコシステムにおける強力なコンビです。しかし、VercelやAWS Lambdaのような環境に持ち込むと、3つの致命的な弱点が露呈します。
- 肥大化したインフラ: Redisクラスターを自分で管理する必要があります。数千のServerless関数とRedis間の接続(Connection Pooling)を維持することは、頻繁に接続溢れを引き起こします。
- 非対称な構造: BullMQはジョブを監視するために24時間365日稼働するWorkerが必要です。対照的に、Serverless functionは数秒間だけ実行されて消滅するため、これらのWorkerが存在する場所がありません。
- 隠れたコスト: UpstashのようなManaged Redisサービスは優れていますが、スケールが大きくなると、リクエストごとのコストやストレージ費用が予算を圧迫し始めます。
よくある代替案(となぜそれらが不十分なのか)
Inngestに出会う前、いくつかの応急処置的な方法を試しました。
setTimeoutを使うのは最も手軽ですが、最悪の方法でもあります。Serverless functionがレスポンスを返すと、実行環境は即座にフリーズされ、バックグラウンドタスクは途中で強制終了されます。もう一つの選択肢はAWS SQSですが、IAM Policyの設定や膨大な技術パラメータにより、開発スピードが著しく低下します。
Inngest:バックグラウンドジョブに対する新しいアプローチ
Inngestはキューの管理を強要しません。これはイベント駆動(Event-driven)メカニズムで動作します。イベントが発生した際、Inngest Cloudにシグナル(Event)を送信するだけです。その後、Inngestがコーディネーターとなり、Next.jsアプリケーションのエンドポイントに対して(HTTP POST経由で)コールバックを行い、ロジックを実行します。
最大のメリットは、複雑なWorkflowを純粋なTypeScriptコードで記述できる点です。「すぐにメールを送信し、ちょうど3日待ってからユーザーが支払いを済ませたか確認する」といった指示を、インフラを気にすることなく数行のコードで完結させられます。
実践的な実装:最適化されたユーザー登録フロー
実際にNext.js App RouterプロジェクトにInngestを導入してみましょう。
ステップ 1:ライブラリのインストール
npm install inngest
ステップ 2:Inngestクライアントの初期化
src/inngest/client.tsを作成します。これがイベント送信の唯一の窓口となります。
import { Inngest } from "inngest";
export const inngest = new Inngest({ id: "my-app-v1" });
ステップ 3:バックグラウンド処理ワークフローの定義
src/inngest/functions.tsでロジックを定義します。step.sleepがいかに複雑なタイマー関数を完全に置き換えるか注目してください。
import { inngest } from "./client";
export const processSignup = inngest.createFunction(
{ id: "process-signup-flow" },
{ event: "app/user.signup" },
async ({ event, step }) => {
// すぐにウェルカムメールを送信
await step.run("send-welcome-email", async () => {
return { status: "success", email: event.data.email };
});
// 24時間待機してからアンケートを送信
await step.sleep("wait-for-survey", "24h");
await step.run("send-survey", async () => {
console.log("アンケート送信先:", event.data.email);
});
}
);
ステップ 4:ルートハンドラーの設定
Inngestがアプリケーションと通信するための「ゲートウェイ」が必要です。src/app/api/inngest/route.tsファイルを作成します。
import { serve } from "inngest/next";
import { inngest } from "@/inngest/client";
import { processSignup } from "@/inngest/functions";
export const { GET, POST, PUT } = serve({
client: inngest,
functions: [processSignup],
});
自己修復機能(Resilience)とCron Jobs
開発者の最大の懸念の一つは、サードパーティAPIのダウンです client. Inngestでは、step.runが失敗すると、システムが自動的にエクスポネンシャルバックオフ(Exponential Backoff)を実行します。リトライロジックを自分で書く必要はありません。
午前2時にデータベースをクリーンアップするような定期タスクが必要な場合、Inngestは非常に簡潔なCron構文をサポートしています。
export const weeklyCleanup = inngest.createFunction(
{ id: "weekly-cleanup" },
{ cron: "0 2 * * 1" }, // 毎週月曜日の午前2時
async ({ step }) => {
await step.run("delete-old-logs", async () => {
// ここにクリーンアップロジックを記述
});
}
);
実体験からの結論
自前で構築したシステムからInngestに移行した後、チームのバックグラウンドジョブ関連機能の開発時間は3日間から約4時間に短縮されました。デバッグも、各ステップの状態をリアルタイムで追跡できるDashboardのおかげで、より直感的になりました。
アドバイス:ローカルで実行する際は、常にnpx inngest-cli@latest devコマンドでDev Serverを起動してください. これはクラウド環境を完璧にシミュレートするため、費用を一切かけずにWorkflowをテストできます。クリーンなコード、高速なアプリを目指し、Redisによる悩みを解消したいなら、InngestこそがNext.jsに欠けていた最後のピースです。

