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

永続ディスクの最適化

永続ディスクは、ディスクタイプの表で示したパフォーマンスを実現できますが、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 ストリームを使用する

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

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

  • 大きい IO サイズを使用する

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

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

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

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

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 を一定期間にわたって分散できるので、ディスクのパフォーマンス上限に到達することはありますが、上限を超えることは決してありません。

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

SSD 永続ディスクのパフォーマンスのベンチマーク

次のコマンドは、2,500 GB の PD-SSD デバイスを想定しています。デバイスサイズが異なる場合は、--filesize 引数の値を変更します。このディスクサイズは、32 vCPU VM スループットの上限を達成するために必要です。

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

    # 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
    
  2. 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
    
  3. 書き込み 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
    
  4. 書き込みレイテンシをテストします。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
    
  5. 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
    
  6. 読み取り 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
    
  7. 読み取りレイテンシをテストします。現実的なレイテンシ測定を行うには、ディスクにデータを書き込む必要があります。また、テスト中に 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
    
  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 \
      --numjobs=4 --thread --offset_increment=500G \
      --bs=1M --iodepth=64 --rw=read
    
  9. 順次書き込み帯域幅をテストします。

    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
    

次のステップ