永続ディスクのパフォーマンスの最適化

永続ディスクの最適化

永続ディスクは、ディスクタイプの表で示したパフォーマンスを実現できますが、VM の使用量が十分でないとパフォーマンスの上限を達成することはできません。パフォーマンスのニーズに合わせて永続ディスクのボリューム サイズを調整した後、アプリケーションとオペレーティングシステムの調整が必要になることがあります。

以降のセクションでは、パフォーマンス向上のためにチューニングできるいくつかの重要な要素と、その一部を特定のタイプのワークロードに適用する方法を説明します。

高い I/O 負荷の上限を 50 TB スパンにする

I/O 負荷が高い場合は、スパンの上限を 50 TB にすると、パフォーマンスが最大になります。複数の永続ディスクから 50 TB 以下のスパンを構成した場合、単一の 50 TB スパンと同等のパフォーマンスが得られます。スパンとは、単一ディスク上の論理ブロック アドレスの連続した範囲のことです。

遅延初期化を無効にし、DISCARD コマンドを有効にする

永続ディスクは、DISCARD または TRIM コマンドをサポートしています。このコマンドを使用すると、オペレーティング システムはブロックが不要になったことをディスクに通知できます。DISCARD のサポートにより、OS は不要なディスク ブロックをマークすることで、ブロックをゼロ化するコストをかけずに済みます。

ほとんどの Linux オペレーティング システムでは、インスタンスに永続ディスクをマウントするときに DISCARD を有効にします。Windows Server 2012 R2 インスタンスでは、永続ディスクをマウントすると DISCARD がデフォルトで有効になります。

DISCARD を有効にすると、一般的なランタイム パフォーマンスが向上し、最初にマウントされるときのディスクのパフォーマンスも向上します。ディスク ボリューム全体のフォーマットには時間がかかる可能性があるため、「ゼロ初期化までしない(Lazy)フォーマット」が一般的な方法ですが、このフォーマットの欠点は、ボリュームの初回マウント時に時間がかかることが多い点です。遅延初期化を無効にして、DISCARD コマンドを有効にすることで、フォーマットとマウントの時間を短縮できます。

  • 遅延初期化を無効にし、フォーマット中に DISCARD を有効にするには、次のパラメータを mkfs.ext4 に渡します。

    -E lazy_itable_init=0,lazy_journal_init=0,discard
    

    lazy_journal_init=0 パラメータは、CentOS 6RHEL 6 のどちらのイメージのインスタンスでも機能しません。これらのインスタンスの場合は、このパラメータなしで永続ディスクをフォーマットします。

    -E lazy_itable_init=0,discard
    
  • マウント時に DISCARD コマンドを有効にするには、次のフラグを mount コマンドに渡します。

    -o discard
    

永続ディスクは、discard オプションを有効にしても正常に動作します。ただし、discard オプションの使用に加えて、またはその代わりに、オプションとして定期的に fstrim を実行することもできます。discard オプションを使用しない場合は、fstrim を実行した後、ディスクのスナップショットを作成します。ファイル システムをトリミングすることで、より小さなスナップショット イメージを作成でき、スナップショットの保存コストを削減できます。

I/O キューの深さ

多くのアプリケーションには、I/O キューの深さに影響を及ぼす設定があります。キューが深いほど IOPS は増加しますが、レイテンシも増加します。キューを浅くすると、各 I/O のレイテンシは減少しますが、最大 IOPS が低下する可能性があります。

先読みキャッシュ

I/O パフォーマンスを向上させるため、オペレーティング システムは先読みなどの技法を導入しています。先読みでは、要求されたものより多くのファイルが、後続の読み取りでそのデータが必要になるという想定の下で、メモリに読み込まれます。先読み量を多くするほどスループットは向上しますが、メモリと IOPS は犠牲になります。先読み量を少なくすると、IOPS は向上しますが、スループットは低下します。

Linux システムでは、blockdev コマンドを使用して先読み量の値を取得や設定できます。

$ sudo blockdev --getra /dev/[DEVICE_ID]
$ sudo blockdev --setra [VALUE] /dev/[DEVICE_ID]

先読み量の値は <desired_readahead_bytes> / 512(バイト)です。

