CAP定理と結果整合性入門|分散システムでデータ整合性をどう設計するか

kento_morota 12分で読めます
社内のAI・IT活用、技術がわかる相手に無料相談。 相談 →
目次

「保存した直後に一覧を開いたら、さっき登録したデータが表示されない」「DNSを変えたのに、人によって古いサーバーにつながる」。こうした現象に遭遇して、バグなのか仕様なのか判断に迷った経験はないでしょうか。データベースのレプリカやCDN、DNSといった仕組みを一つでも使っていれば、あなたのシステムはすでに「分散システム」であり、この種の現象は避けて通れません。

この記事では、分散システムの整合性を語るときに必ず出てくるCAP定理を誤解なく整理し、実務でより役に立つPACELCという考え方、強整合と結果整合の使い分け、そしてレプリカ遅延をアプリケーション側で吸収する具体的なパターンまで解説します。読み終える頃には「このデータはどこまでズレを許容してよいか」を自分で判断し、設計に落とし込めるようになります。

CAP定理とは何か|3つを同時に満たせない本当の意味

C・A・Pの定義を正確に押さえる

CAP定理は、分散データストアにおいて次の3つの性質を同時にすべて満たすことはできない、という定理です。

  • 一貫性(Consistency):どのノードに読みに行っても、直近に書き込まれた最新の値が返ること。ここでの一貫性はACIDの「C」とは別物で、「線形化可能性」に近い強い意味です。
  • 可用性(Availability):故障していないノードに対するリクエストが、エラーではなく必ず何らかの応答を返すこと。
  • 分断耐性(Partition tolerance):ノード間のネットワークが途切れ、メッセージが失われても、システム全体が動作を続けられること。

よくある誤解が「CAとCPとAPの3種類からひとつ選ぶ」という説明です。実際には、複数ノードにまたがるシステムでネットワーク分断を「起きないもの」として扱うことはできません。ケーブル抜け、スイッチ故障、AZ間の一時的な遅延増大など、分断は必ず起こります。したがってPは選択肢ではなく前提であり、CAP定理が本当に言っているのは「分断が起きたそのときに、CとAのどちらを諦めるか」だけです。

分断が起きたときに何が起こるか

プライマリとレプリカの2台構成で、両者の間の通信が切れたとします。このときレプリカに読み取りリクエストが来たら、選択肢は2つしかありません。

  1. 「最新かどうか確認できないので応答しない(エラーを返す)」=一貫性を優先し、可用性を捨てる(CP)
  2. 「古いかもしれないが手元の値を返す」=可用性を優先し、一貫性を捨てる(AP)

どちらが正しいかはデータの性質で決まります。銀行残高を古い値で返してしまえば二重引き出しが起こりえますが、記事の閲覧数が数秒古くても誰も困りません。この「データごとに判断する」という視点が、この記事全体を貫く考え方です。

PACELC|分断がないときのトレードオフも考える

CAPだけでは平常時の設計判断ができない

CAP定理は分断中の話しかしていません。しかし実際の運用時間の大半は分断が起きていない平常時です。そこで提案されたのがPACELCという拡張です。「分断(P)があるならAとCのトレードオフ、そうでなければ(Else)レイテンシ(L)と一貫性(C)のトレードオフ」と読みます。

平常時でも、書き込みを全レプリカに同期してから応答すれば一貫性は高まりますが、その分レスポンスは遅くなります。逆に、プライマリに書いた時点で応答し、レプリカへの反映は非同期にすれば速くなりますが、反映されるまでの間は古い値が読める状態になります。これが結果整合性です。

代表的なデータストアの位置づけ

システム分断時平常時典型的な使いどころ
PostgreSQL / MySQL(単一プライマリ+非同期レプリカ)プライマリ側はC寄りレプリカ読み取りは L寄り(遅延あり)業務データ全般
同期レプリケーション設定のRDBCC(書き込みが遅くなる)金融・在庫など
DynamoDB(デフォルトの結果整合読み取り)AL大量アクセスのKVS
DynamoDB(強力な整合性読み取り指定)CC同上で正確さが必要な読み取り
CassandraA(調整可能)L(調整可能)書き込み量が非常に多いログ・イベント

