AWS RDSでもDBAはparameterチューニングする

弊社で某テック・リーダーの遺産として「PostgreSQL 運用管理トレーニング」が1月に実施されたのはまだ記憶に新しいところ。
あのときのリーダーの言葉、憶えているうちに書き留めておこう。

「Managed Service で RDB を使う時代になっても、この研修で扱う知識はDBAもApplication Engineerも持っておくべきだと信じて、企画し実現しました」
（この記憶はほんとうか？　意訳や美化が入っているかもしれない）

事例の1つを共有しましょう。

# 担当プロダクトの背景事情

担当プロダクトで使っているPostgreSQLをメジャーアップするには、現況を把握して変更影響を洗い出す作業から始めます。
現況把握のひとつとして、postgresql.conf（AWS RDSだとparameter group） の状況調査をしました。

重点ポイント

- PostgreSQLデフォールトから変更しているところ
- AWS RDSデフォールトから変更しているところ

# 運用管理トレーニングで追加した観点

parameterチェック重点ポイントに

- トレーニングテキストの「性能チューニング」で取り上げてるparameter
も追加しました。

- autovaccum関連
  - autovacuum_vacuum_cost_delay
  - autovacuum_naptime
- commit処理関連
  - wal_buffers
  - commit_delay
  - checkpoint_segments
  - checkpoint_timeout
- SSD用プランナコスト関連
  - ramdom_page_cost
  - effective_io_concurrency

# 担当プロダクト現況との不適合を発見

parameterの設定値が適切かどうかは、担当プロダクトのRDB利用状況と照合させて判定することになります。RDSは AWSの中の人のPostgreSQLエンジニアが善きに計らったdefault parameter groupがありますが、全てのアプリケーションに適合する設定値というものはありません。あったら設定可能にする意味がありませんもの。
ひととおり、デフォールト仕様とサービスの現況を調べて、「あ、これ、担当プロダクトでは直さないとダメだ」というのが見つかりました。

# checkpoint処理とは何か

RDBへの更新処理は、メモリ上のページバッファと、単ファイル順アクセス末尾書き込みのWAL(Write-Ahead-Log)に反映させておけば、「ACID」のD:永続性の実現が可能です。
ページバッファをディスクに反映するには複数ファイルへのランダムアクセスが必要になるので、なるべく先送りしたい、書き出しを毎回じゃなく複数回の更新をまとめて反映したい。
しかし先送りしすぎると、クラッシュリカバリのときのリカバリ処理量が増えすぎてしまう。
一定周期でページバッファをディスクに反映して、リカバリ処理量の上限を抑えて、性能と障害復旧の速さのバランスをとろう。
この周期が `checkpoint_timeout` です。
checkpoint処理は、まとまったページ数の複数ファイルランダム書き込みとなるので、RDBMS/OS/HWにとって高負荷処理になります。PostgreSQLには負荷平準化実装があり、次のcheckpoint開始までの N %の時間をつかって必要なディスク書き込み処理を終えるように delay を入れながら処理していきます。Nは `checkpoint_completion_target` で設定します。PostgreSQL default は 0.5 ですが RDS default は0.9です。

# checkpoint log

`checkpoint_timeout` の PostgreSQL/RDS default は 300sec=5min
RDS のログのピーク時間帯とオフピークのを各1時間分download して grep します。

```
$ grep -c 'checkpoint starting' postgresql.log.2019-01-21-02
12
$ grep -c 'checkpoint starting' postgresql.log.2019-01-21-11
21
```
オフピークでは設定通り、5分に1回でcheckpoint処理していますが、ピーク時は平均して3分に1回になっています。

# timeout前のcheckpointとは

WALには容量があって、それを使い切るほど更新が来たなら、リカバリ処理量としても許容できる上限ですよね。
そういうわけで、ページバッファをディスクに反映開始します。
という具合にタイムアウト前にcheckpoint処理が始まるのです。元々リカバリ処理量の上限を抑えるのがcheckpointの目的ですから。
つまり、このプロダクトはピーク時になると更新量が増えて、5分と保たずにWAL容量リミットに達しているということです。