たとえば、先読み量が 8 MB の場合、8 MB は 8,388,608 バイト(8×1,024×1,024)です。

8388608 bytes / 512 bytes = 16384

blockdev を 16384 に設定します。

$ sudo blockdev --setra 16384 /dev/[DEVICE_ID]

空き CPU

永続ディスクの読み取りと書き込みには、VM の CPU サイクルが必要です。非常に高い安定した IOPS レベルを実現するには、I/O の処理に自由に使用できる CPU が必要です。

IOPS 指向のワークロード

SQL か NoSQL かを問わず、データベースはデータへランダムにアクセスする傾向があります。IOPS 指向のワークロードに対する Google の推奨値は次のとおりです。

  • I/O キューの深さの値は 400~800 IOPS あたり 1 にし、大きなボリュームでは上限を 64 にします。

  • 空き CPU の数を、ランダム読み取り 2,000 IOPS ごとに 1 基、ランダム書き込み 2,500 IOPS ごとに 1 基にします。

MongoDBApache Cassandra、その他のデータベース アプリケーションのベスト プラクティスに関するドキュメントでは、通常、先読み量を少なくするよう推奨されています。

スループット指向のワークロード

Hadoop ジョブのようなストリーミング オペレーションのパフォーマンスは、高速な順次読み取りによって向上します。I/O サイズが大きいほど、ストリーミングのパフォーマンスは向上します。スループット指向のワークロードでは、256 KB 以上の I/O サイズをおすすめします。

標準永続ディスクのパフォーマンスの最適化

標準永続ディスクの最大スループット レベルを安定して達成するには、次のベスト プラクティスを使用します。

  • 可能な場合は並行シーケンシャル IO ストリームを使用する

    標準永続ディスクは、実際のハードディスクと同じようにディスクのシーケンシャル アクセスの I/O パフォーマンスを最適化するように設計されています。

    複数の連続するストリームに I/O を分散すると、パフォーマンスが大幅に向上します。最適なレベルの安定性を実現するには、8 つ以上の連続するストリームを使用します。

  • 大きい I/O サイズを使用する

    標準永続ディスクでは、上限でのスループットが非常に高くなっています。IOPS の上限とレイテンシによってアプリケーションのパフォーマンスが妨げられないようにするには、最低でも 256 KB 以上の I/O サイズを使用します。

    分散ファイル システムのアプリケーションには、大きなストライプ サイズを使用します。大きなストライプ サイズ(4 MB 以上)を使用するランダム I/O ワークロードは、ワークロードが複数のシーケンシャル ストリーム ディスク アクセスに類似しているほど、標準永続ディスクで優れたパフォーマンスを発揮します。

  • 十分な並列処理で I/O を実行する

    キューをできる限り深くすることで、OS の並列 I/O 処理を十分に活用します。標準永続ディスクでは、I/O レイテンシによるアプリケーションが妨げられることを回避するため、十分な深さを持つキューを使用することが特に重要です。

SSD 永続ディスクのパフォーマンスの最適化

ディスクタイプ別パフォーマンスの表には、SSD 永続ディスクで達成可能な最大パフォーマンスの値が示されています。これらの速度を達成するためにアプリケーションと VM のインスタンスを最適化するには、次のベスト プラクティスを使用します。

  • アプリケーションが十分な I/O を生成していることを確認する

    アプリケーションが生成する IOPS が前記の表で示されている上限より少ない場合、そのレベルの IOPS に達することはありません。たとえば、500 GB のディスクの場合、予想される IOPS の上限は 15,000 IOPS です。しかし、実際に生成される IOPS が少ないか、I/O オペレーションが 8 KB よりも大きい場合、15,000 IOPS に達することはありません。

  • 十分な並列処理で I/O を実行する

    キューを深くすることで、OS の並列 I/O 処理を十分に活用します。IOPS が 1,000 でも、キューの深さが 1 で同期 I/O を処理する場合、IOPS は表で説明されている上限よりもはるかに低くなります。アプリケーションでは、最低でも、400~800 IOPS ごとに 1 つのキューの深さを使用する必要があります。

  • I/O を生成しているインスタンスに十分な CPU を割り当てる

    VM インスタンスの CPU が不足していると、前述の IOPS をアプリケーションで管理することはできません。予想されるトラフィックの 2,000~2,500 IOPS ごとに使用可能な CPU を 1 つ用意することをおすすめします。

  • 大容量ディスク上で合理的な時間的データ局所性を実現するようにアプリケーションを最適化する

    短い期間にアプリケーションからディスクの各所に分散するデータにアクセスしている状態では(vCPU あたり数百 GB)、最適な IOPS を達成できません。最高のパフォーマンスを得るには、データの時間的局所性、ディスク断片化などの重み付け要因、アクセスしているディスク部分のランダム性を調べて最適化を図る必要があります。

  • OS の I/O スケジューラが特定のニーズを満たすように構成されていることを確認する

    Linux ベースのシステムでは、I/O スケジューラを noop に設定することで、SSD を使用するデバイスでの IOPS 数を最大化できます。

