Vegas Engineering by FORBIS 記事一覧
← 記事一覧へ戻る
Architecture ・ 第21回

バッチを、なくす — 項目別タイマー起動イベントで実現するバッチレス設計

「全件を舐めて対象を探す」夜間バッチをやめ、各項目が自分の発火を"予約"する。軽量ティッカーが到来分だけを起こす、スキャンしない設計。

Vegas 開発チーム ・ 2026-07-03 ・ 読了 7分

「毎晩2:00、全注文・全定期をスキャンして、今日処理すべきものを探す」——このバッチは、データが増えるほど遅くなり、時間内に終わらなくなります。Vegas はこの発想を逆転させ、各項目に"いつ起きるか"を持たせ、その時が来たものだけをイベントとして起こすことで、大きな夜間バッチをなくしています。

The Problem「スキャン型バッチ」の限界

次回定期受注の生成、予約キャンセル、ポイント失効、出荷予定日の出荷指示——通販には「ある時刻に何かする」処理が山ほどあります。素朴に作ると、それぞれが全件スキャンの定期バッチになります。

The Idea「探す」のをやめ、「予約」する

Vegas では、処理を起こしたい項目が発生した時点で、それを「予約イベント(トリガ)」としてレジストリに登録します。各エントリは「対象・呼ぶべきAPI・発火条件(時刻)・未発火フラグ」を持ちます。そしてごく軽量なティッカーが短間隔で回り、到来した(発火すべき)エントリだけをイベントバスへ流し、処理後に"発火済み"を記録します。

Core Idea

全件をスキャンして対象を「探す」のではなく、各項目が自分の発火を「予約」しておく。ティッカーは到来分だけを起こす=処理量はデータ総量ではなく"いま到来した件数"に比例する。

Mechanism仕組み:レジストリ+ティッカー+イベントバス

業務処理 発火を"予約"して登録 予約イベント・レジストリ 対象 / API / 発火時刻 / 未 対象 / API / 発火時刻 / 未 対象 / API / 到来✓ → 発火 全件ではなく"到来分"だけ引く ティッカー 1分ごと・到来分を取得 イベントバス → 冪等な業務処理へ 処理→"発火済み"記録 一度だけ(冪等)
図1:バッチレスの流れ。項目が発火を予約→ティッカーが到来分だけ取得→イベントバス→処理→発火済みに更新。

レジストリは「未発火」かつ「発火時刻が到来した」行だけを索引で引くので、テーブルが何億行あっても、1回で扱うのは"いま到来した数十件"だけです。ティッカー自体は数秒で終わる薄い処理で、EventBridge Scheduler のような仕組みで短間隔に起動します。

ticker.mjsSQL / Node.js
// ティッカー(例: 1分ごと)。全件スキャンではなく「到来した未発火」だけを引く
const due = await db.query(`
  SELECT id, api_name, target
  FROM   trigger_registry
  WHERE  fired_at IS NULL
    AND  fire_at <= NOW()          -- 発火時刻が到来
  ORDER BY fire_at ASC LIMIT 500`);   -- 到来分だけ・上限つき

for (const t of due) {
  await eventBus.publish(t.api_name, t.target);  // イベント化(冪等)
  await db.query(`UPDATE trigger_registry SET fired_at = NOW() WHERE id = ?`, [t.id]);
}

Why it wins効いてくる点

設定を時刻で解決する時刻バージョニングと根は同じ——「時刻を一級の概念として扱う」という思想を、処理の起動にも広げたものです。

Trade-offs設計上の勘所

TAKEAWAY

「全件を探すバッチ」を「項目ごとの予約イベント+軽量ティッカー」に置き換えると、処理量はデータ総量から切り離され、負荷は平準化し、実行はほぼリアルタイムになる。夜間バッチという運用の常識を、1つ消せる。