MongoDBの一貫性を理解する:開発者のためのBASEモデル解説

この詳細ガイドでMongoDBの一貫性モデルを理解しましょう。BASEモデルがどのようにMongoDBのスケーラビリティを推進するのか、従来のACIDデータベースと比較しながら学びます。結果整合性を解明し、MongoDBの柔軟な読み取り保証と書き込み保証を探求し、データベースを最適なパフォーマンスとデータ整合性に調整するための実践例を提供します。分散型NoSQLプラットフォームで堅牢で高性能なアプリケーションを構築するために、これらの選択がなぜ重要なのかを理解しましょう。

MongoDBの一貫性を理解する:開発者のためのBASEモデル解説

MongoDBの一貫性は、「MongoDBは結果整合性を持つ」という一文に単純化されるため混乱を招きます。これはあまりにも大雑把で役に立ちません。MongoDBのレプリカセットは、書き込み保証、読み取り保証、読み取り設定、およびフェイルオーバーが発生しているかどうかに応じて、一部の操作では強い整合性を、他の操作では古いデータの読み取りを提供できます。

PostgreSQLやMySQLから移行する場合、最大の適応点は、一貫性がアプリケーション全体で固定された設定ではないことです。各パスに必要な保証を選択します。チェックアウトフロー、通知フィード、分析ダッシュボードは、すべて同じ鮮度や耐久性を必要とするわけではありません。

ACID vs. BASE:一貫性への2つのアプローチ

MongoDBのモデルに飛び込む前に、データベースの一貫性に関する2つの主要なパラダイム、ACIDとBASEを理解することが役立ちます。

ACID特性(従来のRDBMS)

PostgreSQLやMySQLなどの従来のリレーショナルデータベース管理システム(RDBMS)は、通常、ACID特性に準拠しており、特にトランザクションワークロードにおいてデータの信頼性を保証します。ACIDは次の略です:

  • 原子性(Atomicity):各トランザクションは単一の不可分な単位として扱われます。完全に完了する(コミットする)か、まったく発生しない(ロールバックする)かのいずれかです。部分的なトランザクションはありません。
  • 一貫性(Consistency):トランザクションはデータベースをある有効な状態から別の有効な状態に移行させます。データベースに書き込まれるデータは、定義されたすべてのルールと制約に従って有効であることを保証します。
  • 独立性(Isolation):同時実行トランザクションは分離して実行され、あたかも順次実行されているかのように見えます。同時実行トランザクションの結果は、それらが1つずつ実行された場合と同じです。
  • 永続性(Durability):トランザクションがコミットされると、停電、クラッシュ、その他のシステム障害が発生しても、コミットされた状態が維持されます。変更は永続的に保存されます。

ACIDは強い整合性を保証するため、金融取引など、厳格なデータ整合性を必要とするアプリケーションに最適です。

BASE特性(MongoDBなどのNoSQLデータベース)

対照的に、MongoDBを含む多くのNoSQLデータベースは、即時一貫性よりも可用性と分断耐性を優先し、多くの場合BASEモデルに準拠しています。BASEは次の略です:

  • 基本的に利用可能(Basically Available):システムは可用性を保証します。つまり、データの最新バージョンを保証できなくても、あらゆるリクエストに応答します。
  • ソフトステート(Soft State):システムの状態は、入力がなくても時間の経過とともに変化する可能性があります。これは、データが非同期にシステム全体に伝播する結果整合性モデルによるものです。
  • 結果整合性(Eventual Consistency):特定のデータ項目に新しい更新が行われない場合、最終的にその項目へのすべてのアクセスは最後に更新された値を返します。分散システム内のすべてのノードで変更が表示されるまでに遅延があります。

BASE準拠のシステムは、分散環境での高可用性とスケーラビリティを実現するように設計されており、データ伝播にある程度のレイテンシを許容できるアプリケーションに適しています。

MongoDBにおける結果整合性の理解

MongoDBは、読み取りがセカンダリから提供される場合、または書き込みがまだすべての場所に複製されていない場合に、結果整合性の動作を示すことができます。これは、MongoDBレプリカセットにデータを書き込むと、プライマリノードが書き込みを確認し、その書き込みをセカンダリノードに非同期で複製することを意味します。プライマリは書き込みの永続性を保証しますが、クライアントに成功を通知する前にすべてのセカンダリが追いつくのを待つわけではありません。その結果、セカンダリノードからの後続の読み取りは、最新の書き込みをすぐには反映しない可能性がありますが、最終的には整合性が取れるようになります。

この設計上の選択は、MongoDBが水平方向にスケーリングし、高可用性を維持するための基本です。すべての操作ですべてのノードが完全に同期していることを要求しないことで、MongoDBは一部のノードが一時的に利用不可または遅延している場合でも、読み取りと書き込みを提供し続けることができます。

結果整合性のトレードオフ

  • 長所:高い可用性、優れたパフォーマンス(書き込みのレイテンシが低い)、分散システムのスケーラビリティが向上します。
  • 短所:アプリケーションは、古いデータを読み取る可能性に対処するように設計する必要があります。これは、すべてのレプリカ間での即時一貫性が重要な操作に特に関連します。