永続ディスクのパフォーマンス指標を確認する

永続ディスクのパフォーマンス指標は、Google Cloud の統合モニタリング ソリューションである Cloud Monitoring で確認できます。

指標の一部を活用して、ディスクがスロットリングされているかどうか、およびいつスロットリングされるのかを把握できます。スロットリングは、急増する I/O をなだらかにするために行われます。スロットリングを行うと、急増する I/O を一定期間にわたって分散できるので、ディスクのパフォーマンス上限に到達することはありますが、上限を超えることは決してありません。

詳細については、永続ディスクのパフォーマンス指標の確認をご覧ください。

永続ディスクのパフォーマンスをベンチマークする

永続ディスクのパフォーマンスをベンチマークするには、ddなどの他のディスク ベンチマーク ツールではなく FIO を使用します。デフォルトでは、dd で使用される I/O キューの深さは非常に浅く、ベンチマークの際に、精度の高いディスク パフォーマンス テストを行ううえで十分な I/O 回数とバイト数を生成できません。

また、dd で使用される特殊なデバイスには動作の非常に遅いものが多く、永続ディスクのパフォーマンスを正確に反映していません。通常は、永続ディスク パフォーマンスのベンチマークで /dev/urandom/dev/random/dev/zero などの特別なデバイスは使用しないでください。

実行中のインスタンスで使用されているディスクの IOPS とスループットを測定するには、測定用に作られた構成でファイル システムのベンチマークを行います。そうすることにより、既存のディスクの内容を失わずに現実的なワークロードをテストできます。既存のディスクでファイル システムをベンチマークすると、開発環境固有のさまざまな要素がベンチマーク結果に影響を与える可能性があり、その結果、ディスク パフォーマンスの上限に届かないことがある点にご注意ください。

永続ディスクの純粋なパフォーマンスを測定するには、ブロック デバイスを直接ベンチマークします。この方法を使用して、純粋なディスク パフォーマンスとディスク パフォーマンスの上限を比較します。

以下のコマンドは、apt パッケージマネージャーを使用して Debian または Ubuntu オペレーティング システムで動作します。

実行中のインスタンスのディスクの IOPS とスループットをベンチマークする

