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

無停止で「未来」をリリースする — 時刻バージョニング型アーキテクチャ

価格改定もキャンペーンも「未来の日付」で予約し、当日はシステムを止めずに自動で切り替える。通販基盤 Vegas を支える中核設計。

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

「毎月1日 0:00、価格改定を一斉反映。深夜にメンテ画面を出し、切替を確認し、問題があれば緊張しながら切り戻す」——通販システムの運用で、こんな夜を過ごしたことはないでしょうか。

ECや通販の裏側では、価格・キャンペーン・送料・商品構成といった「設定」が絶えず入れ替わります。しかもその多くは「特定の日時から一斉に切り替えたい」という要件を持ちます。従来これは、デプロイ作業・メンテナンス停止・人手の確認とセットになりがちでした。Vegas はこの問題を、アプリケーションのバージョンそのものを「時刻」で管理するという発想で解いています。

The Problem「当日切替」はなぜ難しいのか

「7/1 0:00 から新価格」を素朴に実装すると、次のいずれかになりがちです。

いずれも「いつの時点の正解か」をシステムが一級の概念として扱っていないことに起因します。ならば、そこを設計の中心に据えればよい——というのが出発点です。

The Ideaすべてに「有効期間」を持たせる

Vegas では、ルーティングと設定を格納する台帳の全レコードに OpenDate(有効開始)CloseDate(有効終了) を持たせます。リクエストのたびに previewDate(基準時刻) を受け取り、その時刻に有効なレコードを解決します。

Core Rule

OpenDate ≤ previewDate < CloseDate を満たすレコードのうち、OpenDate が最大(=最新)のものを採用する。

通常のリクエストでは previewDate = 現在時刻。つまり「今の正解」が自動的に選ばれます。デプロイとは、新しい有効期間を持つレコードを1件追加するだけの操作になります。

Resolutionリクエストはこう解決される

リクエスト path: /a/order-confirm previewDate: 20260703… ゲートウェイ ルート解決 ① 先に参照 ② MISS時のみ Valkey キャッシュ ZREVRANGEBYSCORE ルート台帳 GSI: Path × OpenDate 実体API …_v20260701
図1:ルート解決フロー。まず Valkey を引き、ミス時のみ台帳へフォールバックする。

台帳への問い合わせは、パスを固定し「OpenDate ≤ previewDate」で範囲検索するだけ。GSIを Path × OpenDate で張れば、1クエリで完結します。

route-resolver.mjsDynamoDB Query
// path と previewDate から「その時刻に有効な実体」を解決
const res = await ddb.query({
  IndexName: "Path-OpenDate-index",
  KeyConditionExpression: "#path = :p AND OpenDate <= :t",
  FilterExpression:       ":t < CloseDate",
  ExpressionAttributeValues: { ":p": rawPath, ":t": previewDate },
});
// 候補が複数あれば OpenDate 最大(=最新の有効版)を採用
const target = res.Items.sort((a, b) => b.OpenDate - a.OpenDate)[0].Target;

Timeline「時刻の窓」でバージョンを選ぶ

6/017/019/01 v20260601(旧価格) v20260701(新価格)★採用 v20260901(予約中) previewDate = 今日(7/03)
図2:有効期間タイムライン。針が入っている窓が採用される。9/01以降は予約版に自動で切り替わる。

Preview「未来」を本番に影響なく確認する

最大の利点はプレビューです。previewDate に未来日を渡すだけで、まだ公開していない設定で画面や見積を確認できます。本番データは1バイトも変わりません——「未来の値」は台帳の中で有効期間を待っているだけだからです。「9/1のキャンペーン価格を8月中に営業と画面で確認する」が、本番リスクゼロで日常的に行えます。

PerformanceValkey で「その時刻の最新」を1発で引く

解決は毎リクエストで走るため、台帳照会の前段に Valkey のキャッシュを置き、パスごとに「OpenDate をスコアにした Sorted Set」で保持します。「previewDate 以前で最大の OpenDate を持つ版」は、逆順レンジ取得1コマンドで O(log N) で求まります。

valkey-lookup.mjsRedis / Valkey
// スコア=OpenDate。previewDate 以下で最大のメンバ(=最新の有効版)を1件取得
const hit = await valkey.sendCommand([
  "ZREVRANGEBYSCORE", `route:${rawPath}`,
  String(previewDate), "-inf", "LIMIT", "0", "1",
]);
// hit があれば即採用。なければ台帳へフォールバックし、結果をキャッシュ。

キャッシュはあくまで高速パスで、正は台帳。ミス時フォールバックとタイムアウト付き参照により、キャッシュ障害時も「遅くなるが止まらない」を担保します。

Trade-offs設計上の割り切り

TAKEAWAY

「いつの時点の正解か」を previewDate という一級の概念に据えるだけで、デプロイ・切替・切り戻し・プレビューが、すべて「時間軸上のデータ操作」に還元される。無停止リリースは特別な仕組みではなく、データモデルの自然な帰結になる。