# timeout前checkpointの不都合なところ

WAL容量を使い切った = ある種の非常事態宣言
DBAの想定以上の更新負荷が来ている状況ならば、負荷平準化の delayなんぞ入れている場合じゃありません。もたもたしてたら書き込み処理が滞留して、RDBMS全体のスローダウン、サービス応答時間の劣化、UXの悪化となりかねません。一時的なバースト的な更新量増加なら全力で早期に片付けることがサービスとしては傷が小さいでしょうし。
でもDBAの性能見積失敗や、設定未調整でのtimeout前checkpointだと？
負荷平準化が機能せず、DiskIOPSがcheckpoint処理開始の度にピンピン跳ね上がるデメリットがあります。ピーク時間帯の数時間も継続して非常時が続くというのは健全ではありません。

# 全てをtimeoutにするためにWAL容量を増やす

ピーク時でも 3minは保っているので、容量2倍にすれば現況では 5min の timeout checkpoint処理だけになるはずです。
しかし、サービス成長余裕もいるので、容量4倍にしました。
checkpoint_segments:16 => 64
リカバリ処理量はいまのところ気にしていません。クラッシュリカバリが必要な事態になったら、サービス復旧までの時間はPostgreSQL以外のところに大きく取られるでしょうから。

# Before-After

DiskIOPS.write のスパイクが抜けてピークが小さくなった 600 -> 400
オフピークには違いなし
![advanced-graph (6).png](https://qiita-image-store.s3.amazonaws.com/0/47549/8b5f985c-77bb-b23e-bec8-921394a34abf.png)


checkpoint回数自体が減る＝同じページの複数回の書き込み反映が1回になるケースが増える
この効果があるはずですが、劇的ではなかった。同じページへの書き込みは短い時間に集中していて、3分周期が5分周期になっても同一ページ書き込みの集約はそれほど進まなかった。

1分粒度で見ると、IOPS.write平準化してAfterの方が変動が少ない
![advanced-graph (7).png](https://qiita-image-store.s3.amazonaws.com/0/47549/c86396d9-660a-cb1e-c9ba-8140d4e03eba.png)


# あとがき
「運用管理トレーニング」でも言及ありましたが、parameter tuning で改善できるのはせいぜい30% とかです。
性能問題で困った事態になってから parameter見直しても、ScaleUp/ScaleOutまでの時間稼ぎもできないケースが多いでしょう。
「平準化」は、稼働の安定、監視ノイズの除去をして、先の見通しの精度を上げるのには必要なことですから平時のうちにやっておきましょう。

# 付録
## 一般公開PostgreSQLパラメータ資料

- [PostgreSQL11 設定パラメーター解体新書](https://speakerdeck.com/ester41/postgresql-11-parameter?slide=118)
- OSC 2019 Osakaの登壇資料 by 寺内 大輝（ester41） さん
- [中国地方DB勉強会 in 岡山 2019-02-02](https://dbstudychugoku.connpass.com/event/112468/) で教わりました。

## RDS の Modifiable Parameter だけを抜き出し一覧にする

```
$ aws --output table rds describe-db-parameters \
--db-parameter-group-name <your-parameter-group-name> \
--query "Parameters[?IsModifiable==\`true\`].[ParameterName,ParameterValue]"
```

## PostgreSQL の pg_settings VIEW
parameter の 現在値、default, 説明, 有効値域などの情報を見る

```
=> select * from pg_settings where name LIKE 'checkpoint%' limit 1;
-[ RECORD 1 ]----------------------------------------------------------------------------------------
name       | checkpoint_completion_target
setting    | 0.9
unit       |
category   | Write-Ahead Log / Checkpoints
short_desc | Time spent flushing dirty buffers during checkpoint, as fraction of checkpoint interval.
extra_desc |
context    | sighup
vartype    | real
source     | configuration file
min_val    | 0
max_val    | 1
enumvals   |
boot_val   | 0.5
reset_val  | 0.9
sourcefile |
sourceline |
```