Ceph 最適な PG count で運用するための Best Practice (Nautilus, <=Luminous 対応)

#はじめに
Ceph の運用は難しい、と言われることが良くありますが、最も大きな要因の一つとして PG count (pg_num / pgp_num) という概念が存在している点が挙げられます。

https://docs.ceph.com/docs/mimic/rados/operations/placement-groups/

PG (Placement Group) の考え方自体はとてもシンプルであり、PG が存在するおかげで Ceph のスケーラビリティを高めることができます。一方で、実際に Ceph を安定的に運用するためには適切な PG の値を維持する必要があるため、運用時の考慮事項が増えてしまうことが課題となります。

本ドキュメントでは、Ceph community により今までに共有されてきた、PG count の運用に関する最新の Best Practice についてまとめさせていただきます。
なお、最新版の Ceph では自動的に PG count を最適化する仕組みが導入されたこともあり、従来の手順が必ずしも最適ではない状況になりつつあります。Ceph を導入する際には、運用の観点も踏まえて最適なバージョンを選択することをお勧めします。

#Nautilus(v14.x)
最近の Ceph では運用に関する課題の解決について優先的に取り組まれており、PG count に関する運用の手間が大幅に改善されました。
2019年11月時点での最新版 Ceph (Nautilus; v14.x) では、PG merging や auto-tuning が実装されているため、時間経過に従いデータ容量やサーバ数がが大きく変動する環境でも自動的に PG の最適化が実施されるようになっております。

