第25章:スナップショット概念(なぜ必要?)📸⚡
1. 今日のゴール🎯✨
この章が終わったら、こんなことが「わかる&説明できる」ようになります😊🫶
- スナップショットって何?(一言で言える)📸
- どうして必要になるの?(つらみの正体がわかる)😵💫
- いつ取るのが多い?(代表パターンを知る)🗓️
- 何を取る? 何を取らない?(事故らない)🚧
2. スナップショットって何?📸
**スナップショット =「ある時点の集約(Aggregate)の状態を丸ごと保存した“途中セーブ”」**です🎮✨ イベントソーシングでは、本来 イベント列を最初から順に適用(Apply)して現在の状態を作るよね🔁

でもイベントが増えると、それがだんだん重くなってきます🐢💦
スナップショットがあると、こうできます👇
- 最新スナップショットまで一気にワープ🚀
- そこから先のイベントだけをApplyすればOK✅
※大事:スナップショットは「真実」じゃなくて、あくまで高速化のためのキャッシュだよ🧊✨(イベントが真実)(martinfowler.com)
3. 何がつらいと、スナップショットが欲しくなるの?😵💫
3.1 Rehydrate(復元)が遅くなる🐢
イベントが少ないうちは、復元は余裕です😊 でも、運用が進むとこうなる👇
- 1集約につきイベントが数百〜数万…📈
- コマンド処理のたびに Rehydrate(Load→Decide→Append の Load)🔁
- そのたびにイベントを何百回もApply…😇💥
結果:
- 体感がモッサリする😿
- テストが遅くなる🧪🐢
- コマンド処理がタイムアウトしやすくなる⏱️💦
スナップショットは 「集約の復元コスト」を下げるための手段だよ📸⚡(EventSourcingDB)
3.2 でも、最初から入れなくていい🙅♀️
スナップショットは“性能最適化”なので、基本はこう考えるのが安全です👇
- 計測して、必要になったら入れる⏱️✅
- 先に入れると、設計がややこしくなりがち🧩💦
「最初のルール:やらない」「次のルール:ほんとに必要か確認する」みたいな空気感、現場でもよくあります😂(Stack Overflow)
4. スナップショットは“何を”保存するの?🍱🏷️
**保存するもの(おすすめ)**👇
- 集約の状態(例:Cartの中身、合計金額、ステータスなど)🛒
- そのスナップショットが対応する **version(どこまでのイベントを反映したか)**🔢
- 作成日時などのメタ情報🕒
**保存しないほうがいいもの(ありがち事故)**👇
-
画面表示用に都合の良い形に整形しすぎたもの🖥️💦
- それは Projection の役目になりやすいよ🔎
-
「本来イベントから導けるのに、別ルールで作った値」🤯
- いつかズレる🧨
5. スナップショットは“何に効く”の?何に効かない?🎯
5.1 効く✅:集約の復元(Rehydrate)
コマンド処理で集約をロードするとき、速くなる📮⚡
- スナップショット + 追加分イベントだけApply → Apply回数が減る✨
5.2 効かない❌:Projectionの再構築(リプレイ)
「Projectionを最初から作り直す」時は、基本ぜんぶのイベントを処理します🔁 スナップショットは Projectionの再構築を速くする道具じゃない(ここ勘違い多い!)😵💫(EventSourcingDB)
6. どこに保存するの?📦
よくある保存先は2つ👇
パターンA:イベントストアとは別の場所に置く(“別テーブル/別ストア/別ストリーム”)📦✨
- イベントはイベントのまま、純度を保つ🧼
- スナップショットは “キャッシュ” として管理しやすい🧊
「スナップショットはイベントとは別に保持する」方針は一般的におすすめされがちだよ🗃️(AlgoMaster)
パターンB:スナップショットも“イベントっぽく”扱って同じ仕組みで保存する📜
- 「スナップショット専用のイベント型」を作って、同じAppendの仕組みで保存する感じ🎬
- 例:
fooのイベントとは別にfoo-snapshotストリームを作る、みたいな運用例もあります📼(Stack Overflow)
7. いつ取るの?(代表パターン)🗓️📸
“正解”は1つじゃないけど、よくあるのはこのへん👇
- N件ごと(例:100件ごと)🔢
- 実装しやすい
- 最悪でも「最後の100件だけApply」になる
- 目安として 100〜500件ごとが語られることが多いよ📈(AlgoMaster)
- 時間ごと(例:1時間ごと、1日1回)🕒
- 長期運用で扱いやすい
- 日次バッチみたいなノリにも合う☀️🌙(martinfowler.com)
- **読み込み時(On read)**📖
- 「最後のスナップショットからイベントが増えすぎてたら作る」
- ただし読み込みが重いタイミングで追加作業が入るので注意⚠️(AlgoMaster)
- **特定コマンド時(On command)**📮
- 「重い処理の後だけ取る」みたいなルール
- ドメインに意味があるタイミングに合わせやすい🎀(EventSourcingDB)
8. “スナップショットあり”の復元の流れ(イメージ)🔁✨
スナップショットなし👇
- イベントを最初から読む📜
- ぜんぶApplyする🔁
- 現在の状態ができる✅
スナップショットあり👇
- 最新スナップショットを読む📸
- そのスナップショットの version より後のイベントだけ読む📜
- それだけApplyする🔁
- 現在の状態ができる✅
9. ちいさなTypeScript例(超イメージ)🧸💻
※ここでは「概念が伝わる」ことが目的だよ📸✨(実装は次章でガッツリ!)
type Version = number;
type CartState = {
items: { sku: string; qty: number }[];
// ...他にも合計金額など
};
type Snapshot<T> = {
streamId: string;
version: Version; // ここまでのイベントを反映済み
state: T; // 集約の状態(途中セーブ)
createdAt: string; // ISO文字列など
};
type EventEnvelope = {
streamId: string;
version: Version;
type: string;
data: unknown;
};
async function rehydrateCart(
streamId: string,
loadLatestSnapshot: (streamId: string) => Promise<Snapshot<CartState> | null>,
loadEventsAfter: (streamId: string, afterVersion: Version) => Promise<EventEnvelope[]>,
apply: (state: CartState, e: EventEnvelope) => CartState,
emptyState: () => CartState
): Promise<{ state: CartState; version: Version }> {
const snap = await loadLatestSnapshot(streamId);
const baseState = snap?.state ?? emptyState();
const baseVersion = snap?.version ?? 0;
const events = await loadEventsAfter(streamId, baseVersion);
const finalState = events.reduce((s, e) => apply(s, e), baseState);
const finalVersion = events.length ? events[events.length - 1].version : baseVersion;
return { state: finalState, version: finalVersion };
}
ポイントはこれだけ覚えてね😊🫶
- スナップショットには state と version が必須級📸🔢
- そこから後ろのイベントだけApplyすればいい🔁✨
10. よくある落とし穴集🧨(ここ超大事!)
落とし穴①:スナップショットを最初から入れて複雑化😵💫
- まずは計測→必要なら導入が安全⏱️✅(EventSourcingDB)
落とし穴②:Projectionが速くなると思い込む😇
- 集約の復元が速くなるもの
- Projection再構築のショートカットにはならない(原則)🔎❌(EventSourcingDB)
落とし穴③:スナップショットが古くてズレる🧊💥
- version を必ず持たせて「どこまで反映済みか」を明確にする🔢
- スナップショット生成ロジックが壊れても、イベントから作り直せる設計にする🔁✨(martinfowler.com)
落とし穴④:スナップショットを頻繁に取りすぎる📸📸📸
- 書き込みが増えて逆に重くなることもある🧱💦
- “ほどほど”に(N件ごと、時間ごと…)が無難🌸(AlgoMaster)
11. ミニ演習(手と頭でわかる)✍️🧠📸
演習A:Apply回数を数えて「つらみ」を体感する😵💫
- 集約のイベントを 100件ある想定にする📜
- 「復元に必要なApply回数 = 100回」って数える🔁
- スナップショットを version=80 に置いたとする📸
- すると Apply回数は 残り20回✨
→ これだけで「効いてる!」ってなるよね😊⚡
演習B:スナップショットの“タイミング”を考える🗓️
次の質問に答えてみよう👇
- 100件ごと? 200件ごと? それとも時間ごと?🤔
- 「コマンドが多い集約」だけ取る? 全部取る?📮
- “取るコスト”もあるけど、どこで得する?💰
12. AI活用(Copilot / Codexでやると超はかどる)🤖✨
そのまま貼って使えるプロンプト例だよ📌💕
- スナップショット戦略案を出してもらう📸
- 「この集約は更新頻度が高いです。N件ごと/時間ごとの候補と、メリデメ、採用基準(計測観点)を箇条書きで出して」
- 落とし穴レビュー🧯
- 「スナップショット導入で起きがちなバグや運用事故を列挙して、TypeScript実装での予防策も添えて」
- 計測の設計(次章につながる)⏱️
- 「rehydrateの処理時間とApply回数を計測する仕組みを提案して。console出力でOK。計測ポイントも教えて」
まとめ📌✨
- スナップショットは **途中セーブ(高速化のキャッシュ)**📸
- 効くのは 集約の復元(Rehydrate)⚡
- Projection再構築を速くするものじゃない🔎❌(EventSourcingDB)
- まずは計測して、必要になったら導入が安全😊⏱️(EventSourcingDB)
次の章では、このスナップショットを 最小の形で実装して「ほんとに速くなる!」を体験します📸🧪🚀