第02章:なぜズレるの?二重書き込み(Dual Write)の怖さ 😵💫🧨
今日のゴール 🎯✨
- 「DB保存」と「通知送信(HTTP/キュー)」が別々に成功・失敗しちゃう理由をつかむ🙌
- “成功したと思ったのに実は失敗”が起きるパターンを知る🌀
- Outboxが必要になる“気持ち悪さ”を体感する🤢➡️🙂
1) Dual Write(デュアルライト)ってなに?✍️✍️
Dual Writeは、ざっくり言うとこう👇
- ✅ DBに保存する(OrdersテーブルにINSERTなど)
- ✅ 外に送る(メールAPIへHTTP、メッセージキューへ送信など)
この「2つ」を別々の処理として書いちゃうことだよ📦📩 一見ふつうに見えるんだけど…ここが地雷原💣💥
2) なんで「別世界」なの?🌍🌍

DBは“自分の庭”🏡🌱
DBは「トランザクション」っていう仕組みで、 全部成功 or 全部失敗を作れるよね🔒🍙
- 途中で失敗したらロールバック(なかったことにできる)🧯
- コミットしたら確定(あとから覆らない)✅
でもHTTP/キュー送信は“外の世界”🌏📡
HTTPやキューは、ネットワークの向こう側にある「別のシステム」。 だから、DBのトランザクションで一緒に守れないのが基本🥲
- 送った先が処理したかどうか、こちらが完全に支配できない🙅♀️
- タイムアウトや通信切れで「結果がわからない」が起きる🤷♀️
- “送れた”と“相手が反映した”は別物になりがち😵💫
3) 失敗パターン表(これが地獄の入り口)⛈️😱
| パターン | DB保存 | 送信(HTTP/Queue) | 何が起きる? |
|---|---|---|---|
| A | ✅成功 | ✅成功 | 一見ハッピー🎉(でも後で“実は”もある) |
| B | ✅成功 | ❌失敗 | 注文はあるのに通知が飛んでない😇➡️😱 |
| C | ❌失敗 | ✅成功 | 通知だけ飛んで注文が存在しない👻📩 |
| D | ✅成功 | ❓不明(タイムアウト等) | 重複送信 or 未送信が起きやすい🌀 |
| E | ❓不明(DB側タイムアウト等) | ✅/❌ | DBが確定したか分からず地獄🤢 |
ポイントはここ👇 「DBが成功した!」と「送信が成功した!」が同時に確定しないこと。 これがDual Writeの怖さだよ😵💫🧨
4) “成功したと思ったのに失敗” が起きる理由 🌀🤷♀️
(1) タイムアウトは「失敗」じゃなくて「不明」⏳❓
たとえばHTTP送信でタイムアウトしたとき:
- 実は相手は受け取って処理してた(でも返事が届かなかった)📬✅
- 本当に相手に届いてなかった📭❌
どっちか分からない…これが一番やばい😇
(2) 200 OKは「受付」なだけのこともある👌➡️🤷♀️
相手が「受け付けた(キューに積んだ)」だけで、 本当の処理は後から…って構成、めっちゃあるよね📦➡️⏰
(3) アプリが落ちる場所が悪いとズレる💥🧯
- DB保存の直後にアプリが落ちたら? → 通知が消える😱
- 送信の直後にアプリが落ちたら? → DB更新が消える👻
落ち方の“位置”で世界線が分岐するの、つらすぎる🌀
5) ミニ再現:いちばん素朴な実装が壊れる瞬間 🧪💣
よくある素朴コード(イメージ)😺
「保存してから送る」って、自然に書くとこうなる👇
// ① DBに保存
await SaveOrderAsync(order);
// ② 外部へ通知(HTTP/Queueなど)
await SendNotificationAsync(order);
この2行は、同じ“確実さ”じゃないんだよね😵💫 ①はDBの世界、②はネットワークの世界。
事故シーンA:DBだけ確定して、通知が落ちる 😇➡️😱
await SaveOrderAsync(order); // ✅ここでコミット済み
throw new Exception("ここでアプリが落ちた!💥");
await SendNotificationAsync(order); // 実行されない
結果👇
- 注文はDBにある✅
- でも通知は飛んでない❌
- ユーザーは「買えたの?買えてないの?」状態🤢
事故シーンC:通知だけ飛んで、DBが失敗する 👻📩
順番を入れ替えると今度はこう👇
await SendNotificationAsync(order); // ✅通知だけ先に飛ぶ
await SaveOrderAsync(order); // ❌ここでDBエラー
結果👇
- 通知は飛んだ✅
- でも注文がDBにない❌
- 「注文完了メールが来たのに履歴にない」みたいな怪談になる👻
6) じゃあどう考えればいいの?(初心者の結論)🧠🧡
✅ “1回の処理で2つの世界を完全に同期”は難しい
DBは強いけど、ネットワークは不確実。 だからDual Writeは、放置するとズレが必ず出るタイプの設計になりがち😵💫
✅ “不明”が出る前提で設計する
- タイムアウト=失敗じゃなくて「不明」❓
- 「重複が起きる」前提で受け側も守る(冪等性)🔁➡️1️⃣
7) 2026年の最新ベースライン(ちょいメモ)📝✨
この教材の時代感としては、.NETの最新LTSは **.NET 10(2025-11-11リリース)**で、サポートも長くて安心枠だよ🛡️(~2028年まで)(Microsoft for Developers) 同じタイミングで Visual Studio 2026 と C# 14 も揃ってる感じ!🧰✨(Microsoft for Developers) (ちなみにEF Coreも10系が .NET 10 前提になってたりするよ)(Microsoft Learn)
8) まとめ:この章で覚えておきたい一言 🧷✨
Dual Writeの怖さは、これに尽きる👇
「DB保存」と「外部送信」は、同時に“確定”させにくい別世界 🌍🌏
だから次に必要になるのが、 “送るための情報をまずDBの中に安全に残す”という発想(Outbox)なんだよね📦➡️📩✨
ちいさな確認クイズ(1分)🧠💡
- タイムアウトは、成功?失敗?それとも…?⏳
- 「DB成功・送信失敗」だと、ユーザーには何が起きる?😱
- 「送信成功・DB失敗」だと、どんな怪談になる?👻
(答えはこの章の表に全部あるよ😉📌)