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

第25章:スナップショット概念(なぜ必要?)📸⚡

この章でできるようになること 🎯✨

  • スナップショットが 何のためにあるのか を説明できる📸
  • 「いつ入れるべきか/まだ要らないか」を 判断できる
  • スナップショットの 取り方のパターン(N件ごと・時間ごと等)を選べる🧠
  • 次章(最小実装)に向けて、設計メモを作れる📝

1) そもそもスナップショットって何?📸

スナップショットの概念

イベントソーシングでは、集約(Aggregate)の現在状態は イベント列を最初から順に Apply して復元(Rehydrate) して作りますよね🔁

でも、イベントが増えると…

  • 復元のたびに 読むイベント数が増える 📚
  • Apply回数が増えて CPU時間も増える 🧠💦

そこで登場するのが スナップショット(Snapshot)です✨ スナップショットは「あるバージョン(version)時点の 集約の状態を丸ごと保存 したもの」です📦 復元するときは「最新スナップショット + そこから後のイベントだけ」を当てればOKになります✅

重要:スナップショットは “設計の主役” じゃなくて 性能最適化の道具 です📌 まずはシンプルに作って、必要になったら入れる、が基本! (martendb.io)


2) スナップショットが欲しくなる「あるある」😵‍💫

次のどれかが起きると、スナップショットを検討する価値が出ます👇

A. 1つのストリームにイベントが多すぎる📚📚📚

たとえば「ユーザー設定」「買い物カート」「サブスク契約」みたいに、長期で更新され続ける集約はイベントが積もりがち。

B. コマンド処理が遅くなってきた🐢💦

コマンド処理はだいたい Load(復元)→ Decide(ルール判定)→ Append(保存) なので、Load が遅いと全体が遅くなります⏱️

C. “高い頻度で復元される” 集約がある🔁

同じ集約が短時間に何度も更新されると、復元コストが効いてきます💥


3) まず覚える「大原則」3つ 🧷✅

原則①:スナップショットは“最初から入れない”🙅‍♀️

スナップショットは便利だけど、書き込みが増えるし、運用が複雑になります🧯 だから「必要になるまで入れない」が推奨されがちです。 (martendb.io)

原則②:毎回スナップショットを取らない🙅‍♀️📸

イベントを1つ書くたびにスナップショットも書くと、 「速くするための機能」が「遅くする原因」になりがち…😇 一般には 間隔(一定イベント数ごと、または特定イベント発生時など)で取ります。 (martendb.io)

原則③:スナップショットの前に“構造”を疑う🧠🔍

イベントが増えすぎる原因が「集約がデカすぎ」なら、 スナップショットより先に 集約の境界見直し で解決できることもあります⚖️ (EventSourcingDB)


4) どのタイミングで取る?代表パターン4つ 📈📸

パターンA:N件ごと(例:100件ごと)🔢

  • いちばん分かりやすい王道✨
  • 「スナップショットを読む + 最大N件のイベントだけ再生」で復元できる
  • Nは、集約の重さ次第(数百ごと、などの経験談もあります) (Kurrent - event-native data platform)

向いてる:イベント数が素直に増え続ける集約 弱点:Nの根拠が薄いと、調整が迷子になる🌀


パターンB:時間ごと(例:1日1回、1時間1回)🕒

  • “増え方” が一定ではない場合に便利
  • バッチ的に取れる(夜間に取るなど)🌙

向いてる:日次・週次の更新が多い、運用都合がある 弱点:更新が集中する時間帯だと、その時間の復元が重いことも💦


パターンC:特定イベントが来たら取る(節目)🏁

例:

  • OrderPlaced の直後だけ
  • SubscriptionRenewed の直後だけ

「この節目の状態が重要」「ここからの復元が重い」みたいなときに効きます✨ Martenのドキュメントでも「一定間隔 or 特定イベントタイプで」がよくある、と説明されています。 (martendb.io)


パターンD:混合(N件ごと + 節目)🧩

  • 通常はN件ごと
  • でも “特別に重いイベント” が来たら即スナップショット

向いてる:イベントの種類で重さがバラバラな集約


5) スナップショットに入れるもの(最低限)🍱🏷️

スナップショットは「状態の保存」なので、最低限これが必要です👇

  • AggregateId(どの集約?)
  • Version(どのイベントまで反映済み?)🔢
  • State(集約の状態本体)📦
  • CreatedAt(いつ作った?)🕒(あると便利)

コツ:スナップショットの中身は「復元に必要な状態だけ」に絞ると安全です✅ “画面表示用の情報” まで詰めると、変更に弱くなりがち😵‍💫