- [The Ceph Blog "New in Nautilus: PG merging and autotuning"](https://ceph.io/rados/new-in-nautilus-pg-merging-and-autotuning/)
- [AUTOSCALING PLACEMENT GROUPS](https://docs.ceph.com/docs/master/rados/operations/placement-groups/#autoscaling-placement-groups)

##PG auto-scaler
Nautilus(v14.x)以降では、PG auto-scaler が実装されました。本機能により、Ceph 管理者は Target の Disk 使用率(またはサイズ)を予め設定しておくと、pool 内のデータ量が Target を上回った際に ceph-mgr が自動的に適切な pg_num / pgp_num を設定するようになります。

Target の設定は必須ではありませんが、もし Target が設定されていない場合はデータ量が少し増加する度に pg_num / pgp_num が調整されるため、定常的に cluster に余分な負荷を掛けてしまう可能性があります。予め想定されるデータ量が分かっている場合は、Target の値を設定しておくことで pg_num / pgp_num を大きな値に設定しておくことができます。

なお、auto-scaler 機能は Nautilus の時点ではデフォルト設定が無効になっておりますが、コマンドを 数個入れるだけで有効化できます。

PG auto-scale の設定方法は、CLI を用いる場合は次のような手順になります。

```bash
# pool 'foo' を 作成します
ceph osd pool create foo 1
rbd pool init foo

# PG auto-scaler 機能を有効化します
ceph mgr module enable pg_autoscaler

# pool 'foo' に対して、Target Ratio を設定します (.8 = 80%)
ceph osd pool set foo target_size_ratio .8

# pool 'foo' に対して、PG auto-scaler を有効化します
ceph osd pool set foo pg_autoscale_mode on
```

###ROOKを使う場合
ROOK を用いて kubernetes 上に Ceph のデプロイを実施した場合は、CephCluster の Custom Resource に設定を追加するだけで、全ての pool について PG auto-scaler が有効になります。ただし、個別の pool に対する enable / disable の設定はできないため、手動で制御したい pool がある場合は上記のような手順にて個別に設定する必要があります。

- [ROOK Ceph Configuration](https://rook.io/docs/rook/v1.1/ceph-configuration.html)

##PG manual-scaling
Nautilus では手動での PG scaling についても改善がされております。主な変更点は次の通りです。

- PG merging に対応
  - Nautilus では PG merging が実装されたため、pg_num, pgp_num を小さくすることが出来るようになりました
- pgp_num 変更に伴うデータ移動をシーケンシャルに実施
  - データの移動対象(misplaced)を 5%に制限した上で順次実施する実装になったため、client へのサービス影響を抑えることが出来る
  - 5% の制限値については、target_max_misplaced_ratio にて変更することも出来る

Nautilus 以降では pgp_num 変更に伴うサービス影響が低減されています。このため、後述する Luminous 以前のような複雑な手順を踏まずに、次のような手順を実施するだけでサービス影響を抑えつつ PG count を変更できます。

```bash
# pool 'foo' の pgp_num を 64 にする
# pg_num については、pg_num == pgp_num の場合は自動的に pg_num も新しい値に追従するため、明示的な設定変更が不要
# (nautilus 以降にて仕様が変更されています)
ceph osd pool set foo pgp_num 64
```

#Luminous(v12.x)以前
Luminous(v12.x)以前の Ceph については、ceph community で得られる情報としては、概ね次のような問題が報告されています。

- [[EXTERNAL] Re: Increase PG number](http://lists.ceph.com/pipermail/ceph-users-ceph.com/2016-September/013119.html)
- [Increase ceph cluster pg and pgp placement groups without downtime](http://sudomakeinstall.com/uncategorized/increase-ceph-cluster-pg-and-pgp-placement-groups-without-downtime)

- 一度に沢山の pgp_num を増加させると、client の通信について次のような影響が出る
  - 例えば、16,384 -> 32,768 に pgp_num を増やすためにコマンドを実行すると、PG の peer 追加が一度に実施されるため、一時的に ceph-osd の負荷が上昇して client I/O に影響を与える可能性がある
  - pgp_num を増やした後、新規に追加された PG のデータ移動が終わるまでは、ceph-mon の compaction が実施されないため ceph-mon ノードのデータ肥大化による Disk full が発生しないように注意する必要がある
  - backfill の負荷については、ceph-osd の直近の負荷や network のキャパシティ を良くみた上で、必要であれば client I/O にリソースが優先されるようにしておくべき


上記 ceph-users ML で紹介されている方法としては、次のような script を用いて、256 ずつ pg_num / pgp_num を増やすことで、client I/O に与える影響を低減できます。

```bash:script_example_1
check_health(){
#If this finds any of the strings in the grep, then it will return 0, otherwise it will return 1 (or whatever the grep return code is)
    ceph health | grep 'peering\|stale\|activating\|creating\|down' > /dev/null
    return $?
}

for flag in nobackfill norecover noout nodown
do
    ceph osd set $flag
done

#Set your current and destination pg counts here.
for num in {2048..16384}
do
    [ $(( $i % 256 )) -eq 0 ] || continue
    while sleep 10
    do
        check_health
        if [ $? -ne 0 ]
        then
#This assumes your pool is named rbd
            ceph osd pool set rbd pg_num $num
            break
        fi
    done
    sleep 60
    while sleep 10
    do
        check_health
        if [ $? -ne 0 ]
        then
#This assumes your pool is named rbd
            ceph osd pool set rbd pgp_num $num
            break
        fi
    done
    sleep 60
done

for flag in nobackfill norecover noout nodown
do
    ceph osd unset $flag
done
```

```bash:script_example_2
ceph osd set nobackfill
ceph osd set norebalance
function healthy_wait() {
  while ceph health | grep -q 'peering\|inactive\|activating\|creating\|down\|inconsistent\|stale'; do
    echo waiting for ceph to be healthier
    sleep 10
  done
}
for count in {2048..4096..256}; do
  healthy_wait
  ceph osd pool set $pool pg_num $count
  healthy_wait
  ceph osd pool set $pool pgp_num $count
done
healthy_wait
ceph osd unset nobackfill
ceph osd unset norebalance
```

backfill については、次のような設定を事前に入れておくことで migration による負荷を低減できます。

```bash
# ceph daemon mon.rccephmon1 config show | egrep "(osd_max_backfills|osd_recovery_threads|osd_recovery_op_priority|osd_client_op_priority|osd_recovery_max_active)"
   "osd_max_backfills": "1",
   "osd_recovery_threads": "1",
   "osd_recovery_max_active": "1"
   "osd_client_op_priority": "63",
   "osd_recovery_op_priority": "1"
```

上記 script のような手順で PG count を増やせば、原理的には最小限の負荷で migration を実施できそうです。

ただし、script_example_1 の方は 'noout' と 'nodown' を設定してしまうと migration 実施時に ceph-osd で障害が発生しても自動的に当該ノードが cluster から切り離されないため、client I/O が刺さる等の影響が出るリスクがあります。別の監視システムを用いてリアルタイム監視や対応が出来るような状況でない限りは、'noout' と 'nodown' は設定しない方が無難だと考えられます。
script_example_2 の方は script_example_1 と同等の内容ですが、'nobackfill' および 'norebalance' のみを実施しております。こちらの方が、より安全に migration が実施出来ると思います。

#おわりに
今回のドキュメントでは、Ceph を大規模な環境で運用していると一度は遭遇しそうな、PG count の増やし方について焦点を当ててみました。
昔の Ceph バージョンだと、ceph-osd ノードをどんどん追加していくうちに気付いた時には PG count が足りなくなっていた..泥臭い作業を繰り返して Healthy な状況に戻す..ということが良く起こっていたと思いますが、最新の Nautilus 以降を使っていれば、ほぼメンテナンスフリーで良い感じに最適な PG count を維持させることが出来るようになります。
Nautilus ではこの他にも様々な改善が進んでいるので、Ceph を使う機会があれば是非最新バージョンをチャレンジするのが良いと思います。