同じ製品でも設定や読み取り方法で位置づけが変わる点に注意してください。「MySQLだから強整合」ではなく、「レプリカから読んでいるなら結果整合」というように、アクセス経路ごとに考える必要があります。レプリケーションの仕組みと遅延の発生メカニズムはデータベースレプリケーション入門で詳しく解説していますので、前提として押さえておいてください。

強整合と結果整合の使い分け|在庫・残高といいね数は何が違うのか

「古い値を見たときに損害が出るか」で分類する

使い分けの判断軸はシンプルで、「一時的に古い値が見えたり、同時更新が競合したりしたときに、取り返しのつかない結果になるか」です。

データ古い値を見た結果推奨
商品在庫数在庫切れなのに注文を受け付け、後で謝罪・キャンセル強整合(プライマリで確定)
口座残高・ポイント残高残高不足なのに決済が通る、二重消費強整合+トランザクション
予約枠・座席ダブルブッキング強整合(行ロックや一意制約)
いいね数・閲覧数数秒間、数が少なく見える結果整合で十分
タイムライン・通知一覧数秒遅れて表示される結果整合で十分
検索インデックス新規登録が検索に出るまで少し遅れる結果整合(遅延を仕様として明示)

ここで重要なのは、「在庫」を扱うシステムでも、在庫の表示は結果整合でよく、在庫の確定(引き当て)だけ強整合にすればよい、という切り分けです。商品一覧の「残りわずか」表示はレプリカやキャッシュから読み、注文確定時だけプライマリでSELECT ... FOR UPDATEや条件付きUPDATEを使って確定します。

-- 注文確定時:在庫が残っている場合だけ減らす(条件付きUPDATE)
UPDATE products
   SET stock = stock - 1
 WHERE id = 1234
   AND stock >= 1;
-- 更新行数が0なら在庫切れとしてエラーを返す

この形にしておけば、同時に複数の注文が来てもDB側で直列化され、在庫がマイナスになることはありません。同時実行の競合がどう起こるかはトランザクション分離レベルとデッドロックの記事が参考になります。

「結果整合」を選ぶときは遅延の上限を決める

結果整合を選ぶ場合も、「いつかは揃う」では設計になりません。「通常は1秒以内、最大でも30秒以内に反映される」のように上限を決め、監視の対象にします。PostgreSQLであれば、レプリカ側で次のように遅延を確認できます。

-- レプリカ側で実行:最後に適用したWALからの経過秒数
SELECT EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp())) AS lag_seconds;

上限を超えたらレプリカからの読み取りを止めてプライマリに切り替える、という制御をアプリやプロキシに入れておくと、「レプリカが数分遅れていることに誰も気づかない」事故を防げます。

身近に起きている結果整合|DNS・CDN・レプリカ

DNSは世界規模の結果整合システム

DNSレコードを変更しても、各地のキャッシュDNSサーバーはTTL(レコードを保持してよい秒数)が切れるまで古い値を返し続けます。つまりDNSは「可用性とレイテンシを最優先し、一貫性はTTL単位で妥協する」設計です。サーバー移転の前日にTTLを短くしておくのは、この結果整合の収束時間を意図的に縮める操作にほかなりません。仕組みの詳細はDNSの仕組みをわかりやすく解説をご覧ください。

CDNのキャッシュも同じ構造

CDNはオリジンサーバーのコンテンツをエッジに複製して配信します。オリジンを更新しても、エッジのキャッシュが切れるか明示的にパージするまでは古いコンテンツが配信されます。「更新したのに一部のユーザーだけ古いページが見える」という問い合わせは、まさに結果整合の収束途中の現象です。キャッシュキーの設計やパージの運用はCDNとは?仕組みとメリットで解説しています。

トラブル事例:保存直後に一覧に出ない

症状:管理画面で商品を登録し、完了後に商品一覧へリダイレクトすると、登録したはずの商品が一覧に出ない。リロードすると出る。再現は不定期。

原因:書き込みはプライマリ、一覧の読み取りはレプリカという振り分けをしていた。レプリケーション遅延が通常は数十ミリ秒だったため気づかなかったが、バッチ処理でプライマリの負荷が上がる時間帯に遅延が1〜2秒に伸び、リダイレクト後の読み取りがレプリカ反映前に実行されていた。