さらに設計としては「スナップショットも専用タイプとして扱う(イベント扱いに近い形)」みたいな方針もあります。 (EventSourcingDB)


6) よくある事故パターン(ここ超大事)🧯😱

事故①:Versionズレ(これ一番多い)💥

スナップショットが Version=120 なのに、 後続イベントを 121 から適用しないといけないのに間違えて 120 から適用… → 二重適用で壊れます😇

✅ 対策:

  • 「スナップショットは versionまで適用済み」を厳密に定義
  • 復元コードで snapshot.Version + 1 から読むように固定

事故②:スナップショットが壊れてる(でもイベントは正しい)🧨

スナップショットは “最適化データ” なので、壊れると厄介💦

✅ 対策:

  • 「スナップショットが読めなければ、イベント最初から復元する」フォールバックを用意
  • スナップショットは再生成できる設計にする🔁

  • スナップショットは再生成できる設計にする🔁

事故③:書き込み増えすぎ(速くするはずが遅い)📸📸📸

スナップショット頻度が高いと、IOが増えて逆効果になりがち。 「毎回は取らず、間隔で」が基本です。 (martendb.io)


事故④:保存場所の設計が微妙で運用地獄🌀

例として EventStoreDB まわりでは「スナップショット用に別ストリームを作り、最大1件だけ保持する」みたいなやり方が紹介されることがあります(最新だけ残す)。 (ITNext) (※あなたの自作EventStoreでも、同じ考え方でテーブルやキー設計ができます👌)


7) “入れるかどうか”判断チェックリスト ✅📝

次の質問にYESが増えたら、スナップショット検討です📸

  • 1ストリームのイベント数が 数百〜数千 になってきた
  • コマンド処理の遅さが 体感できる(or 計測で出た)⏱️
  • 同じ集約を短時間に何度も復元している🔁
  • でも集約分割(境界見直し)では解決しにくい⚖️
  • スナップショット導入の 追加書き込み を許容できる✍️

「計測してから入れる」がいちばん強いです💪 .NET 10 でも診断・観測が強化されていて、計測の重要性はますます上がってます。 (Microsoft Learn)


8) ミニ演習:イベント100件の“重さ”を体感しよう 😵‍💫➡️😲

この章は概念回なので、実装は次章でやります! でも「遅さの正体」を体感すると理解が爆上がりします💡

やること(手順)🧪

  1. すでに作った集約に、イベントを 100件 連続でAppend(テストでOK)
  2. Rehydrate10回 ループして Stopwatch で時間を計る
  3. ついでに Apply の中で「ちょい重い処理」を わざと追加(例:簡単なループ)
  4. 「イベント数が増えるほど復元が重くなる」を体感する

計測の最小コード例(雰囲気)⏱️

using System.Diagnostics;

var sw = Stopwatch.StartNew();

for (int i = 0; i < 10; i++)
{
var agg = await repository.LoadAsync(id); // ここがイベント再生(復元)
}

sw.Stop();
Console.WriteLine($"rehydrate x10: {sw.ElapsedMilliseconds}ms");

✅ 観察ポイント👀

  • イベント数が増えると、復元が比例して重くなる
  • Applyが重いと、さらに効く
  • だから「最新状態を途中保存できたら嬉しい」ってなる📸✨

9) AI活用(Copilot / Codex)プロンプト例 🤖💬✨

目的:スナップショット導入の“設計メモ”を作る📝

  • 「N件ごと」「時間ごと」「節目イベント」の候補を3つ出して
  • それぞれのメリデメと、採用条件(こうなったら入れる)を書いて
  • うちの題材(カート/ToDo等)に合わせて例もつけて

目的:次章の実装に向けた“雛形”を作る🧱

  • Snapshot レコード(AggregateId, Version, State, CreatedAt)を定義して
  • Load で「スナップショット読んで、残りイベントだけ読む」流れを疑似コードで

AIに出してもらったら、必ずチェックするのはここ👇

  • versionの扱い(+1の読み開始)
  • “毎回スナップショット保存” になってないか
  • Stateに詰めすぎてないか(表示用まで入れてない?)

まとめ 🎀

  • スナップショットは 復元(Rehydrate)を速くするための最適化 📸⚡ (martendb.io)
  • まずは入れずに作って、必要になったら入れるのが基本✅ (martendb.io)
  • 取り方は「N件ごと」「時間ごと」「節目イベント」が代表🧠 (Kurrent - event-native data platform)
  • 最大の事故は versionズレ!ここは設計でガチガチに固定する🔒

次章チラ見せ 👀✨

次の第26章では、いま学んだ概念を使って

「最新スナップショット + 残りイベント」 で復元する最小実装を作ります📸🧪