MongoDBの読み取り保証と書き込み保証:一貫性の調整

MongoDBはデフォルトで結果整合性を採用していますが、開発者が操作ごとに一貫性のレベルを調整できる強力なメカニズム、読み取り保証書き込み保証を提供します。これにより、アプリケーションのニーズに応じて、一貫性、可用性、パフォーマンスのバランスを取ることができます。

書き込み保証

書き込み保証は、書き込み操作に対してMongoDBから要求される確認応答のレベルを表します。操作が成功を返す前に、レプリカセットメンバーのうちいくつが書き込みを確認する必要があるかを指定します。

主要な書き込み保証オプション:

  • w:書き込みを確認する必要があるmongodインスタンスの数を指定します。
    • w: 0:確認応答なし。クライアントはデータベースからの応答を待ちません。これにより最高のスループットが得られますが、書き込み直後にプライマリがクラッシュするとデータ損失のリスクがあります。
    • w: 1(デフォルト):プライマリノードのみからの確認応答。プライマリは書き込みを受信して処理したことを確認します。高速ですが、書き込みがセカンダリに複製されたことは保証しません。
    • w: "majority":レプリカセットメンバーの過半数(プライマリを含む)からの確認応答。これにより、より強力な永続性が保証されます。書き込みはノードの過半数にコミットされるためです。プライマリに障害が発生した場合、データは他のノードの過半数に存在することが保証されます。
  • jmongodインスタンスが書き込みを確認する前に、オンディスクジャーナルに書き込む必要があるかどうかを指定します。ジャーナリングを有効にすると(j: true)、mongodプロセスがクラッシュした場合でも永続性が提供されます。
  • wtimeout:書き込み保証が満たされるまでの時間制限。この時間内に書き込み保証が満たされない場合、書き込み操作はエラーを返します。

書き込み保証の例(w: "majority"とジャーナリングを使用):

db.products.insertOne(
  { item: "laptop", qty: 50 },
  { writeConcern: { w: "majority", j: true, wtimeout: 5000 } }
);

ヒント:永続性と高可用性が必要な重要なデータには、w: "majority"j: trueを推奨します。重要でないデータや高スループットのログ記録には、w: 1またはw: 0でも許容される場合があります。

読み取り保証

読み取り保証を使用すると、読み取り操作の一貫性と分離のレベルを指定できます。特にレプリカ環境において、MongoDBがクエリに返すデータを決定します。

主要な読み取り保証オプション:

  • local:クライアントが接続しているインスタンス(プライマリまたはセカンダリ)からデータを返します。これはスタンドアロンインスタンスとセカンダリのデフォルトです。レプリカセットの場合、これにより最低のレイテンシが得られますが、古いデータを返す可能性があります。
  • available:データがレプリカセットの過半数に書き込まれたことを保証せずに、インスタンスからデータを返します。localと同様に、可用性と低レイテンシを優先します。
  • majority:レプリカセットメンバーの過半数によって確認されたデータを返します。これにより、データが永続的であり、ロールバックされないことが保証されます。localavailableよりも強い整合性を提供しますが、レイテンシが高くなる可能性があります。
  • linearizable:返されるデータがグローバルに最新の確認済み書き込みを反映していることを保証します。これは最も強力な読み取り保証であり、majority書き込み保証によって確認されたすべての書き込みが読み取りに表示されることを保証します。パフォーマンスに大きなオーバーヘッドが発生する可能性があり、プライマリからの読み取りでのみ使用できます。
  • snapshot(マルチドキュメントトランザクション用):クエリが特定の時点のデータを返すことを保証し、トランザクション内の複数のドキュメント間で読み取りの一貫性を可能にします。

読み取り保証の例(majorityを使用):

db.products.find(
  { item: "laptop" },
  { readConcern: { level: "majority" } }
);

警告linearizableは強い整合性を提供しますが、パフォーマンスに影響があります。書き込みの厳密な順序付けとグローバルな可視性が重要なシナリオにのみ慎重に使用してください。

BASEと結果整合性がスケーリングにとって重要な理由

BASEモデルと結果整合性は、MongoDBのスケーラビリティと高可用性を実現するための核となる要素です:

  1. 水平スケーリング(シャーディング):即時一貫性を緩和することで、MongoDBはデータを複数のシャード(レプリカセットのクラスター)に分散できます。各シャードは比較的独立して動作するため、データベースは水平方向にスケールアウトして大規模なデータセットと高スループットを処理でき、分散システム全体のすべてのノードが常に完全に同期している必要はありません。
  2. 高可用性とフォールトトレランス:レプリカセットでは、プライマリノードが利用不可になった場合、セカンダリから新しいプライマリを選出できます。結果整合性により、フェイルオーバー中でも、セカンダリノードは(読み取り保証に応じて)読み取りを提供し続けることができ、システムは利用可能なままです。プライマリがすべての書き込みですべてのセカンダリを待つ必要がある場合、1つの遅延しているセカンダリがシステム全体のボトルネックになる可能性があります。
  3. パフォーマンス:厳格でない一貫性要件は、書き込み操作のレイテンシを低減し、システムが先に進む前にすべてのノードからの確認応答をブロックして待つ必要がないため、全体的なスループットを向上させます。

