メインコンテンツまでスキップ

第17章:RedisベースのQueueを導入する(まずはここから)🟥📦

この章は「APIが仕事を投げる → Redisに溜まる → Workerが拾って処理する」という流れを、最小のコードで“動く”ところまで作ります💪✨ (Workerを別サービスにするのは次章でやるよ👷‍♂️➡️)


1) まず“キュー”を1枚絵で理解しよ🖼️🧠

イメージはこれ👇

[API] --(Jobを追加)--> [Redis(Queue)] --(Jobを取得)--> [Worker]
  • API:HTTPリクエストにすぐ返したい(待たせない)🚀
  • Redis:ジョブの“待合室”(順番待ちの箱)📦
  • Worker:時間かかる処理を裏でやる係👷‍♂️

2) 今回使うライブラリ:BullMQ(Redisベースの定番)🐂⚙️

BullMQは TypeScriptで書かれていて、サンプルもTS中心なので教材と相性よし👍 (docs.bullmq.io) 内部のRedis接続は ioredis を使い、接続オプションもそれに沿う形です。(docs.bullmq.io)


3) Redis側を“キュー向け”にちょい設定🧯(超重要)

BullMQは Redisが勝手にキーを捨てる(eviction)設定だと正常に動けないので、maxmemory-policynoeviction が推奨(というか必須級)です。(docs.bullmq.io)

compose.yaml の redis をこうしておくと安心👌 (すでにRedisがあるなら、command: 行だけ足してOKだよ)

services:
redis:
image: redis:7-alpine
command: ["redis-server", "--appendonly", "yes", "--maxmemory-policy", "noeviction"]
volumes:
- redis-data:/data
ports:
- "6379:6379"

volumes:
redis-data:

ついでに小ネタ:Composeのトップレベル version: は今は“obsolete(形だけ)”扱いで、書かなくてOK寄りです。(Docker Documentation)


4) BullMQをインストールしよう📦✨

(npm例。pnpm/yarnでも同様!)

npm install bullmq

5) Redis接続情報を環境変数で統一🔑🧩

ここ、初心者が一番ハマりやすいポイント😇

  • コンテナの中からRedisに繋ぐlocalhost じゃなくて サービス名 redis を使う
  • ホストPC(Windows)からRedisに繋ぐlocalhost:6379

.env にこんな感じで置くのがラク👇

REDIS_HOST=redis
REDIS_PORT=6379

(次章でWorkerを別サービスにしても、同じ設定がそのまま使えます👍)


6) キューの“共通部品”を作る🧱🛠️

src/queue.ts(ファイル名はお好みでOK)

import { Queue } from "bullmq";

const QUEUE_NAME = "demo";

export const connection = {
host: process.env.REDIS_HOST ?? "redis",
port: Number(process.env.REDIS_PORT ?? "6379"),
};

export const demoQueue = new Queue(QUEUE_NAME, { connection });

export type DemoJob = {
message: string;
requestedAt: number;
};

ポイント👇

  • QUEUE_NAMEAPIとWorkerで一致させる(ここが同じ箱📦)
  • connectionBullMQ → ioredis に渡る設定(host/portなど)(docs.bullmq.io)

7) APIからJobを投げる(enqueue)📨➡️📦

あなたのAPIが何製でもやることは同じ!

  • リクエスト受ける
  • queue.add() する
  • すぐレスポンス返す(これが気持ちいい😆)

例(Expressっぽい最小イメージ)👇

import { demoQueue, type DemoJob } from "./queue";

export async function enqueueDemo(message: string) {
const payload: DemoJob = { message, requestedAt: Date.now() };
const job = await demoQueue.add("demo:print", payload);
return { id: job.id };
}

add("ジョブ名", データ) で投げる感じ。(docs.bullmq.io)


8) Workerを作る(処理する側)👷‍♂️🔥

src/worker.ts を作成👇

import { Worker } from "bullmq";
import { connection, type DemoJob } from "./queue";

const QUEUE_NAME = "demo";

const worker = new Worker<DemoJob>(
QUEUE_NAME,
async (job) => {
// ここが「重い処理」ゾーン💪(例なのでログ出すだけ)
console.log("✅ start:", job.name, job.id, job.data);

// 重い処理っぽく待つ(デモ)
await new Promise((r) => setTimeout(r, 800));

console.log("🎉 done:", job.id);
return { ok: true };
},
{ connection }
);

worker.on("failed", (job, err) => {
console.log("💥 failed:", job?.id, err.message);
});

console.log("👂 worker is listening...");

Workerは「ジョブを処理して、成功ならcompleted、失敗ならfailed」みたいに状態遷移します。(docs.bullmq.io)


9) “まずは同じコンテナ”でWorkerを起動してみよう🧪✨

次章で分離するから、この章は 同居運用でOK🙆‍♂️

  1. いつも通り起動(すでに起動中ならスキップ)
docker compose up -d
  1. Workerだけ別プロセスで起動 (api があなたのAPIサービス名だとして)
docker compose exec api node dist/worker.js

もしまだTSを直接動かす運用(tsx等)なら、あなたのやり方に合わせて👇

  • docker compose exec api npm run worker
  • docker compose exec api npx tsx src/worker.ts

(この章の主役は“流れが動く”ことなので、起動手段は合わせてOK👍)

  1. APIにリクエストしてジョブ投入🎯 PowerShellならこんな感じ(例)👇
Invoke-RestMethod -Method Post http://localhost:3000/demo -ContentType "application/json" -Body '{"message":"hello queue"}'
  1. Worker側のターミナルに ✅ start ...🎉 done ... が出たら勝ち🏆✨

10) よくある詰まり(ここだけ読めば大体助かる)🧯🪄

(A) ECONNREFUSED 127.0.0.1:6379 になる😇 → コンテナ内からRedisに繋ぐときは localhost じゃなくて redis(サービス名) .envREDIS_HOST=redis を確認✅

(B) Eviction policy is ... should be 'noeviction' と怒られる😱 → さっきの command: ["redis-server", ... "--maxmemory-policy", "noeviction"] を入れる BullMQはここが超大事。(docs.bullmq.io)

(C) ジョブが溜まるだけで処理されない🥲 → Workerが起動してない! Queueは“箱”で、処理する人(Worker)がいないと永遠に待ちます📦🧍‍♂️


11) 設計のミニ定石(超入門だけど効く)🧠✨

  • Queue名は用途で分ける(例:email / image / report)📦📦📦
  • Jobのデータは小さく(巨大な本文や画像そのものを入れない)🪶
  • Jobは“同じのが2回実行されても壊れない”設計に寄せる(後でリトライするから)🔁 ※リトライ/失敗設計は次章・次々章でガッツリやるよ🔥

12) AI(Copilot / Codex)に頼むと速いところ🤖⚡

おすすめの頼み方(そのままコピペOK)👇

  • 「BullMQで QUEUE_NAME=demo のQueue/WorkerをTypeScriptで作って。Jobのpayload型も定義して。Redis接続は REDIS_HOST/REDIS_PORT。最小構成で。」
  • localhost を使わず、Docker Compose内接続はサービス名 redis 前提にして。」
  • maxmemory-policy noeviction のRedis設定も compose.yaml の差分で出して。」

AIの出力は便利だけど、**“hostがlocalhostになってないか”**だけ必ず目視チェックね👀(ここが事故りがち)


次章へのつながり🎁➡️

この章で「Queueの導入」と「API→Queue→Worker」が動いた!🎉 次章(第18章)で、いよいよ WorkerをComposeの別サービスとして分離して、 “開発スタック感”が一気に完成します👷‍♂️🚀