ディスクの内容を失うことなく、実行中のインスタンスのアクティブなディスクについて、現実的なワークロードの IOPS とスループットを測定する場合は、既存のファイル システムの新しいディレクトリに対してベンチマークを実行します。

  1. インスタンスに接続します。

  2. 依存関係をインストールします。

    sudo apt update
    sudo apt install -y fio
    
  3. ターミナルで、VM にアタッチされているディスクを一覧表示し、テストするディスクを見つけます。永続ディスクがまだフォーマットされていない場合は、ディスクをフォーマットしてマウントします。

    sudo lsblk
    
    NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
    sda      8:0    0   10G  0 disk
    └─sda1   8:1    0   10G  0 part /
    sdb      8:32   0  2.5T  0 disk /mnt/disks/mnt_dir
    

    この例では、デバイス ID が sdb になっている 2,500 GB の SSD 永続ディスクをテストします。

  4. ディスク上に新しいディレクトリ fiotest を作成します。この例では、ディスクは /mnt/disks/mnt_dir にマウントされます。

    TEST_DIR=/mnt/disks/mnt_dir/fiotest
    sudo mkdir -p $TEST_DIR
    
  5. I/O ブロックサイズを 1 MB、I/O の深さを 64 以上に指定し、複数の並列ストリーム(8 以上)で順次書き込みを実行して書き込みスループットをテストします。

    sudo fio --name=write_throughput --directory=$TEST_DIR --numjobs=8 \
    --size=10G --time_based --runtime=60s --ramp_time=2s --ioengine=libaio \
    --direct=1 --verify=0 --bs=1M --iodepth=64 --rw=write \
    --group_reporting=1
    
  6. I/O ブロックサイズを 4 KB、I/O の深さを 64 以上に指定し、順次書き込みを実行して書き込み IOPS をテストします。

    sudo fio --name=write_iops --directory=$TEST_DIR --size=10G \
    --time_based --runtime=60s --ramp_time=2s --ioengine=libaio --direct=1 \
    --verify=0 --bs=4K --iodepth=64 --rw=randwrite --group_reporting=1
    
  7. I/O ブロックサイズを 1 MB、I/O の深さを 64 以上に指定し、複数の並列ストリーム(8 以上)で順次書き込みを実行して読み取りスループットをテストします。

    sudo fio --name=read_throughput --directory=$TEST_DIR --numjobs=8 \
    --size=10G --time_based --runtime=60s --ramp_time=2s --ioengine=libaio \
    --direct=1 --verify=0 --bs=1M --iodepth=64 --rw=read \
    --group_reporting=1
    
  8. I/O ブロックサイズを 4 KB、I/O の深さを 64 以上に指定し、読み取り IOPS をテストします。

    sudo fio --name=read_iops --directory=$TEST_DIR --size=10G \
    --time_based --runtime=60s --ramp_time=2s --ioengine=libaio --direct=1 \
    --verify=0 --bs=4K --iodepth=64 --rw=randread --group_reporting=1
    
  9. クリーンアップします。

    sudo rm $TEST_DIR/write* $TEST_DIR/read*
    

純粋な永続ディスクのパフォーマンスをベンチマークする

開発環境以外で永続ディスク単体のパフォーマンスを測定する場合は、使い捨ての永続ディスクと VM でブロック デバイスの読み取りと書き込みのパフォーマンスをテストします。

