はじめに

CentOS 6とCentOS 7とAmazon LinuxではデフォルトのI/Oスケジューラに違いがあるため、ioniceコマンドを実行するときやパフォーマンスチューニングをするときに注意が必要だ。

各OSでのデフォルトI/Oスケジューラ

CentOS 6ではI/Oスケジューラはcfqになっている。CentOS 7ではdeadlineに変更された。(https://access.redhat.com/solutions/32376

In RHEL 7, the deadline IO scheduler is the default IO scheduler for all block devices except SATA disks. For SATA disk, the default IO scheduler is cfq.

Amazon Linuxではnoopになっている。

  • CentOS 6

    $ cat /etc/redhat-release && uname -r
    CentOS release 6.7 (Final)
    2.6.32-279.el6.x86_64
        
    $ cat /sys/block/sda/queue/scheduler
    noop anticipatory deadline [cfq] 
  • CentOS 7

    $ cat /etc/redhat-release && uname -r
    CentOS Linux release 7.3.1611 (Core) 
    3.10.0-229.el7.x86_64
        
    # SATAディスクではないので、デフォルトI/Oスケジューラはcfqではなくdeadlineになっているはず
    $ dmesg | grep sd[a-z] | grep SCSI
    [    1.029523] sd 2:0:0:0: [sda] Attached SCSI disk
        
    $ cat /sys/block/sda/queue/scheduler
    noop [deadline] cfq
  • Amazon Linux

    $ uname -r
    4.4.23-31.54.amzn1.x86_64
        
    $ cat /sys/block/xvda/queue/scheduler
    [noop]

I/Oスケジューラ

http://tfcenturion.hatenablog.com/entry/2015/11/07/091307にI/Oスケジューラの各種について、簡潔にまとめられているので引用する。

noop
最もシンプルなスケジューラで、I/O要求を単純に要求順に処理する。
noopはスケジューリング負荷が小さく、ランダムアクセスが高速なハードウェアに適していると考えられている。

deadline
ディスク上で位置が近いI/O要求を優先して処理を行うことで、HDDヘッドの移動量を削減する。位置が近いI/O要求を優先するため、HDDヘッドから遠くにあるI/O要求は後回しされるが、待ち時間の限界値(deadline)より長く待たされているI/O要求が発生した際には、そのI/O要求を優先する。
deadlineの設定によりレイテンシの上限が保証されるため、リアルタイムアプリケーションやデータベース管理に適していると言われている。

anticipatory(AS)
I/O要求を予測し、その予測に基づいて処理を行う。位置が近い場所へのI/Oがすぐ後に発行されると予測 した場合、I/O要求の処理を遅延させ、近隣のI/O要求が発行されるのを待ってから、I/O要求の処理を行う。つまり、いくつかのI/O要求を貯めてから処理を行う性質がある。
ただしDeadlineと同様に、待ち時間の長いI/O要求が発生した場合にはI/O待ちを中断し、待ち時間の長いI/O要求を優先して処理する。
anticipatoryは、データにシーケンシャルにアクセスするWebサーバなどに有効とされている。

cfq(Completely Fair Queuing)
スケジューラは内部に多数のキューを保持しておき、プロセス単位でI/O要求をそれらキューに割り振っていく。処理対象のキューを一定間隔で切り替えることで、プロセス間でI/O要求は公平に処理されていく。
また、Deadlineやanticipatoryと同様に待ち時間の長いI/O要求が存在した場合、それを優先的に処理していく。

I/Oスケジューラとパフォーマンス

クラウドと仮想化

Linux 2.6 カーネル ベースの仮想マシンでディスク I/O パフォーマンスが遅い (2094615)に書かれている通り、「仮想化された環境では多くの場合、I/O をホスト レイヤーとゲスト レイヤーの両方でスケジュールすることは有益ではなく」、ゲストが頑張るよりもホストに任せた方がいい。NOOP または Deadline のパフォーマンスが仮想化された Linux ゲストに対し向上したことが示されたとのことなので、クラウド全盛の現在は、デフォルトがNOOP または DeadlineになっているCentOS 7あるいはAmazon Linuxは、この観点では適切なのだろう。
noopでもdeadlineでもVMwareのテストでは効果がでたとのことだが、ホストに極力任せるという観点からはnoopの方がよさそう。

CentOS 6のEOLは2020/11/30だが、現時点でもCentOS 6のサーバをクラウド上で動かし、I/Oをヘビーユースするのであれば、I/Oスケジューラを変えた方がパフォーマンスがあがるかもしれない。
ディスク/dev/sdaのI/Oスケジューラを変更するのであれば、以下のように変更する。

$ sudo su -
# cat /sys/block/sda/queue/scheduler 
noop anticipatory deadline [cfq] 
# echo noop > /sys/block/sda/queue/scheduler
# cat /sys/block/sda/queue/scheduler 
[noop] anticipatory deadline cfq

サーバを再起動したら元に戻ってしまうので、/etc/rc.localに記入したり、initスクリプト化してchkconfig onにしたりするといい。
/dev/sdaだけのように各ディスク単位ではなく、サーバ全体でI/Oスケジューラを変更し、さらに永続化したければ、/etc/grub.confを編集する。/etc/grub.confkernel行にelevator=deadlineelevator=noopを追記して再起動すればいい。

CentOS 7であれば/etc/grub.confではなく/etc/default/grubGRUB_CMDLINE_LINUX行にelevator=noopを書いてから、/boot/grub2/grub.cfgをrebuildするためにgrub2-mkconfig -o /boot/grub2/grub.cfgを実行する。
参考:https://access.redhat.com/solutions/32376

SSD

現在はクラウドとともにSSDを使うことも多くなっている。ランダムアクセスが高速なハードウェアに適しているnoopを選択することでパフォーマンス向上するかもしれない。
deadlineanticipatoryもHDDヘッドの移動量が少なくなるようにするロジックなので、HDDヘッドがないSSDには適切なロジックではない。

ionice

ioniceはcfqじゃないと意味がない

パフォーマンス観点でcfqからnoopに変えた方がいいという話をしてきたが、パフォーマンスとは真逆のI/O負荷をできるだけ抑えながら処理をさせるioniceについても考えなければならない。

ioniceが効く場合は限られていて、I/Oスケジューラがcfqでないと効果がない。

ioniceが効く条件

  • I/Oスケジューラがcfqである
  • Read()である
  • Write()の場合は、-o syncでマウントされているかO_DIRECT,O_SYNCフラグを立てている

参考:
その ionice、ほんとに効いてますか?
【続編】その ionice、ほんとに効いてますか?

またLVMやソフトウェアRAIDを組んでいる場合も、ioniceは無意味となる。

CFQ is the default for scheduling actual physical disks, but small things like software RAID and LVM do not have disk schedulers at all and as far as I can tell ionice is completely ineffectual on them (for both read and write IO)

参考:Some notes on Linux's ionice

ioniceの代わり

コピーのときのio負荷を減らしたい場合

rsync--bwlimitで帯域制限を行う。R/W両方とも負荷が減る。

巨大なファイルを削除する場合

大量・巨大なファイル操作を低負荷で行う方法truncateを使った方法が紹介されていた。

#/bin/bash

set -euo pipefail

filepath=$1
# 10MBytes/sec
filesize=$(du -m ${filepath} | awk '{print $1}')
sleep_time=0.1

for num in $(seq 1 ${filesize});
do
 truncate -s $((${filesize}-${num}))M ${filepath}
 sleep ${sleep_time}
done

\rm ${filepath}