トランザクションサポートを備えた分散 NoSQL - FoundationDB

[はじめに] 週末の読書時間、良い論文 (https://cacm.acm.org/magazine/2023/6/273229-foundationdb-a-distributed-key-value-store/fulltext) が視野を広げてくれました、サポートする NoSQLトランザクション セマンティクスは、ソフトウェア システム アーキテクチャの代替案に含める必要があります。

FoundationDB は、オープン ソースのトランザクション キー/値ストレージ システムであり、NoSQL アーキテクチャの柔軟性と拡張性を ACID トランザクションの強力なパフォーマンスと組み合わせた最初のシステムの 1 つです。FoundationDB アーキテクチャは、インメモリ トランザクション管理システム、分散ストレージ システム、および組み込みの分散構成システムに分離されています。各サブシステムは、スケーラビリティ、高可用性、耐障害性を実現するために個別に構成できます。

FoundationDB には、起こり得る障害条件下で新機能をテストするための決定論的シミュレーション フレームワークも含まれています。この厳格なテストにより、FoundationDB の安定性が高まり、開発者は新しい機能を速いペースで導入およびリリースできるようになります。

同時に、FoundationDB は、FoundationDB 上にさまざまなシステムを構築するために、慎重に選択された最小限の機能セットを提供します。その強力なデータ一貫性、堅牢性、可用性により、Apple、Snowflake、その他の企業のクラウド インフラストラクチャの基盤となり、ユーザー データ、システムのメタデータと構成、その他の重要な情報の保存に使用されます。

1. 背景情報

1.1 現在の NoSQL ソリューションと直面する問題

多くのクラウド サービスは、アプリケーションの状態を保持するためにスケーラブルな分散ストレージ バックエンドに依存しています。このようなストレージ システムは、耐障害性と高可用性を備えていると同時に、迅速なアプリケーション開発を可能にする十分な柔軟性を備えたセマンティクスとデータ モデルを提供する必要があります。これらのサービスは、数十億のユーザーに拡張し、ペタバイトまたはエクサバイトのデータを保存し、毎秒数百万のリクエストを処理する機能をサポートする必要があります。

NoSQL システムの出現により、アプリケーション開発が容易になり、ストレージ システムの拡張と運用が容易になり、耐障害性が提供され、さまざまなデータ モデルがサポートされます。スケーラビリティを確保するために、これらの NoSQL システムはトランザクション セマンティクスを犠牲にしてデータの最終的な一貫性を提供するため、アプリケーション開発者は同時操作のためのデータ更新の問題を考慮する必要があります。

b5dcd1f6e7ba3e3c7ade09fd53a3b7e4.png

1.2 FoundationDBの成り立ちと特徴

FoundationDB は、高度な分散システムを構築するために必要な基盤システムになることを期待して 2009 年に作成されました。これは、キー空間全体にわたって厳密にシリアル化されたマルチキー トランザクションをネイティブにサポートする、順序付けされたトランザクション型のキーと値のストアです。慎重に選択された最小限の機能セットを備えた、拡張性の高いトランザクション ストレージ エンジンを提供します。構造化セマンティクス、クエリ言語、データ モデル、セカンダリ インデックス、またはトランザクション データベースで一般的に見られるその他の多くの機能は提供しません。

NoSQL モデルは、アプリケーション開発者に優れた柔軟性を提供します。アプリケーションはデータを単純なキーと値のペアとして保存できますが、一貫したセカンダリ インデックスや参照整合性チェックなどのより高度な機能を実装する必要があります。FoundationDB はデフォルトで厳密にシリアル化可能なトランザクションを設定しますが、そのようなトランザクションを必要としないアプリケーションに対応するために、これらのセマンティクスをきめ細かい制御の下で緩和することができます。

FoundationDB の人気とオープン ソース コミュニティの成長の理由の 1 つは、FoundationDB がデータベースの「下半分」に焦点を当て、残りを上部のステートレス アプリケーションに任せて、さまざまなデータ モデルやその他の機能を提供していることです。FoundationDB 上に構築されたさまざまなレイヤーは、この珍しい設計の有用性を示しています。たとえば、FoundationDB レコード レイヤーは、ユーザーがリレーショナル データベースに期待するもののほとんどを追加し、グラフ データベース JanusGraph は、FoundationDB レイヤーに基づいた実装を提供します。CouchDB は FoundationDB 上のレイヤーとして再構築されています。したがって、従来のアプリケーションでも FoundationDB を使用できます。

分散システムのテストとデバッグは、分散システムを構築するのと同じくらい難しいです。予期しないプロセスやネットワークの障害、メッセージの並べ替え、その他の非決定性の原因により、現実には崩れる暗黙の仮定が露呈する可能性があり、これらのバグの再現やデバッグが非常に困難になります。これらのエラーは、多くの場合、明示的なデータベース システムにとって致命的です。さらに、データベース システムのステートフルな性質により、このようなエラーはデータ破損につながる可能性がありますが、それを検出するには数か月かかる場合があります。モデル検査技術は分散プロトコルの正確性を検査できますが、多くの場合、実際の実装は検査できません。特定のシーケンスで複数のクラッシュが発生した場合にのみ発生する深刻な脆弱性は、エンドツーエンドのテストに課題をもたらします。

FoundationDB は根本的なアプローチを採用しています。データベース自体を構築する前に、相互作用するプロセスのネットワークやさまざまなディスク、プロセス、ネットワーク、要求レベルの障害と回復をシミュレートできる決定論的なデータベース シミュレーション フレームワークを構築します。これらすべてが 1 つの物理内で完了します。プロセス。C++ 構文拡張機能 Flow は、この目的のために特別に作成されました。このシミュレーションでの厳密なテストにより、FoundationDB は非常に安定し、開発者は新しい機能やリリースを速いペースで導入できるようになります。

FoundationDB の疎結合アーキテクチャは、コントロール プレーンとデータ プレーンで構成されます。コントロール プレーンはクラスターのメタデータを管理し、高可用性のために Active Disk Paxos を使用します。データ プレーンは、トランザクション管理システムと分散ストレージ層で構成されます。前者は更新の処理を担当し、後者は読み取りの提供を担当します。両方は独立して拡張できます。FoundationDB は、オプティミスティック同時実行制御とマルチバージョン同時実行制御の組み合わせを通じて厳密なシリアル化を実装します。

FoundationDB を他の分散データベースと区別する機能の 1 つは、障害の処理方法です。FoundationDB はクォーラム メカニズムに依存せず、システムを再構成することで障害を積極的に検出し、回復しようとします。これにより、より少ないリソースで同じレベルのフォールト トレランスを実現できます。FoundationDB は、(2n+1 ではなく) n+1 個のレプリカのみを必要としながら、n 個の障害を許容できます。この方法は、ローカルまたは地域の展開に適しています。データ損失のないリージョン間の自動フェイルオーバーを提供しながら、リージョン間の書き込み遅延を回避する、WAN 導入のための新しい戦略を提供します。

2. 設計原則とシステムアーキテクチャ

FoundationDB の主な設計原則は、分割統治、障害指向設計、およびシミュレーション テストです。

FoundationDB は、トランザクション管理システム (書き込み) を分散ストレージ システム (読み取り) から分離し、それらを独立してスケーリングします。トランザクション管理システムでは、プロセスには、トランザクション管理のさまざまな側面を表すさまざまな役割が割り当てられます。さらに、過負荷制御や負荷分散などのクラスター全体のオーケストレーション タスクも、他の異なる役割によって分割され、処理されます。

分散システムでは、障害は例外ではなく必然です。トランザクション管理システムの障害に対処するには、すべての障害を回復する必要があります。障害が検出されると、トランザクション システムはアクティブに停止されます。その結果、すべての障害処理は単一の回復操作に減り、これがよくテストされた共通のコード パスになります。可用性を向上させるために、FoundationDB は平均復旧時間 (MTTR) を最小限に抑えるよう努めています。弊社の実稼働クラスターでは、通常、合計時間は 5 秒未満です。

FoundationDB は、分散データベースの正確性をテストするために、確率論的かつ決定論的なシミュレーション テスト フレームワークに依存しています。模擬テスト フレームワークは根深いバグを明らかにするだけでなく、開発者の生産性と FoundationDB のコード品質も向上します。

2.1. アーキテクチャ

FoundationDB クラスターには、次の図に示すように、重要なシステム メタデータとクラスター全体のオーケストレーションを管理するためのコントロール プレーンと、トランザクション処理とデータ ストレージのためのデータ プレーンがあります。

f8d85d0d5d6a637b835f3bfd832cf515.jpeg

コントロールプレーン

コントロール プレーンは、コーディネーター上で主要なシステム メタデータ (つまり、トランザクション システム構成) を永続化する責任があります。これらのコーディネーターは Paxos グループを形成し、クラスター コントローラーを選出します。クラスター コントローラーはクラスター内のすべてのサーバーを監視し、シーケンサー、データ ディストリビューター、およびレート コントローラーの 3 つのプロセスを維持します。失敗またはクラッシュした場合、これらのプロセスは再起動されます。データ ディストリビュータは、障害を監視し、ストレージ サーバー間でデータのバランスをとる責任があります。レート コントローラーはクラスターに過負荷保護を提供します。

データプレーン

FoundationDB は、読み取りが多く書き込みが少なく、トランザクションごとに少数のキーワードの読み取りと書き込みが行われ、スケーラビリティが必要な OLTP ワークロードに適しています。分散トランザクション管理システムは、シーケンサー、エージェント、およびパーティション全体のリゾルバーで構成されており、これらはすべてステートレス プロセスです。ログ システムは TS の書き込み前ログを保存し、別の分散ストレージ システムを使用してデータを保存し、読み取りサービスを提供します。ロギング システムは一連のログ サーバーで構成されますが、分散ストレージ システムは複数のストレージ サーバーで構成されます。シーケンサーは、読み取りバージョンとコミット バージョンを各トランザクションに割り当てます。ブローカーはクライアントにマルチバージョン読み取りを提供し、トランザクションのコミットを調整します。パーサーはトランザクション間の競合をチェックします。ログ サーバーは、複製、シャーディング、分散された永続キューとして機能し、各キューには 1 つのストレージ サーバーの WAL データが保存されます。分散ストレージ システムは複数のストレージ サーバーで構成され、各ストレージ サーバーはデータ シャードのセット、つまり連続したキー範囲を保存し、クライアントにそれらへのアクセスを提供します。ストレージ サーバーはシステム内のプロセスの大部分を占めており、これらが集まって分散 B ツリーを形成します。各ストレージ サーバーのストレージ エンジンは SQLite の拡張バージョンであり、範囲のクリアを高速化し、削除をバックグラウンド タスクに延期し、非同期プログラミング サポートを追加する機能強化が施されています。

2.1.1 読み取りと書き込みの分離と拡張

上記のプロセスはさまざまな役割に割り当てられ、役割ごとに新しいプロセスを追加することで拡張されます。クライアントは共有ストレージ サーバーから読み取りを行うため、読み取りはストレージ サーバーの数に比例して増加します。プロキシ、パーサー、ログ サーバーを追加して書き込みを拡張します。コントロール プレーンのシングルトン プロセス (クラスター コントローラーやシーケンサーなど) とコーディネーターはパフォーマンスのボトルネックではなく、限られたメタデータ操作のみを実行します。

2.1.2 ブート起動

FoundationDB は外部調整サービスに依存しません。すべてのユーザー データとほとんどのシステム メタデータはストレージ サーバーに保存されます。ストレージ サーバーに関するメタデータはログ サーバーに保存され、ログ サーバーの構成データはすべてのコーディネーターに保存されます。コーディネーターはディスク Paxos グループであり、クラスター コントローラーが存在しない場合、サーバーはクラスター コントローラーになろうとします。新しく選択されたクラスター コントローラーは、コーディネーターから古い LS 構成を読み取り、新しいトランザクション サーバーとログ サーバーを生成します。エージェントは、すべてのストレージ サーバーに関する情報を含むシステム メタデータを古い LS から復元します。シーケンサーは、新しいトランザクション サーバーが回復を完了するのを待ってから、新しいログ サーバー構成をすべてのコーディネーターに書き込みます。これで、新しいトランザクション システムはクライアント トランザクションを受け取る準備が整います。

2.1.3 再構成

シーケンサー プロセスは、エージェント、パーサー、およびログ サーバーの状態を監視します。ログ サーバーまたはログ サーバーに障害が発生したり、データベース構成が変更されたりすると、シーケンサは終了します。クラスター コントローラーはシーケンサーの障害を検出し、新しいトランザクション サーバーとログ サーバーを起動してブートストラップします。このように、トランザクション処理はエポックに分割され、各エポックは独自のシーケンサーを備えたトランザクション管理システムの生成を表します。

2.2. トランザクション管理

2.2.1 エンドツーエンドのトランザクション処理

クライアント トランザクションは、まずブローカーの 1 つに連絡して読み取りバージョン (つまり、タイムスタンプ) を取得します。次に、エージェントは、以前に発行されたすべてのトランザクションのコミット バージョンと少なくとも同じ読み取りバージョンを生成するようにシーケンサーに要求し、この読み取りバージョンをクライアントに送り返します。その後、クライアントはストレージ サーバーに読み取りを発行し、特定の読み取りバージョンで値を取得できます。クライアントの書き込みはクラスターに接続せずにローカルにキャッシュされ、トランザクションのデータベース検索結果はコミットされていない書き込みと結合されて読み取りが保持されます。コミット時に、クライアントはトランザクション データをブローカーの 1 つに送信し、コミットまたは中止の応答を待ちます。トランザクションのコミットに失敗した場合、クライアントはトランザクションを再開することを選択できます。

エージェントは 3 つのステップでクライアント トランザクションをコミットします。まず、シーケンサーに問い合わせて、既存の読み取りバージョンまたはコミット バージョンよりも大きいコミット バージョンを取得します。シーケンサーは、1 秒あたり最大 100 万バージョンの速度でコミット バージョンを選択します。次に、ブローカーはトランザクション情報をパーティション範囲リゾルバーに送信します。パーティション範囲リゾルバーは、読み取りと書き込みの競合をチェックすることにより、FoundationDB のオプティミスティック同時実行制御を実装します。すべてのリゾルバーに競合がない場合、トランザクションは最終コミット フェーズに入ることができます。それ以外の場合、エージェントはトランザクションを中止としてマークします。最後に、コミットされたトランザクションは、永続化のために一連のログ サーバーに送信されます。指定されたすべてのログ サーバーがブローカーに応答すると、トランザクションはコミットされたとみなされ、ブローカーはコミットされたバージョンをシーケンサーに報告してからクライアントに応答します。ストレージ サーバーはログ サーバーから変更ログを継続的に取得し、コミットされた更新をディスクに適用します。

上記の読み取り/書き込みトランザクションに加えて、FoundationDB は読み取り専用トランザクションスナップショット読み取りもサポートしています。読み取り専用トランザクションはシリアル化可能 (バージョンの読み取り時に発生) で効率的であり、クライアントはデータベースに接続せずに操作できます。これらのトランザクションをローカルで送信します。FoundationDB のスナップショット読み取りは、競合を減らすことでトランザクションの分離特性を選択的に緩和します。つまり、同時書き込みはスナップショット読み取りと競合しません。

2.2.2 厳密なシリアル化

FoundationDB は、最適化された同時実行制御とマルチバージョン制御を組み合わせることにより、シリアル化可能なスナップショット分離を実装します。トランザクションTx はシーケンサーから読み取りバージョンとコミット バージョンを取得することを思い出してください。読み取りバージョン番号は、Txの開始時のコミット バージョン以上であることが保証され、コミット バージョンは既存の読み取りバージョン番号またはコミット バージョン番号よりも大きくなります。このコミット バージョンはトランザクションのシリアル履歴を定義し、ログ シーケンス番号 (LSN) として使用されます。Tx は以前にコミットされたすべてのトランザクションの結果を監視するため、FoundationDB は厳密なシリアル化を実装します。ログ シーケンス番号の間にギャップがないことを確認するために、シーケンサーは各コミット内の前のコミットを返します。ブローカーは、LSN と以前の LSN をパーサーとログ サーバーに送信し、LSN の順序でトランザクションをシリアルに処理できるようにします。

同様に、ストレージ サーバーは、LSN の昇順でログ サーバーからログ データを抽出します。パーティション範囲リゾルバーは、書き込みスナップショット分離と同様のロックフリー競合検出アルゴリズムを使用します。ただし、FoundationDB でコミットされたバージョンが選択される前に競合検出が行われる点が異なります。これにより、FoundationDB はバージョン割り当てと競合検出を効率的にバッチ処理できるようになります。キー空間全体がパーティション範囲リゾルバー間で分割されるため、競合検出を並行して実行できます。トランザクションは、すべてのパーティション全体のリゾルバーがトランザクションを承認した場合にのみコミットできます。それ以外の場合、トランザクションは中止されます。中止されたトランザクションがパーティション全体のリゾルバーのサブセットによって認識され、コミットされた可能性のあるトランザクションの履歴が更新された可能性があります。これにより、他のトランザクションの競合 (つまり、誤検知) が発生する可能性があります。

実際には、トランザクションのキー範囲は通常パーティション範囲リゾルバーに属しているため、これは実稼働ワークロードにとって問題になりません。さらに、変更されたキーはマルチバージョン化ウィンドウの後に期限切れになるため、このような誤検知は短いマルチバージョン化ウィンドウ時間 (つまり 5 秒) の間にのみ発生します。FoundationDB の最適化された同時実行制御設計メカニズムにより、ロックの取得と解放の複雑なロジックが回避され、トランザクション サービスとストレージ サービス間の対話が大幅に簡素化されます。その代償として、トランザクションが中止されると作業が無駄になります。

マルチテナントの実稼働ワークロードでは、トランザクション競合率は非常に低く (1% 未満)、最適化された同時実行制御が適切に機能します。競合が発生した場合、クライアントはトランザクションを再開するだけで済みます。

2.2.3 ロギングプロトコル

ブローカーがトランザクションのコミットを決定すると、メッセージがすべてのログ サーバーに送信されます。変更内容はキー範囲の変更を担当するログ サーバーに送信され、他のログ サーバーは空のメッセージ本文を受け取ります。ログ メッセージ ヘッダーには、シーケンサーから取得した現在および以前の LSN と、このエージェントの既知の最大コミット バージョンが含まれます。ログ サーバーはログ データを永続化した後、エージェントに応答します。すべてのレプリカ ログ サーバーが応答し、この LSN が現在の KCV より大きい場合、エージェントは KCV を LSN に更新し、REDO ログを LS からサーバーは送信パスの一部ではありませんが、バックグラウンドで実行されます。

FoundationDB では、ストレージ サーバーはログ サーバーからの非永続 REDO ログをメモリ内のインデックスに適用します。通常、これは反映されたコミットされた読み取りバージョンがクライアントに配布される前に発生し、非常に低い遅延で複数バージョンの読み取りを処理できるようになります。したがって、クライアントの読み取りリクエストがストレージ サーバーに到達すると、通常、リクエストされたバージョン (つまり、最新のコミットされたデータ) がすでに利用可能になっています。ストレージ サーバーのレプリカに読み取り可能な新しいデータがない場合、クライアントはデータが使用可能になるまで待つか、別のレプリカでリクエストを再発行します。

両方がタイムアウトした場合、クライアントはトランザクションを再開するだけで済みます。ログ データはログ サーバー上にすでに保存されているため、ストレージ サーバーは更新をメモリにバッファリングし、定期的にデータをバッチでディスクに保存できるため、I/O 効率が向上します。

2.2.4 トランザクションシステムの復旧

従来のデータベース システムは通常、ARIES 回復プロトコルを使用します。リカバリ中、システムは、REDO ログ レコードを関連するデータ ページに再適用することにより、前のチェックポイントからのログ レコードを処理します。これにより、データベースが一貫した状態になり、クラッシュ中に行われたトランザクションは、元に戻すログを実行することでロールバックできます。FoundationDB では、リカバリは非常に安価になるように意図的に設計されており、元に戻すログ エントリを適用する必要はありません。これは、非常に単純化された設計選択によるものです。つまり、REDO ログの処理は通常のログ転送パスと同じです。 

FoundationDB では、ストレージ サーバーがログ サーバーからログを取得し、バックグラウンドで適用します。回復プロセスは、障害を検出し、新しいトランザクション システムを採用することから始まります。新しい TS は、古いログ サーバー内のすべてのデータが処理される前にトランザクションを受け入れることができます。リカバリには、REDO ログの終わりを見つけるだけで済みます。その時点で (通常の順方向操作と同様に)、ストレージ サーバーはログを非同期に再生します。

エポックごとに、クラスター コントローラーはいくつかの手順で回復を実行します。まず、コーディネーターから以前の TS 構成を読み取り、この情報をロックして、別の同時回復を防ぎます。次に、古いログ サーバーに関する情報を含む以前の TS システム状態を復元し、トランザクションの受け入れを停止し、シーケンサー、エージェント、パーサー、およびログ サーバーの新しいセットを採用します。以前のログ サーバーが停止され、新しいトランザクション サーバーが起動された後、クラスター コントローラーは新しいトランザクション サーバーの情報をコーディネーターに書き込みます。プロキシとリゾルバはステートレスであるため、それらの回復には追加の作業は必要ありません。代わりに、ログ サーバーはコミットされたトランザクションのログを保持し、これらのトランザクションすべてが耐久性があり、ストレージ サーバーによって取得可能であることを確認する必要があります。古いログ サーバーの復元の本質は、REDO ログの終わり、つまりリカバリ バージョン (RV) を決定することです。元に戻すログをローリングすると、基本的に、古いログ サーバーとストレージ サーバー内の RV 以降のデータがすべて破棄されます。図 2 は、シーケンサーによって RV がどのように決定されるかを示しています。

4e21e3158adb86d5ee4155f1cfe7f7e6.jpeg

ログ サーバーに対するエージェントのリクエストは、そのエージェントがコミットした最大の LSN である KCV と現在のトランザクションの LSN を使用してピギーバックされます。各ログ サーバーは、受信した最大 KCV と、LogServer が保持する最大 LSN である永続バージョンを保持します。回復プロセス中に、シーケンサーは古いログ サーバーをすべて停止しようとします。各応答には、そのログ サーバー上の DV および KCV が含まれます。 

ログサーバの複製次数をkとする。シーケンサーが mk 個を超える応答を受信すると、シーケンサーは、前のエポックでコミットされたトランザクションがすべての KCV の最大値に達したことを認識し、これが前のエポックの終了バージョン (PEV) になります。このリリースより前のすべてのデータは完全に複製されています。開始バージョンが PEV +1 である現在のエポックの場合、シーケンサーはすべての DV の最小値を RV として選択します。[PEV + 1, RV] の範囲のログは、ログ サーバーに障害が発生した場合に複製を修復するために、前のエポックのログ サーバーから現在のエポックのログ サーバーに複製されます。この範囲には数秒間のログ データしか含まれていないため、コピーのオーバーヘッドは非常に小さくなります。

シーケンサが新しいトランザクションを受け入れるとき、最初のトランザクションは特別なリカバリ トランザクションであり、ストレージ サーバーに現在の RV 値を通知して、RV を超えるデータをロールバックできるようにします。現在の FoundationDB ストレージ エンジンは、バージョン管理されていない SQLite B ツリーとメモリ内のマルチバージョン REDO ログ データで構成されています。マルチバージョン対応ウィンドウを離れる変更 (つまり、コミットされたデータ) のみが SQLite に書き込まれます。ロールバックは、ストレージ サーバー内のメモリ内のマルチバージョン データを単純に破棄します。次に、ストレージ サーバーは、バージョンPEVより大きいデータを新しいログ サーバーからプルします

2.3. コピー

FoundationDB はさまざまなレプリケーション戦略を使用して、さまざまなデータの障害を許容します。

2.3.1 メタデータのレプリケーション

コントロール プレーンのシステム メタデータは、Active Disk Paxos を使用してコーディネーターに保存されます。このメタデータは、コーディネーターの過半数 (つまり過半数) がアクティブである限り復元できます。

2.3.2 ログのレプリケーション

エージェントがログ サーバーにログを書き込むと、各シャードのログ レコードが k = f + 1 個のログ サーバーに同期的に複製されます。すべての k 回の応答が正常に永続化された後でのみ、エージェントはクライアントにコミット応答を送信できます。ログサーバ障害によりトランザクションシステムが復旧します。

2.3.3 ストレージのレプリケーション

各シャード (つまり、キー範囲) は、チームと呼ばれる k = f + 1 台のストレージ サーバーに非同期的に複製されます。ストレージ サーバーは通常、複数のシャードをホストして、多くのチームにデータを均等に分散します。ストレージ サーバーに障害が発生すると、データ アロケータがトリガーされ、障害が発生したプロセスを含むチームから他の正常なチームにデータが移動されます。ストレージ チームの抽象化はコピーセットよりも複雑であることに注意してください。

同時障害によるデータ損失の可能性を減らすために、FoundationDB では、レプリカ グループ内の最大 1 つのプロセスがホスト、ラック、可用性ゾーンなどの障害ドメインに配置されるようにします。各チームには少なくとも 1 つのプロセスがアクティブであることが保証されており、対応する障害ドメインのいずれかがまだ使用可能な場合、データは失われません。

2.4 シミュレーションテスト

分散システムのテストとデバッグは、困難で非効率な作業です。この問題は、FoundationDB にとって特に深刻です。その強力な同時実行制御コントラクトが失敗すると、上位層システムにほぼ任意の損害が発生する可能性があります。したがって、野心的なエンドツーエンドのテスト アプローチが最初から採用されました。つまり、ランダムに生成された合成ワークロードとフォールト インジェクションとともに、決定論的な離散イベント シミュレーションで実際のデータベース ソフトウェアを実行するというものでした。過酷なシミュレーション環境では、データベース内でエラーがすぐに引き起こされますが、決定論により、そのような各エラーを確実に再現して調査できます。

2.4.1 決定論的シミュレータ

FoundationDB には、このテスト手法が最初から組み込まれています。すべてのデータベース コードは決定的であり、マルチスレッドの同時実行を回避します (代わりに、コアごとに 1 つのデータベース ノードがデプロイされます)。以下の図は、FoundationDB シミュレータ プロセスを示しています。このプロセスでは、ネットワーク、ディスク、時間、擬似乱数ジェネレータなど、非決定性と通信のすべてのソースが抽象化されています。FoundationDB は、async/await のような同時実行プリミティブと自動キャンセルを追加する新しい C++ 構文拡張機能である Flow で書かれており、同時実行性の高いコードを確定的な方法で実行できます。Flow は、FoundationDB サーバー プロセスのさまざまな操作を、Flow ランタイム ライブラリによってスケジュールされた複数のアクターに抽象化するアクター プログラミング モデルを提供します。シミュレーター プロセスは、単一の離散イベント シミュレーションでシミュレートされたネットワークを介して相互に通信する複数の FoundationDB サーバーを生成できます。実稼働実装は、関連するシステム コールへの単純なブリッジです。

fe108f04160d0c905812f98bf9eff2c1.jpeg

エミュレーターは、シミュレートされたネットワーク上でシミュレートされた FoundationDB サーバーと通信する複数のワークロードを実行します。これらのワークロードには、フォールト挿入命令、シミュレートされたアプリケーション、データベース構成の変更、内部データベース関数呼び出しが含まれます。ワークロードはさまざまな機能をテストするために構成でき、包括的なテスト ケースを構築するために再利用できます。

2.4.2 テストエージェント

FoundationDB は、さまざまなテスト エージェントを使用してシミュレーションの障害を検出します。ほとんどの合成ワークロードには、トランザクションのアトミック性と分離によってのみ維持できるデータの不変条件をチェックするなど、データベースのコントラクトとプロパティを検証するためのアサーションが組み込まれています。アサーションは、「ローカル」で検証できるプロパティをチェックするためにコード ベース全体で使用されます。回復可能性 (最終的な可用性) などのプロパティは、(データベースの可用性を破壊するほどの一連の障害が発生した後) モデル化されたハードウェア環境を回復可能な状態に戻し、クラスターが最終的に回復することを確認することによって確認できます。

2.4.3 フォールトインジェクション

挿入されたマシン、ラック、およびデータセンターの障害と再起動、さまざまなネットワーク障害、パーティショニングと遅延の問題、ディスクの動作 (マシンの再起動時の非同期書き込みの破損など)、およびランダム化されたイベントをシミュレートします。このさまざまな障害挿入により、特定の障害に対するデータベースの回復力がテストされ、シミュレーション内の状態の多様性が増加します。フォールト注入の分布は、システムが小さな状態空間に入る原因となる過度の故障率を避けるために慎重に調整されています。FoundationDB 自体はシミュレータと連携して、非公式に「バグ化」と呼ばれる高度なフォールト挿入技術を通じて、まれな状態やイベントをより一般的にします。

シミュレータは、コード ベースの多くの場所で、正常に成功した操作で不必要にエラーを返す、通常は高速な操作で遅延を挿入する、例外を選択するなど、異常な (ただし契約違反ではない) 動作の挿入を許可します。数値調整パラメータなど これは、ネットワークおよびハードウェア レベルでの障害挿入によって補完されます。チューニング パラメーターのランダム化により、正確性を確保するために特定のパフォーマンス チューニング値が誤って必要になることがなくなります。スウォーム テストは、シミュレーション実行の多様性を最大化するために広く使用されています。各実行では、ランダムなクラスター サイズと構成、ランダムなワークロード、ランダムなフォールト挿入パラメーター、ランダムな調整パラメーターが使用され、バグ修正ポイントのランダムなサブセットが有効または無効になります。

私たちは、FoundationDB の Swarm テスト フレームワークをオープンソース化しました。条件付きカバレッジ マクロは、シミュレーションの有効性を評価および調整するために使用されます。たとえば、新しいコードでバッファーがいっぱいになることはめったにないことを懸念する開発者は、行 TEST(buffer.is_full()) を追加できます; シミュレーション結果の分析により、その条件に到達した異なるシミュレーションの実行回数がわかります。数値が低すぎるかゼロの場合は、バグ修正、ワークロード、またはフォールト挿入機能を追加して、シナリオが完全にテストされていることを確認できます。

2.4.4 エラー発見の遅れ

バグを迅速に見つけることは、運用前のテストでバグに遭遇する場合と、エンジニアリングの生産性を向上させる場合の両方において重要です。単一のコミットですぐに発見されたバグは、そのコミットまで簡単に追跡できます。シミュレータ内の CPU 使用率が低い場合、シミュレータはクロックを次のイベントに早送りできるため、離散イベント シミュレーションを任意の速度で実行できます。分散システムのバグの多くは発見するのに時間がかかり、長期間低使用率でシミュレーションを実行すると、「現実の」エンドツーエンドのテストよりもコアごとに多くのバグを発見できます。さらに、ランダム テストには恥ずかしいほどの量の並列処理があり、FoundationDB 開発者は、これまでテスト プロセスを回避してきた非常にまれなバグを発見することを期待して、メジャー リリースの前に多数のテストを「バースト」することができ、実際に実行しています。検索スペースは事実上無限であるため、スクリプトによる機能テストやシステム テストとは対照的に、より多くのテストを実行すると、より多くのコードがカバーされ、より多くの潜在的なバグが発見されます。

2.4.5 シミュレーションテストの制限

シミュレーションでは、不完全な負荷分散アルゴリズムなどのパフォーマンスの問題を確実に検出できません。また、サードパーティのライブラリや依存関係、さらにはフローに実装されていないパーティのコードをテストすることもできません。したがって、外部システムへの依存をほとんど回避します。最後に、重要な依存システム (ファイル システムやオペレーティング システムなど) のバグやその規則の誤解により、FoundationDB でエラーが発生する可能性があります。たとえば、一部のバグは、実際のオペレーティング システムの規約が予想よりも弱いことが原因で発生します。

4. 評価方法

合成ワークロードを使用して FoundationDB のパフォーマンスを評価します。具体的には、(1) 設定されたランダム鍵の数を更新するブラインド書き込み、(2) ランダム鍵から開始して設定された連続鍵の数を取得するインターバル読み取り、(3) ランダムな n 個の鍵を取得するポイント読み取りの 4 種類があります。 (4) ポイント書き込み、m 個のランダムなキーを取得し、別の m 個のランダムなキーを更新します。書き込みおよび読み取りのパフォーマンスはブラインド書き込みとインターバル読み取りによって評価され、スポット読み取りとスポット書き込みを併用して読み取りと書き込みの混合パフォーマンスを評価します。データセットが StorageServer のメモリに完全にキャッシュされないように注意してください。

書き込みスループットが最大になると、ログ サーバーの CPU 使用率は飽和状態に達します。読み取り操作と書き込み操作の両方で、トランザクション内の操作の数を増やすとスループットが向上します。ただし、操作数をさらに増やしても大きな変化は生じず、パーサーとプロキシの CPU 使用率が飽和に達する可能性があります。コミット リクエストには複数のホップと 3 つのログ サーバーへの永続性が含まれるため、読み取りおよび読み取りバージョンよりも待ち時間が長くなります。バッチ処理はスループットの維持に役立ちますが、飽和によりコミット レイテンシーが急増する可能性があります。

顧客対応という性質上、これらのクラスターの高可用性には再構成時間を短縮することが重要です。このように回復時間が短いのは、データやトランザクション ログのサイズによって制限されず、システム メタデータのサイズによってのみ制限されるためです。リカバリ中、読み取りおよび書き込みトランザクションは一時的にブロックされ、タイムアウト後に再試行されます。ただし、クライアントの読み取りには影響しません。これらの再構成の原因には、ソフトウェアまたはハードウェアの障害による自動フェイルオーバー、ソフトウェアのアップグレード、データベース構成の変更、運用上の問題の手動処理などが含まれます。

5. FoundationDB のコア機能

5.1. アーキテクチャ設計

分割統治の設計原則は、クラウド展開を可能にする上で重要な役割を果たし、データベースのスケーラビリティとパフォーマンスの両方を可能にします。

まず、トランザクション システムをストレージ層から分離することで、コンピューティング リソースとストレージ リソースのより柔軟な独立した展開と拡張が可能になります。さらに、検証済みレプリカと同様に、ログ サーバーの導入により、一部のマルチリージョン運用環境で高可用性を実現するために必要なストレージ サーバー (フル レプリカ) の数が大幅に削減されます。運用スタッフは、パフォーマンスとコストを最適化するために、さまざまなタイプのサーバー インスタンスに FoundationDB のさまざまな役割を自由にデプロイすることもできます。

第 2 に、この疎結合設計により、既存の SQLite エンジンを RocksDB に置き換えるなど、データベースの機能を拡張できます。

最後に、データ ディストリビュータとストリーム周波数制御をシーケンサから分離する、ストレージ キャッシュを追加する、プロキシを読み取りバージョン プロキシと送信プロキシに分割するなど、機能を独立した役割に特化することによって、多くのパフォーマンスの向上を達成できます。この設計パターンは、新しい機能を頻繁に追加し、機能を拡張するという目標を達成します。

5.2. シミュレーションテスト

シミュレーション テストにより、FoundationDB は小規模なチームでも高い開発速度を維持できます。これは、バグの導入とその発見の間の遅延を短縮し、問題の決定論的な再現を可能にすることによって実現されます。たとえば、ログを追加してもイベントの決定的なシーケンスには影響しないため、正確な再現が保証されます。このデバッグ方法は、通常の実稼働環境でのデバッグよりもはるかに生産的です。現実の環境で初めてバグが発見されるというまれなケースでは、デバッグ プロセスは通常、問題がシミュレーションで再現できるまでシミュレーションの機能または精度を改善することから始まり、その後、通常のデバッグ プロセスが始まります。シミュレーションによる厳密な正確性テストにより、FoundationDB は非常に信頼性が高くなります。

シミュレーション テストは、依存関係を排除し、フローに実装することで、シミュレーション可能性テストの限界を押し広げ続けています。たとえば、FoundationDB の初期バージョンは調整のために Apache Zookeeper に依存していましたが、Flow に実装された新しい Paxos に置き換えられました。

5.3. 迅速な回復

高速リカバリは可用性の向上に役立つだけでなく、ソフトウェアのアップグレードや構成の変更を大幅に簡素化し、迅速化します。分散システムをアップグレードする従来の方法は、問題が発生した場合にロールバックできるようにローリング アップグレードを実行することです。ローリング アップグレードの期間は、数時間から数日かかる場合があります。対照的に、FoundationDB はすべてのプロセスを同時に (通常は数秒以内に) 再起動することでアップグレードを実行できます。さらに、このアップグレード パスにより、異なるバージョン間のプロトコル互換性の問題が簡素化され、異なるソフトウェア バージョン間で RPC プロトコルの互換性を確保する必要がなくなります。さらに、クイック リカバリは、ソフトウェア復活テクノロジと同様に、根本的なエラーを自動的に修正できる場合があります。

5.4. 5 秒の MVCC ウィンドウ

FoundationDB は、トランザクション システムとストレージ サーバーのメモリ使用量を制限するために 5 秒のマルチバージョン同時実行制御ウィンドウを選択します。これは、マルチバージョン データがパーサーとストレージ サーバーのメモリに保存され、トランザクションのサイズが制限されるためです。この 5 秒のウィンドウは、ほとんどのオンライン トランザクション処理のユースケースには十分な長さです。したがって、時間制限を超えると、アプリケーションの非効率性が露呈することがよくあります。

5 秒以上かかる一部のトランザクションの場合、多くは小さなトランザクションに分割して処理できます。たとえば、FoundationDB の継続的バックアップ プロセスは、キー空間全体をスキャンし、キー範囲のスナップショットを作成します。5 秒の制限により、スキャン プロセスは多くの小さな範囲に分割され、各範囲が 5 秒以内に完了できるようになります。実際、これは一般的なパターンです。トランザクションは複数のタスクを作成し、各タスクはトランザクション内でさらに分割または実行できます。FoundationDB はこのパターンを「TaskBucket」と呼ばれる抽象化で実装しており、バックアップ システムはこれに大きく依存しています。

6. まとめ

FoundationDB は、OLTP クラウド サービス用に設計された分散キーバリュー ストアです。主なアイデアは、トランザクション処理をロギングとストレージから切り離すことです。この分離されたアーキテクチャにより、読み取り処理と書き込み処理の分離と水平拡張が可能になります。トランザクション システムは、オプティミスティック同時実行制御 (OCC) とマルチバージョン同時実行制御 (MVCC) を組み合わせて、厳密なシリアル化を保証します。ロギングと確定的なトランザクション順序の分離により、リカバリ プロセスが大幅に簡素化され、その結果、リカバリ時間が非常に速くなり、可用性が向上します。最後に、決定論的シミュレーションと確率論的シミュレーションにより、データベース実装の正確性が保証されます。

【参考資料と関連書籍】

おすすめ

転載: blog.csdn.net/wireless_com/article/details/132797823