前書き
MongoDBレプリカセット・トランザクションの紹介
- MongoDB レプリカセットのトランザクション
- MongoDBのレプリカセットのレプリケーションは、ドラフトプロトコルに基づいており、Paxosに比べて、ドラフトプロトコルの実装は簡単ですが、ドラフトプロトコルのみがサポートし、MongoDBのレプリカセットに対応するマスタースレーブアーキテクチャであり、唯一の。 MongoDBのレプリカセットのトランザクション管理は、競合の検出、トランザクションのコミットおよびその他の重要な操作を含む、唯一のマスターノード上で完了します。つまり、トランザクション管理のレプリカセットは、単一ノードのロジックと基本的に同じです。
- MongoDBのトランザクションは依然としてACIDの4つの機能を実装しており、MongoDBはトランザクションの分離レベルとしてSIを使用しています。
SI
CSI
- CSI はトランザクションの読み取りスナップショットとして最新のシステムスナップショットを選択します。
- は、トランザクションの開始時に現在のDBの最新のスナップショットをトランザクションの読み取りスナップショットとして取得することです。
- snapshot(Ti) = start(Ti)
- 書き込みトランザクションが競合する確率を減らし、読み取りトランザクションが最新のデータを読み取る機能を提供します。
- 一般的に言って、データベースはSI分離レベルをサポートしており、実際にはデフォルトではCSIをサポートしていると言えます。例えば、RocksDBがサポートしているSIはCSIであり、バージョン3.0のWiredTigerがサポートしているSIもCSIです。
GSI
- GSIはトランザクションの読み取りスナップショットとして過去のデータベーススナップショットを選択するので、CSIはGSIの特別なケースと見なすことができます。
- レプリカセットの場合、マスターノード上のトランザクションについては各トランザクションの開始時刻に取得されるシステムスナップショットであるCSIを考えますが、他のスレーブノードについては、スナップショットの統一された" "概念はありません。一般化されたスナップショットは実際にはスナップショット観測に基づいており、現在のトランザクションに対して適切な以前のスナップショットを選択することで、スレーブノード上のトランザクションを遅延なく正しく実行することができます。
- 以下に例を示します。:
- 例えば、データベースの現在の状態がS={T1, T2, T3}で、これからT4の実行を開始したいとします。
- T4で変更したい値がT3で変更されていないことが分かっている場合、T4を実行すると、T2コミット後のsnashotに従って読み取ることができます。
- より早い時点を選ぶには、以下のルールを満たす必要があります。
- シンボル定義
- Ti:
- Xi: トランザクションiによって変更されたX変数
- snapshot(Ti): トランザクションiの選択されたスナップショットの時刻。
- start(Ti): トランザクション i 開始時刻
- commit(Ti): トランザクションiのコミット時間
- abort(Ti): the time when Ti is aborted.
- end(Ti): the time when Ti is committed or aborted.
計算式の説明
読み取りルール
- G1.1では、このトランザクションによって変数Xが変更され、新しい値が読み取られる場合、読み取り操作の後に書き込み操作が行われなければなりません。
- G1.2、トランザクションiがトランザクションjによって更新された変数のXを読む場合、トランザクショ ンjによって更新された変数のXを読むトランザクションiの操作の前に、Xを更新するトランザクショ ンiの操作が存在してはならない;そして
- G1.3、トランザクションjはトランザクションiのスナップショット時間より早くコミットします;
- G1.4では、変数Xを更新する任意のトランザクションkについて、このトランザクションkのコミット時間がトランザクションjのコミット時間より小さいか、このトランザクションkのコミット時間がトランザクションiのコミット時間より大きいかのどちらかを満たさなければなりません。
- G2、すでにコミット履歴にある2つのトランザクションCi、Cjに対して、トランザクションjのコミットタイムスタンプがトランザクションiの観測期間内(commit(Ti))にある場合、それらの更新された変数の交点は空でなければならないことが保証されます。
PCSI
- 国土地理院は、SIとして使用可能な範囲を定義しているだけで、どのSIを選択すべきかを正確に定義しているわけではありません。
- PCSIはレプリカセット用に設計されています。トランザクションTiがSノード上で実行を開始するためには、Sノードにトランザクションの実行とコミットに必要なすべての先行トランザクションが含まれていなければなりません。
- GSI、PCSIの読み取りルールと比較して、P1.5のルールが追加されています。
- A Critique of ANSI SQL Isolation Levelsで説明されているように、SIのコミットタイムスタンプの設定は単調に増加する必要があります。新しく設定されたタイムスタンプは、すでにシステムに存在する開始タイムスタンプやコミットタイムスタンプよりも大きくなければなりません。
- SI読み取りタイムスタンプは、現在実行中のトランザクションの最小コミットタイムスタンプよりも小さくなるように設定する必要があります。なぜなら、SI読み取りタイムスタンプが現在実行中のトランザクションの最小コミットタイムスタンプよりも大きい場合、読み取りトランザクションによって読み取られたデータは未定義であり、スナップショットが取得されたタイミングではなく、読み取りトランザクションが開始されたタイミングに依存するため、一貫性要件に違反するからです。以下に例を示します。
- T3の読み取りタイムスタンプがT2のコミットタイムスタンプより大きく、T2トランザクションが実行中であれば、T2トランザクションが終了するまで待ちます。データベースを見ると、GSIに違反していることがわかります。
トランザクションの実行順序は以下の通りです。
T1 commited and commitTs(1) -> T2 start -> T2 set commitTs(2) -> T3 start -> T3 set snapshotTs(3) -> T3 commit -> pointA -> T2 commit -> pointB
その後、それはT3トランザクションが実際に読み取られた値は、T1トランザクションの値であることがわかります。しかし、ポイントBによると、GSIはルール1.4の要件を読んで表示するには、T3はT1トランザクションの変更を読み取った場合、必然的にT3とT1の間に穴がないことが必要であることがわかります。しかし、実際には、T2はT3とT1の間に落ちている、つまり、GSI 1.4読み取り規則の違反です。
- そのため,
MongoDBレプリカセットタイムスタンプアプリケーション
MongoDB 4.0のレプリケーションでは、3.xシリーズのMongoDBスレーブノードレプリケーションで発生するスレーブノードのパフォーマンス低下に対する重要な解決策として、タイムスタンプ機能も使用しています。
- MongoDBのマスターノードとスタンバイノードのデータ同期は、WiredTigerのwalログに基づいて行われません。その代わりに、mongodbは各操作のデータ変更をoplogと呼ばれるコレクションに書き込みます。
- コレクションの名前はlogですが、実際にはMongoDBのテーブルであり、oplogへの書き込みはアペンド方式ではなく、末尾のごちゃごちゃした方式で変更されます。
- oplogの場合、oplogの読み込み順はTSフィールドでソートされ、上位のコミット順とは関係ありません。そのため、後から開始されたトランザクションがoplogで最初に読み込まれるシナリオがあります。
- そのため、スレーブ・ノードがoplogを読み取る際に、ある時点で穴が開いてしまいます。例
- 時点1:oplogシーケンス:Ta→Tb、システムにはまだトランザクションTcが実行中。
- タイミング2:oplogのシーケンスはTa -> Tc -> Tbで、Tcの実行が終わると、tsの順序からTcがTaとTbの間に挿入されているように見えます。
- これは、スレーブノードが毎回最新のデータをオプログから読み出すと、例えば時点1でTa->Tbのような不連続なデータを取得する可能性があるためです。
- 特定のレプリケーション・ロジックでは、ボイドを含むノードからオプログ・データを読み取る方法を見つけなければなりません。スナップショットの選択にボイドが含まれていないこともGSIの要件です。
- oplogのTsはmongoの上位レイヤーから与えられるので、どのトランザクションがどのTsを持っているかを知るのは簡単です。そして、このTsをトランザクションのcommitTsとしてoplogのstored transactionsに入れることで、oplogのトランザクションの可視性の読み込み順を整合させることができます。この場合、アクティブなトランザクションのリストによると、oplogを2つに分割することができます。
- アクティブなコミットTのリストが{T10, T11, T12}で、アクティブなトランザクションのリストが{T10, T11, T12, T13, T14}であると仮定すると、現在T10, T11, T12, T13, T14が実行中で、T10, T11, T12がすでにコミットTを設定していることになります。また、minCommitTs(T10,T11,T12)はグローバルな最小commitTsであり、それ以下のcommitTsを持つトランザクションはmaxCommitTs(T10,T11,T12)よりも大きくなければなりません。これらのトランザクションはアクティブなトランザクションのリストにないため、すでにコミットされていることになり、グローバル最小コミットTsのoplog tsはコミットされていることがわかり、oplogは以下のようにコミットTsでソートされます。
... Tx|minCommitTs(T10,T11,T12)|...
システムはminCommitTs(T10,T11,T12)未満のトランザクションをoplogにコミットしないので、スレーブノードはここで直接データを回復できます。
- 上記のmongodbのoplog minCommitTs(T10,T11,T12)は特別なタイムスタンプですが、これについては後述します。
- 上記の方式では、ボイドの問題を解決することができます。このとき、スレーブノードがデータをリカバリするたびに、スレーブノードのリカバリデータとリードデータが競合しないように、リードスナップショットを、最後のリカバリTs(ホールTsもなし)に設定します。これにより、3.xシリーズのノードからのデータを同期することによって生じるノードのパフォーマンス低下の問題が解決されます。