対処:書き込み直後の同一ユーザーの読み取りはプライマリに向ける「読み取り自分の書き込み(Read-your-writes)」を実装。あわせてレプリケーション遅延をメトリクスとして監視し、閾値超過でアラートを出すようにした。

アプリケーションで結果整合を吸収する設計パターン

読み取り自分の書き込み(Read-your-writes)

もっとも実用的なのが、「書き込んだ本人には、しばらくの間プライマリから読ませる」方法です。書き込み時に「最終書き込み時刻」をセッションやCookieに記録し、それから一定時間(レプリカ遅延の上限より長い時間)はその利用者の読み取りをプライマリに向けます。

# Django風の擬似コード:書き込み後N秒はプライマリから読む
STICKY_SECONDS = 5

def after_write(request):
    request.session["last_write_at"] = time.time()

def choose_db(request):
    last = request.session.get("last_write_at", 0)
    if time.time() - last < STICKY_SECONDS:
        return "primary"
    return "replica"

この方式なら、他の利用者のアクセスはレプリカに分散したまま、本人だけが常に最新の値を見られます。ほとんどのWebアプリでは、これだけで「保存したのに出ない」問題は解消します。

単調読み取りとバージョン番号

複数のレプリカにランダムに振り分けていると、「一度は最新を見たのに、次のリクエストで別のレプリカに当たって過去に戻る」現象が起こります。これを防ぐには、同じ利用者を同じレプリカに固定する(セッションアフィニティ)か、データにバージョン番号や更新時刻を持たせ、クライアントが「これ以上新しいものだけ受け付ける」と指定します。APIでupdated_atやETagを返し、クライアント側で保持済みのものより古い応答を捨てるのが典型的な実装です。

UIで吸収する(楽観的更新)

いいねボタンを押したら、サーバーの応答を待たずに画面上の数値を即座に+1する「楽観的更新」も、結果整合を前提とした設計です。サーバー側の集計が数秒遅れていても、本人の画面には反映済みに見えます。失敗したときだけ元に戻せばよいので、実装コストのわりに体感品質が大きく上がります。

実務での判断手順

  1. データを棚卸しする:テーブルや画面ごとに「古い値を見せた場合の損害」を書き出す。
  2. 確定操作と表示を分ける:損害が出る操作(在庫引き当て、決済、予約確定)はプライマリで条件付きUPDATEや一意制約により直列化する。表示はレプリカ・キャッシュでよい。
  3. 結果整合の上限を決める:「最大何秒のズレを許容するか」を決め、レプリカ遅延・キャッシュTTLをそれに合わせ、監視する。
  4. 本人向けの読み取りだけプライマリに寄せる:Read-your-writesを実装し、「保存したのに出ない」を防ぐ。
  5. 仕様として明文化する:「検索結果への反映は最大1分」のように、ズレを仕様書とUIに明示し、問い合わせを減らす。

大切なのは、システム全体を一律に「強整合にする」でも「結果整合にする」でもなく、データと操作の単位で選ぶことです。全体を強整合にするとレイテンシとコストが跳ね上がり、全体を結果整合にすると業務上許されない事故が起きます。

まとめ

CAP定理の本質は「分断は必ず起きる前提で、そのときCとAのどちらを取るか」であり、平常時のレイテンシと一貫性のトレードオフを含めて考えるのがPACELCです。在庫や残高のような「古い値で損害が出る」データは強整合で確定させ、いいね数や一覧表示のような「少し古くても困らない」データは結果整合で速さとスケーラビリティを取る。そしてRead-your-writesのようなパターンで、利用者本人が違和感を覚えない体験を作る。この3点が実務での設計指針になります。

まずは自分のシステムで「レプリカ・キャッシュ・CDN・DNS」のどこに結果整合が潜んでいるかを洗い出し、遅延の上限を決めて監視するところから始めてみてください。データ整合性を踏まえたアーキテクチャ設計やレプリカ構成の見直しにお困りの際は、Harmonic Societyのシステム開発・インフラ支援にご相談ください。

#CAP定理#結果整合性#分散システム#設計

Harmonic Society

この記事の内容、自社の業務でも活かせそうですか?

ローカルLLM・AI・クラウドなどの技術導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK。まずは30分の無料相談から。売り込みはしません。

共有:
無料メルマガ

週1回、最新の技術記事をお届け

