こんにちは、みんな。ウラジスラフ・ロディンから連絡があります。私は現在、OTUS でソフトウェア アーキテクチャと高ストレス ソフトウェア アーキテクチャのコースを教えています。 新しいコースの流れのスタートに向けて
導入
HDD は 400 秒あたり約 700 ~ XNUMX の操作しか実行できないため (これは、高負荷システムの一般的な RPS とは比較になりません)、クラシック ディスク データベースがアーキテクチャのボトルネックになっています。したがって、このストレージのスケーリング パターンには特別な注意を払う必要があります。
現在、データベースのスケーリング パターンには、レプリケーションとシャーディングの 2 つがあります。シャーディングを使用すると、書き込み操作をスケールでき、その結果、クラスター内のサーバーごとの書き込みあたりの RPS を削減できます。レプリケーションを使用すると、読み取り操作を使用して同じことを行うことができます。この記事で取り上げるのはこのパターンです。
複製
レプリケーションを非常に高いレベルで見ると、それは単純なことです。サーバーが 1 台あり、そこにデータがありましたが、このサーバーはこのデータの読み取り負荷に対処できなくなりました。さらにいくつかのサーバーを追加し、すべてのサーバー間でデータを同期すると、ユーザーはクラスター内の任意のサーバーから読み取ることができます。
見かけの単純さにもかかわらず、このスキームのさまざまな実装を分類するためのオプションがいくつかあります。
- クラスター内の役割別 (マスター-マスターまたはマスター-スレーブ)
- 送信されたオブジェクト別 (行ベース、ステートメントベース、または混合)
- ノード同期メカニズムによると
今日はポイント3を扱います。
トランザクションのコミットはどのように行われるのでしょうか?
このトピックはレプリケーションに直接関係するものではなく、別の記事を書くこともできますが、トランザクションのコミット メカニズムを理解していなければこれ以上読んでも意味がないため、最も基本的なことを思い出してもらいます。トランザクションのコミットは 3 つの段階で発生します。
- トランザクションをデータベース ログに記録します。
- データベース エンジンでのトランザクションの使用。
- トランザクションが正常に適用されたことを示す確認をクライアントに返します。
データベースによっては、このアルゴリズムに微妙な違いがある場合があります。たとえば、MySQL データベースの InnoDB エンジンには 2 つのログがあります。XNUMX つはレプリケーション用 (バイナリ ログ)、もう XNUMX つは ACID 維持用 (undo/redo ログ) ですが、PostgreSQL では両方の機能を実行するログが XNUMX つあります (先行書き込みログ = WAL)。しかし、上に示したものはまさに一般的な概念であり、そのようなニュアンスは考慮されません。
同期(同期)レプリケーション
受け取った変更をトランザクション コミット アルゴリズムにレプリケートするロジックを追加しましょう。
- トランザクションをデータベース ログに記録します。
- データベース エンジンでのトランザクションの使用。
- すべてのレプリカにデータを送信します。
- すべてのレプリカからトランザクションが完了したという確認を受信します。
- トランザクションが正常に適用されたことを示す確認をクライアントに返します。
このアプローチでは、次のような多くの欠点が生じます。
- クライアントは、変更がすべてのレプリカに適用されるまで待機します。
- クラスター内のノードの数が増えると、書き込み操作が成功する可能性が低くなります。
1 番目のポイントですべてが多かれ少なかれ明らかであれば、2 番目のポイントの理由を説明する価値があります。同期レプリケーション中に少なくとも XNUMX つのノードから応答を受信しない場合、トランザクションはロールバックされます。したがって、クラスター内のノードの数を増やすと、書き込み操作が失敗する可能性が高くなります。
特定の割合のノード (たとえば 51% (クォーラム)) のみからの確認を待つことはできますか?はい、可能ですが、クラシック バージョンではすべてのノードからの確認が必要です。これにより、クラスター内の完全なデータの一貫性を確保できます。これは、このタイプのレプリケーションの疑いのない利点です。
非同期 (async) レプリケーション
先ほどのアルゴリズムを変更してみましょう。 「いつか」レプリカにデータを送信し、「いつか」変更がレプリカに適用されます。
- トランザクションをデータベース ログに記録します。
- データベース エンジンでのトランザクションの使用。
- トランザクションが正常に適用されたことを示す確認をクライアントに返します。
- データをレプリカに送信し、レプリカに変更を適用します。
このアプローチでは、データがレプリカに到達し、コミットされるまでクライアントを待たせる必要がないため、クラスターが迅速に動作するようになります。
しかし、「後で」レプリカにデータをダンプする状態では、トランザクションの損失や、ユーザーが確認したトランザクションの損失につながる可能性があります。これは、データを複製する時間がなかった場合、クライアントに確認が行われないためです。操作の成功が送信され、変更が到着したノードで HDD がクラッシュした場合、トランザクションが失われ、非常に不快な結果につながる可能性があります。
準同期レプリケーション
最後に、準同期レプリケーションに進みます。このタイプのレプリケーションはあまり知られていない、またはあまり一般的ではありませんが、同期レプリケーションと非同期レプリケーションの両方の利点を組み合わせることができるため、非常に興味深いものです。
前の 2 つのアプローチを組み合わせてみましょう。クライアントを長期間保持しませんが、データをレプリケートする必要があります。
- トランザクションをデータベース ログに記録します。
- データベース エンジンでのトランザクションの使用。
- データをレプリカに送信します。
- 変更が受信されたことを示すレプリカからの確認を受信します (変更は「しばらくして」適用されます)。
- トランザクションが正常に適用されたことを示す確認をクライアントに返します。
このアルゴリズムでは、変更を受信するノードとレプリカ ノードの両方に障害が発生した場合にのみトランザクション損失が発生することに注意してください。このような障害が発生する可能性は低いと考えられており、これらのリスクは受け入れられます。
ただし、このアプローチではファントム リードのリスクが発生する可能性があります。次のシナリオを想像してみましょう。ステップ 4 で、どのレプリカからも確認を受信しませんでした。このトランザクションはロールバックする必要があり、クライアントに確認を返す必要はありません。データはステップ 2 で適用されたため、ステップ 2 の終了とトランザクションのロールバックの間に時間ギャップがあり、その間、並列トランザクションはデータベース内にあるべきではない変更を認識する可能性があります。
ロスレス準同期レプリケーション
少し考えれば、アルゴリズムの手順を逆にするだけで、このシナリオにおけるファントム リードの問題を解決できます。
- トランザクションをデータベース ログに記録します。
- レプリカデータを送信中です。
- 変更が受信されたことを示すレプリカからの確認を受信します (変更は「しばらくして」適用されます)。
- データベース エンジンでのトランザクションの使用。
- トランザクションが正常に適用されたことを示す確認をクライアントに返します。
今後は、変更がレプリケートされた場合にのみ変更をコミットします。
出力
いつものことですが、理想的な解決策はありません。それぞれに独自の長所と短所があり、さまざまな種類の問題の解決に適した一連の解決策が存在します。これは、レプリケートされたデータベース内のデータを同期するメカニズムの選択にまったく当てはまります。半同期レプリケーションが持つ一連の利点は十分に確実で興味深いため、普及率は低いにもかかわらず、注目に値すると考えられます。