読み取り保証と書き込み保証を介して調整可能な一貫性を提供することで、MongoDBは開発者が情報に基づいた決定を下せるようにします。高可用性とスループットを優先するアプリケーション(例:IoTデータ取り込み、リアルタイム分析)は、より弱い一貫性を選択できます。逆に、より強力なデータ整合性を必要とするアプリケーション(例:金融取引、在庫更新)は、関連するパフォーマンスのトレードオフを受け入れて、より強力な一貫性レベルを選択できます。

実践的な考慮事項とベストプラクティス

  • 重要なデータを特定する:強い整合性が絶対に必要なデータ(例:口座残高)と、結果整合性を許容できるデータ(例:ユーザープロフィールの更新、セッションデータ)を決定します。
  • 冪等性を考慮した設計:弱い書き込み保証を使用する場合、書き込みがプライマリでは成功しても、セカンダリへの複製前に失敗し、その後のロールバックとクライアントが書き込みが失敗したと認識する可能性があります。クライアントが操作を再試行すると、重複が発生する可能性があります。可能な限り、操作を冪等になるように設計します。
  • クライアント側の自己書き込み読み取り:ユーザーが書き込みを実行し、すぐにそれを読み取ろうとすると、弱い読み取り保証でセカンダリから読み取った場合、古いデータが表示される可能性があります。ユーザーが常に自分の最近の書き込みを読み取れるようにするには、そのような読み取りをプライマリに誘導するか、majority読み取り保証を使用し、場合によってはそれらの特定の操作に対してmajority書き込み保証と組み合わせることを検討します。
  • 監視rs.printReplicationInfo()またはMongoDB Atlasのメトリクスを使用して、レプリカセットのラグを監視します。高いレプリケーションラグは、結果整合性の問題を悪化させる可能性があります。

MongoDBの一貫性についてのより有用な考え方

MongoDBはすべての状況で単に「結果整合性」であるわけではなく、そのように扱うとずさんな設計につながります。確認済みの書き込み後のプライマリからの読み取りは、数秒遅れているセカンダリにルーティングされた読み取りとは大きく異なる動作をします。w: 1で確認された書き込みは、w: "majority"で確認された書き込みとは異なるリスクプロファイルを持ちます。一貫性のストーリーは、読み取り設定、読み取り保証、書き込み保証、トポロジー、および不適切なタイミングでフェイルオーバーが発生するかどうかに依存します。

通常の製品ページの場合、結果整合性で問題ないかもしれません。管理者が製品の説明を変更し、顧客がセカンダリからしばらく古い説明を見ても、ビジネスへの影響は通常小さいです。注文確認ページの場合、許容度は異なります。顧客が注文を送信し、次の画面でそれが見つからない場合、一時的であっても、システムは壊れているように感じます。ここでは、生のスループットよりも自己書き込み読み取り動作が重要です。

実用的なパターンは、ユーザー向けの確認パスにはより強力な設定を使用し、バックグラウンドまたは分析パスには緩い設定を使用することです。たとえば、注文の書き込みはw: "majority"を使用し、即時の確認読み取りはプライマリに送信します。昨日のアクティビティを集約するダッシュボードは、少しのラグが通常許容されるため、セカンダリから読み取ることができます。ログ取り込みパイプラインは、請求元帳よりも弱い確認応答を受け入れるかもしれませんが、クラッシュやフェイルオーバー中に失われる可能性があるものについて正直であるべきです。

「利用可能」という言葉にも注意してください。分散データベースは障害中も一部のリクエストにサービスを提供し続けることができますが、それはすべてのリクエストが同じ保証で成功できることを意味するわけではありません。プライマリ選出は、書き込みを短時間一時停止します。セカンダリは、読み取り設定で許可されている場合にのみ読み取りを提供できます。ネットワーク分断により、MongoDBは、過半数に属さなくなったノードでの書き込みを受け入れるよりも安全性を選択せざるを得なくなる可能性があります。これらは欠陥ではなく、レプリケートされたデータが2つの矛盾する履歴に分割されるのを防ぐトレードオフです。

以下は、アプリケーション設計ノートに書き込むべき判断です:

重要なアカウント、注文、在庫の変更:
- writeConcern: majority
- ユーザー自身のアクションを確認するときはプライマリから読み戻す
- ドライバーがサポートしている場合は再試行可能な書き込みを使用する
- リクエストIDまたは一意キーを使用して書き込み操作を冪等にする

検索ページ、フィードページ、分析、および重要でないプロフィール表示:
- セカンダリ読み取りは許容される場合がある
- UIで古い結果を許容する
- 鮮度が重要な場合はタイムスタンプを表示する

この種のノートは、「MongoDBはBASEです」と言って次に進むよりもはるかに有用です。将来のエンジニアに、古い読み取りが許容される場所とそうでない場所を伝えます。