AI・クラウド・開発の最新記事を毎週月曜にメールでお届けします。登録は無料、いつでも解除できます。

プライバシーポリシーに基づき管理します

関連記事

Related / 9 articles

  1. プログラミング

    DDoS攻撃の仕組みと対策入門|レイヤー別の防御とCDN・クラウドの活用

    DDoS攻撃をボリューム型・プロトコル型・アプリ層に分けて仕組みを解説し、自前サーバーで防げない理由、CloudflareやAWS Shieldの標準防御、オリジンIPの隠し方、レートリミットとBot対策、攻撃を受けたときの初動、費用が跳ね上がるDenial of Walletへの備えまでわかります。

  2. プログラミング

    WAFとは?仕組み・導入パターン・誤検知対策|Webアプリを攻撃から守る実践ガイド

    WAFがファイアウォールやIDSと何が違うのか、シグネチャとマネージドルールの仕組み、Cloudflare WAF・AWS WAF・ModSecurityの比較、フォーム送信がブロックされる誤検知の調査と例外設定、ログ監視、WAFが代替できないことまで実践的に解説します。

  3. プログラミング

    セキュリティヘッダー入門|CSP・HSTS・X-Frame-Optionsの設定と効果を実践解説

    CSP・HSTS・X-Frame-Options・X-Content-Type-Optionsなど主要セキュリティヘッダーが防ぐ攻撃と、CSPのReport-Onlyからの段階導入、nonce/hash、HSTS preloadの不可逆リスク、Nginx・Next.js・Astroでの設定例、確認方法を解説します。

  4. プログラミング

    クラウドの通信費(Egress)入門|データ転送量課金の仕組みと転送コストを抑える設計

    クラウドの「受信無料・送信有料」の原則、AZ間・リージョン間・インターネット向けの単価差、NATゲートウェイ処理料の罠、CDNで転送量を減らす方法、バックアップやログ転送の見落とし、請求書で転送料を特定する手順を解説。想定外の請求を防げます。

  5. プログラミング

    秘密情報をGitに入れない仕組み|.gitignore・git-secrets・履歴から漏れた鍵の削除

    APIキーや.envをGitにコミットしてしまう典型経路と、.gitignore・.env.exampleの運用、pre-commitでのgitleaks検知、GitHub secret scanningの活用、漏れた鍵の無効化と履歴書き換え(git filter-repo)の手順を解説。仕組みで再発を防げます。

  6. プログラミング

    開発・ステージング・本番環境の分離設計|環境差分をなくす構成とアクセス制御

    開発・ステージング・本番それぞれの目的と、構成をコードで揃える方法、環境別の設定注入、本番データを使わないテストデータ戦略、ステージングの保護(Basic認証・IP制限・noindex)、コストを抑える運用までを解説。環境差分による本番障害を防げます。

  7. プログラミング

    ngrok・Cloudflare Tunnelでローカルを公開|Webhook開発とデモ環境の作り方

    NAT内のローカル環境にStripeやLINEのWebhookを届けるトンネリングの仕組みを解説。ngrok・Cloudflare Tunnel・localtunnelの比較、固定ドメインと認証、リクエスト検査、公開時のセキュリティ、自宅サーバー公開への応用までわかります。

  8. プログラミング

    ローカル開発環境のHTTPS化|mkcert・hostsファイル・自己署名証明書の正しい使い方

    ローカル開発をHTTPS前提にすべき理由(Secure Cookie・Service Worker・OAuth)と、mkcertでローカルCAを作りhostsで独自ドメインを割り当ててVite・Next.js・Dockerで使う手順を解説。証明書警告を無視する癖の危険も理解できます。

  9. プログラミング

    localhost・0.0.0.0・127.0.0.1の違い|ポートとUnixソケットを理解して「つながらない」を解決

    localhost・127.0.0.1・0.0.0.0の意味の違い、Dockerで外から接続できない原因、host.docker.internal、ポート競合の調べ方、Unixソケットの利点と権限、1024未満ポートの制約を解説。「つながらない」を仕組みから解決できます。

Harmonic Society

「読んで終わり」にせず、自社の業務で試してみませんか?

AI・ローカルLLM・クラウドの導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK・売り込みなし。

無料・30分・オンラインOK|1営業日以内に返信します