以下コマンドは、VM に 2,500 GB の SSD 永続ディスクがアタッチされていることを前提としています。デバイスサイズが異なる場合は、--filesize 引数の値を変更します。このディスクサイズは、32 vCPU VM のスループットの上限値を達成するために必要な値です。詳細については、ブロック ストレージのパフォーマンスをご覧ください。

  1. インスタンスに接続します。

  2. 依存関係をインストールします。

    sudo apt update
    sudo apt install -y fio
    
  3. ディスクにゼロ以外のデータを入力します。空のブロックからの永続ディスク読み取りには、データを含むブロックとは異なるレイテンシ プロファイルがあります。読み取りレイテンシのベンチマークを実施する前に、データをディスクに入力しておくことをおすすめします。

    # Running this command causes data loss on the second device.
    # We strongly recommend using a throwaway VM and disk.
    sudo fio --name=fill_disk \
      --filename=/dev/sdb --filesize=2500G \
      --ioengine=libaio --direct=1 --verify=0 --randrepeat=0 \
      --bs=128K --iodepth=64 --rw=randwrite
    
  4. I/O サイズを 1 MB、I/O キューの深さを 64 以上にして、複数の並列ストリーム(8 以上)で順次書き込みを行い、書き込み帯域幅をテストします。

    # Running this command causes data loss on the second device.
    # We strongly recommend using a throwaway VM and disk.
    sudo fio --name=write_bandwidth_test \
      --filename=/dev/sdb --filesize=2500G \
      --time_based --ramp_time=2s --runtime=1m \
      --ioengine=libaio --direct=1 --verify=0 --randrepeat=0 \
      --bs=1M --iodepth=64 --rw=write --numjobs=8 --offset_increment=100G
    
  5. 書き込み IOPS をテストします。PD IOPS を最大にするには、I/O キューを深くする必要があります。たとえば、書き込みレイテンシが 1 ミリ秒の場合、VM は処理中の各 I/O に対して最大 1,000 IOPS を達成できます。15,000 の書き込み IOPS を達成するには、VM は少なくとも 15 の処理中 I/O を維持する必要があります。ディスクと VM が 30,000 の書き込み IOPS を達成するには、処理中の I/O 数は少なくとも 30 I/O である必要があります。I/O サイズが 4 KB より大きい場合、VM は IOPS の上限に達する前に帯域幅の制限に達することがあります。

    # Running this command causes data loss on the second device.
    # We strongly recommend using a throwaway VM and disk.
    sudo fio --name=write_iops_test \
      --filename=/dev/sdb --filesize=2500G \
      --time_based --ramp_time=2s --runtime=1m \
      --ioengine=libaio --direct=1 --verify=0 --randrepeat=0 \
      --bs=4K --iodepth=256 --rw=randwrite
    
  6. 書き込みレイテンシをテストします。I/O レイテンシをテストしている間、VM が帯域幅の制限または IOPS の上限に達しないようにします。そうしないと、レイテンシに実際の永続ディスクの I/O レイテンシが反映されません。たとえば、I/O の深さ 30 で IOPS の上限に達するときに fio コマンドでその 2 倍を指定した場合、合計 IOPS は変わらず、報告される I/O レイテンシは 2 倍になります。

    # Running this command causes data loss on the second device.
    # We strongly recommend using a throwaway VM and disk.
    sudo fio --name=write_latency_test \
      --filename=/dev/sdb --filesize=2500G \
      --time_based --ramp_time=2s --runtime=1m \
      --ioengine=libaio --direct=1 --verify=0 --randrepeat=0 \
      --bs=4K --iodepth=4 --rw=randwrite
    
  7. I/O サイズを 1 MB にし、I/O キューの深さを 64 以上にして、複数の並列ストリーム(8 以上)で順次読み取りを行い、読み取り帯域幅をテストします。

    sudo fio --name=read_bandwidth_test \
      --filename=/dev/sdb --filesize=2500G \
      --time_based --ramp_time=2s --runtime=1m \
      --ioengine=libaio --direct=1 --verify=0 --randrepeat=0 \
      --bs=1M --iodepth=64 --rw=read --numjobs=8 --offset_increment=100G
    
  8. 読み取り IOPS をテストします。PD IOPS を最大にするには、I/O キューを深くする必要があります。たとえば、I/O サイズが 4 KB より大きい場合、VM は IOPS の上限に達する前に帯域幅の制限に達することがあります。最大 10 万の読み取り IOPS を達成するには、このテストに --iodepth=256 を指定します。

    sudo fio --name=read_iops_test \
      --filename=/dev/sdb --filesize=2500G \
      --time_based --ramp_time=2s --runtime=1m \
      --ioengine=libaio --direct=1 --verify=0 --randrepeat=0 \
      --bs=4K --iodepth=256 --rw=randread
    
  9. 読み取りレイテンシをテストします。現実的なレイテンシ測定を行うには、ディスクにデータを書き込む必要があります。また、テスト中に VM が IOPS またはスループットの上限に達しないことが重要です。永続ディスクは、飽和限界に達すると受信 I/O を差し戻し、これが I/O レイテンシの「見せかけ」の増加として反映されます。

    sudo fio --name=read_latency_test \
      --filename=/dev/sdb --filesize=2500G \
      --time_based --ramp_time=2s --runtime=1m \
      --ioengine=libaio --direct=1 --verify=0 --randrepeat=0 \
      --bs=4K --iodepth=4 --rw=randread
    
  10. 順次読み取り帯域幅をテストします。

    sudo fio --name=read_bandwidth_test \
      --filename=/dev/sdb --filesize=2500G \
      --time_based --ramp_time=2s --runtime=1m \
      --ioengine=libaio --direct=1 --verify=0 --randrepeat=0 \
      --numjobs=4 --thread --offset_increment=500G \
      --bs=1M --iodepth=64 --rw=read
    
  11. 順次書き込み帯域幅をテストします。

    sudo fio --name=write_bandwidth_test \
      --filename=/dev/sdb --filesize=2500G \
      --time_based --ramp_time=2s --runtime=1m \
      --ioengine=libaio --direct=1 --verify=0 --randrepeat=0 \
      --numjobs=4 --thread --offset_increment=500G \
      --bs=1M --iodepth=64 --rw=write
    

次のステップ