CurveのRaftベースの書き込みレイテンシの最適化

1背景

Curve(https://github.com/opencurve/curve)は、クラウドネイティブのソフトウェアディファインドストレージシステムであり、NetEase Shufanが独自に設計および開発した、高性能、簡単な操作とメンテナンス、および完全なシナリオのサポートを備えています。 Ceph独自のアーキテクチャではサポートできないシナリオもあります。要件については、2020年7月に正式にオープンソースになります。現在、CurveBSとCurveFSの2つのサブプロジェクトで構成されており、それぞれ分散ブロックストレージと分散ファイルストレージを提供します。その中で、CurveBSは、オープンソースクラウドネイティブデータベースPolarDB for PostgreSQLの分散共有ストレージベースになり、ストレージとコンピューティングの分離アーキテクチャをサポートしています。

Curve_logo

CurveBSの設計では、データサーバーChunkServerのデータ整合性は、raftに基づく分散整合性プロトコルによって実現されます。

ラフトの一貫性に基づく書き込み操作の一般的な実装を次の図に示します。

一般的な3つのコピーを例にとると、一般的なプロセスは次のとおりです。

  1. 最初に、クライアントは書き込み操作を送信し(ステップ1)、書き込み操作がリーダーに到達した後(リーダーがいない場合は、リーダーの選出が最初に実行され、書き込み操作は常に最初にリーダーに送信されます)、リーダーは最初に書き込み操作を受信して​​WAL(先行書き込みログ)を生成し、WALをローカルストレージエンジンに永続化し(ステップ2)、同時にログ送信rpcを介して2人のフォロワーにWALを並行して送信します(ステップ3)。
  2. リーダーからログ要求を受信した後、2人のフォロワーは受信したログをローカルストレージエンジンに保持し(ステップ4)、ログ書き込みの成功をリーダーに返します(ステップ5)。
  3. 一般的に、リーダーログは常に最初に配置を完了します。この時点で、別のフォロワーのログから正常に応答を受け取った後、ほとんどの条件が満たされ、書き込みOpがステートマシンに送信され、書き込みOpが送信されます。ローカルストレージエンジンに書き込みます(ステップ6)。
  4. 上記の手順が完了すると、書き込み操作が完了し、書き込みの成功をクライアントに返すことができます(手順7)。後で、2人のフォロワーもリーダーのログコミットのメッセージを受信し、書き込み操作をローカルストレージエンジンに適用します(ステップ9)。

CurveBSの現在の実装では、raftがローカルストレージエンジン(データストア)に適用されてOpを書き込む場合、O_DSYNCに基づく同期書き込み方式が使用されます。実際、ログがラフトに基づいて書き込まれている場合、書き込みOpは同期せずにクライアントに安全に戻ることができるため、この記事で説明する書き込み遅延を最適化する原則である書き込みOpの遅延を減らすことができます。

コードは次のとおりで、チャンクファイルのOpen関数でO_DSYNCフラグが使用されます。

CSErrorCode CSChunkFile::Open(bool createFile) {
    WriteLockGuard writeGuard(rwLock_);
    string chunkFilePath = path();
    // Create a new file, if the chunk file already exists, no need to create
    // The existence of chunk files may be caused by two situations:
    // 1. getchunk succeeded, but failed in stat or load metapage last time;
    // 2. Two write requests concurrently create new chunk files
    if (createFile
        && !lfs_->FileExists(chunkFilePath)
        && metaPage_.sn > 0) {
        std::unique_ptr<char[]> buf(new char[pageSize_]);
        memset(buf.get(), 0, pageSize_);
        metaPage_.version = FORMAT_VERSION_V2;
        metaPage_.encode(buf.get());

        int rc = chunkFilePool_->GetFile(chunkFilePath, buf.get(), true);
        // When creating files concurrently, the previous thread may have been
        // created successfully, then -EEXIST will be returned here. At this
        // point, you can continue to open the generated file
        // But the current operation of the same chunk is serial, this problem
        // will not occur
        if (rc != 0  && rc != -EEXIST) {
            LOG(ERROR) << "Error occured when create file."
                       << " filepath = " << chunkFilePath;
            return CSErrorCode::InternalError;
        }
    }
    int rc = lfs_->Open(chunkFilePath, O_RDWR|O_NOATIME|O_DSYNC);
    if (rc < 0) {
        LOG(ERROR) << "Error occured when opening file."
                   << " filepath = " << chunkFilePath;
        return CSErrorCode::InternalError;
    }
...
}

2問題分析

以前にO_DSYNCが使用された理由は、ラフトスナップショットのシナリオで、データがディスクに配置されていない場合、スナップショットが開始されてログも切り捨てられると、データが失われる可能性があることを考慮したためです。 [書き込みの適用]または[同期しない]を変更します。最初にこの問題を解決する必要があります。質問。
最初に、次の図に示すように、CurveChunkServerでスナップショットを作成するプロセスを分析する必要があります。

スナップショットプロセスのいくつかの重要なポイント:

  1. スナップショットを作成するプロセスは、StateMachineに入り、読み取りおよび書き込み操作を適用することにより、StateMachineキューで実行されます。
  2. スナップショットに含まれるlast_applied_indexは、StateMachineを呼び出してスナップショットを保存する前に保存されています。つまり、スナップショットが実行されるときに、保存されたlast_applied_indexがStateMachineによって適用されていることを保証する必要があります。
  3. また、StatusMachineの書き込みOp Applyを変更してO_DSYNCを削除する場合、つまり同期しない場合、スナップショットがlast_applied_indexに切り捨てられ、Opを書き込むApplyが実際にはディスクに同期されていない可能性があります。解決する必要のある問題。

3つのソリューション

2つの解決策があります:

3.1オプション1

  1. スナップショットでは、last_applied_indexまでの適用の書き込み操作を確実に同期する必要があるため、スナップショットの実行時に同期を実行するのが最も簡単な方法です。3つの方法があります。最初の方法は、ディスク全体でFsSyncを実行することです。2番目の方法は、スナップショットプロセスで、現在のコピー内のすべてのチャンクファイルをスナップショットメタデータに保存する必要があるため、当然、現在のスナップショットのすべてのファイル名のリストがあり、すべてのファイルのスナップショットを取得できます。同期一つずつ。3番目の方法は、レプリケーショングループ内のチャンクの数が多く、書き込まれるチャンクの数が少ない場合があるため、データストアが書き込み操作を実行するときに同期する必要があるチャンクIDのリストを保存できます。スナップショットを作成するとき、上記のリストのチャンクが同期する限り。
  2. 上記の3つの同期方法には時間がかかる可能性があり、スナップショットプロセスは現在ステートマシンで「同期的に」実行されています。つまり、スナップショットプロセスはIOをブロックするため、スナップショットプロセスの変更を検討できます。同時に、この変更により、スナップショットを作成する際のIOジッターへの影響を減らすこともできます。

3.2オプション2

2番目のソリューションはより複雑です。O_DSYNC書き込みが削除されるため、last_applied_indexまでの書き込み操作がすべて同期されることを保証できません。ApplyIndexを2つ、つまりlast_applied_indexとlast_synced_indexに分割することを検討してください。具体的な方法は次のとおりです。

  1. last_applied_indexを2つのlast_applied_indexとlast_synced_indexに分割します。last_applied_indexは同じ意味です。完全なFsSyncを実行した後、last_synced_indexを増やし、last_applied_indexをlast_synced_indexに割り当てます。
  2. 前述のスナップショットの手順で、スナップショットの前に保存されたlast_applied_indexをスナップショットメタデータのlast_synced_indexに変更して、スナップショットの作成時にスナップショットに含まれるデータが同期されていることを確認します。
  3. FsSyncを定期的に実行し、タイマーを介して同期タスクを定期的に実行するには、バックグラウンドスレッドが必要です。実行プロセスは次のようになります。最初に、バックグラウンド同期スレッドがすべてのステートマシンをトラバースし、現在のすべてのlast_applied_indexを取得し、FsSyncを実行してから、上記のlast_applied_indexをステートマシンのlast_synced_indexに割り当てます。

3.3 2つのスキームの長所と短所:

  1. スキーム1は比較的簡単に変更でき、カーブコードのみを変更する必要があり、ブラフトコードを変更する必要はありません。これは、ブラフトフレームワークに影響を与えません。スキーム2はより複雑で、ブラフトコードを変更する必要があります。
  2. スナップショット実行パフォーマンスの観点から、ソリューション1は元のスナップショットの速度を低下させます。元のスナップショットは同期であるため、この変更では非同期スナップショット実行に変更するのが最適です。もちろん、ソリューション2は元のスナップショットを次のように最適化することもできます。非同期であるため、IOへの影響が軽減されます。

3.4採用された計画:

  1. 最初の実装方法が採用されているのは、braftの非侵入的な変更が、コードの安定性とその後の互換性に有益であるためです。
  2. チャンクの同期方法については、ソリューション1の3番目の方法を採用しています。つまり、データストアが書き込み操作を実行するときに、同期する必要のあるチャンクIDのリストを保存すると同時に、スナップショットを作成します。上記のリストのチャンクIDを同期して、すべてのチャンクがディスクに配置されるようにします。このアプローチにより、すべてのチャンクサーバーで頻繁に発生するFsSyncによるIOへの影響を回避できます。また、上記同期を行う場合は、バッチ同期方式を採用し、同期のチャンクIDを重複排除することで、実際の同期回数を減らし、フォアグラウンドIOへの影響を軽減します。

4 POC

以下は、O_DSYNCが直接削除されたときに、IOPS、遅延などがさまざまなシナリオに最適化されているかどうかをテストするpocテストです。テストの各グループは少なくとも2回テストされ、1つのグループが選択されます。

テストで使用されるfioテストパラメータは次のとおりです。

  • 4Kランダム書き込みテストシングルボリュームIOPS:
[global]
rw=randwrite
direct=1
iodepth=128
ioengine=libaio
bsrange=4k-4k
runtime=300
group_reporting
size=100G

[disk01]
filename=/dev/nbd0
  • 512Kシーケンシャル書き込みテストの単一ボリューム帯域幅:
[global]
rw=write
direct=1
iodepth=128
ioengine=libaio
bsrange=512k-512k
runtime=300
group_reporting
size=100G
 
 
[disk01]
filename=/dev/nbd0
  • 4Kシングルデプスランダム書き込みテストのレイテンシー:
[global]
rw=randwrite
direct=1
iodepth=1
ioengine=libaio
bsrange=4k-4k
runtime=300
group_reporting
size=100G

[disk01]
filename=/dev/nbd0

クラスター構成:

マシーン 役割 ディスク
server1 client、mds、chunkserver ssd / hdd * 18
server2 mds、chunkserver ssd / hdd * 18
server3 mds、chunkserver ssd / hdd * 18

4.1HDD比較テスト結果

シーン 最適化前 最適化
シングルボリューム4Kランダム書き込み IOPS = 5928、BW = 23.2MiB / s、lat = 21587.15usec IOPS = 6253、BW = 24.4MiB / s、lat = 20465.94usec
シングルボリューム512Kシーケンシャル書き込み IOPS = 550、BW = 275MiB / s、lat =232.30msec IOPS = 472、BW = 236MiB / s、lat = 271.14msec
シングルボリューム4Kシングルデプスランダム書き込み IOPS = 928、BW = 3713KiB / s、lat = 1074.32usec IOPS = 936、BW = 3745KiB / s、lat = 1065.45usec

上記のテストでは、RAIDカードキャッシュ戦略のライトバックのパフォーマンスはわずかに改善されていますが、改善の効果は明ら​​かではありません。512Kのシーケンシャル書き込みシナリオでは、パフォーマンスがわずかに低下し、深刻なIOジッターがあることもわかります。 O_DSYNCを削除した後。

RAIDカードのキャッシュにより、パフォーマンスの向上は明らかではないと思われるため、RAIDカードのキャッシュポリシーをライトスルーモードに設定し、テストを続行します。

シーン 最適化前 最適化
シングルボリューム4Kランダム書き込み IOPS = 993、BW = 3974KiB / s、lat =128827.93usec IOPS = 1202、BW = 4811KiB / s、lat = 106426.74usec
シングルボリュームシングルデプス4Kランダム書き込み IOPS = 21、BW = 85.8KiB / s、lat = 46.63msec IOPS = 38、BW = 154KiB / s、lat = 26021.48usec

RAIDカードキャッシュ戦略のライトスルーモードでは、パフォーマンスの向上がより明白になり、単一ボリュームの4Kランダム書き込みが約20%向上します。

4.2SSD比較テスト結果

SSDのテストはRAIDパススルーモード(JBOD)でテストされ、パフォーマンスの比較は次のとおりです。

シーン 最適化前 最適化
シングルボリューム4kランダム書き込み bw = 83571KB / s、iops = 20892、lat = 6124.95usec bw = 178920KB / s、iops = 44729、lat = 2860.37usec
シングルボリューム512kシーケンシャル書き込み bw = 140847KB / s、iops = 275、lat = 465.08msec bw = 193975KB / s、iops = 378、lat = 337.72msec
シングルボリュームシングルデプス4kランダム書き込み bw = 3247.3KB / s、iops = 811、lat = 1228.62usec bw = 4308.8KB / s、iops = 1077、lat = 925.48usec

上記のシナリオでは、テスト効果が大幅に向上していることがわかります。4Kランダム書き込みシナリオでは、IOPSがほぼ100%増加し、512Kシーケンシャル書き込みも大幅に向上し、レイテンシも大幅に短縮されます。 。

5まとめ

上記の最適化はCurveブロックストレージに適用できます。RAFT分散整合性プロトコルに基づいて、ローカルストレージエンジンに適用されるRAFTステートマシンの即時ディスクドロップを減らすことができるため、Curveブロックストレージの書き込み遅延を減らし、書き込みを改善できます。 Curveブロックストレージのパフォーマンス。SSDシナリオでテストしたところ、パフォーマンスが大幅に向上しました。HDDシナリオの場合、RAIDカードキャッシュの存在は通常有効になっているため、効果は明ら​​かではありません。そのため、HDDシナリオでは、この最適化を有効にしないことを選択できるスイッチを提供します。

この記事の著者:NeteaseShufanのシニアシステム開発エンジニアであるXuChaojie

{{o.name}}
{{m.name}}

おすすめ

転載: my.oschina.net/u/4565392/blog/5519430