Skip to main content

第02章:なぜズレるの?二重書き込み(Dual Write)の怖さ 😵‍💫🧨

今日のゴール 🎯✨

  • 「DB保存」と「通知送信(HTTP/キュー)」が別々に成功・失敗しちゃう理由をつかむ🙌
  • “成功したと思ったのに実は失敗”が起きるパターンを知る🌀
  • Outboxが必要になる“気持ち悪さ”を体感する🤢➡️🙂

1) Dual Write(デュアルライト)ってなに?✍️✍️

Dual Writeは、ざっくり言うとこう👇

  • DBに保存する(OrdersテーブルにINSERTなど)
  • 外に送る(メールAPIへHTTP、メッセージキューへ送信など)

この「2つ」を別々の処理として書いちゃうことだよ📦📩 一見ふつうに見えるんだけど…ここが地雷原💣💥


2) なんで「別世界」なの?🌍🌍

Two Worlds

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 2026C# 14 も揃ってる感じ!🧰✨(Microsoft for Developers) (ちなみにEF Coreも10系が .NET 10 前提になってたりするよ)(Microsoft Learn)


8) まとめ:この章で覚えておきたい一言 🧷✨

Dual Writeの怖さは、これに尽きる👇

「DB保存」と「外部送信」は、同時に“確定”させにくい別世界 🌍🌏

だから次に必要になるのが、 “送るための情報をまずDBの中に安全に残す”という発想(Outbox)なんだよね📦➡️📩✨


ちいさな確認クイズ(1分)🧠💡

  1. タイムアウトは、成功?失敗?それとも…?⏳
  2. 「DB成功・送信失敗」だと、ユーザーには何が起きる?😱
  3. 「送信成功・DB失敗」だと、どんな怪談になる?👻

(答えはこの章の表に全部あるよ😉📌)