サーバーインフラの負荷対策をどのように進めていけば良いか

この記事は [Akatsuki Advent Calendar 2017](https://qiita.com/advent-calendar/2017/aktsk) の15日目の記事です。
14日目の記事は [Beam SQL DSL ファーストインプレッション](https://qiita.com/apstndb/items/15aa847592f671366e74) でした。

# はじめに
ソーシャルゲームの運用では、ときに大規模なトラフィックが予想されるイベントが開催されます。
普段は NewRelic や AWS CloudWatch 等で普通に監視していれば問題になることはありませんが、急に3-5倍のトラフィックが来たときは、予想もしていなかった箇所がボトルネックになったりします。

過去の事例をあげると、Amazon RDS(MySQL)のCPUもメモリもIOPSもLock状況も問題ないメトリクスのなかで、データベースの応答時間が極端に遅いということありました。
よく調べてみると `SHOW GLOBAL STATUS;` で確認できる `Open_files` の値が低く、これは `open_files_limit` のパラメータが低くなりすぎていることが原因でした。

MySQLでは最大どれだけのコネクションを受けつけるかという `max_connections` というパラメータと、テーブルのファイルディスクリプタをキャッシュする数である `table_open_cache` がトレードオフの関係にあります。
この件は、アプリケーションサーバのプロセス数が多すぎるために、`max_connections` の値を大きくしすぎた結果、`table_open_cache` に割り当てた数が少なすぎてパフォーマンスの低下を招いていました。
負荷対策として実施したアプリケーションサーバのスケールアウトと `max_connections` の調整が、パフォーマンスの低下を招いたという事例でした。

## 計測と対策

[Jeff Dean](https://qiita.com/umegaya/items/ef69461d6f4967d5c623) 以外の人間は、大規模トラフィック下で何が起きるかを100%の精度で予想することはできません。それでは、どのようにして計測し、対策を進めたら良いでしょうか。
これから初めて大規模なトラフィックを捌く予定のインフラエンジニアエンジニア向けに、考えなければいけないことをまとめたいと思います。

# 負荷対策のプロセス

負荷対策は、大きく以下のフェーズで分かれます。

1. 予測/計画
2. 計測
3. 対策
4. 安定化
5. 本番
6. 振り返り

それぞれの観点と例を以下に記載します。

## 1. 予測/計画

最初に予測を立て、目標を設定し、計画を立てます。

### 負荷予測

- 予想DAU / 予想最大同時アクセス数
  - 最初から「これくらいのトラフィックがある」ということを予想するのは難しいので、「大体どれだけのユーザーが増える見込みか」ということと「過去の類似イベント」のログを組み合わせて、APIごとの予想トラフィック量を計算するのが良いでしょう。

- イベント期間中に特に増えるデータの予測
  - 極端にデータ量が増加するものがないか、企画者に確認しておくと良いでしょう。

### SLO(サービスレベル目標)の設定

提供サービスの形によりますが、特定の顧客向けのサービスでない場合は、SLA(Agreement)ではなくSLO(Objective)で設定するのが望ましいです。Agreementの責任感から生じるメリットよりも、修正にコミュニケーションコストがかかり状況に合わせた適切なチューニングがしずらいことのデメリットが上回るエースの方が多いです。

例えば、以下の様に設定します。

```
- 予想負荷の2倍の状況において、サービスが停止しないこと
- 予想負荷の5倍までインフラ環境のスケールアウトが可能なこと
- サーバー障害による緊急メンテナンス（計画外のサービスの停止）がイベント期間中10分以内にとどまること
- 予想負荷を超えた場合の、サーバ増強を目的とする緊急メンテナンスが発生する場合は、復旧時間が1時間以内にとどまること
- 予想負荷の前提で、以下主要APIの平均レイテンシが範囲に収まること
    - ログインAPI: 200 ms以内
    - 購入API: 250 ms以内
    - ...
- エラーレートが0.01%以内に収まること
```

#### コツ
- あまり多すぎると何が大切か分からなくなるので、必要最小限に留める
- 「絶対にサービスが停止しない」といった非現実的な目標は立てない


### 体制
- 負荷対策責任者の決定
- 対策メンバーと役割を可視化、共有
  - チームメンバーに事前に共有し理解を得ておくことで、運用中に発生する問い合わせの量などを調整することができます。

### 指標の標準化
全リソースの計測指標の標準化をしておきます。
集計期間がバラバラでインシデントが発生したときに比較できない、といった問題を事前に防いでおきます。

例えば、以下の様なことを決めておきます。

```
- 集計のインターバル: 集計期間は1分とする
    - 計測の頻度: 最低1分以内に計測する
- 集計の対象領域: APPサーバー、RDS、Elasticache
- データの取得方法: NewRelic + CloudWatch
```

## 2. 計測
### 負荷テスト計画、シナリオ作成、レビュー

SLOを満たすためのシナリオを作り、レビューしてもらいます。詳細は本やWebの記事等にたくさん情報があるので、良さそうなものを。
テスト対象、テストツール、テスト環境、合格基準などは計画時に決めておくと良いです。

## 3. 対策
### 改善
負荷テストの結果SLOを満たせないものがあれば、開発期間中に改善を行います。

- ボトルネックの解消
- 可用性の改善
- パフォーマンス・チューニング

### トイルの撲滅
本番環境に対してマニュアル作業が発生するものは、イベントを迎える前に出来る限り自動化しておきましょう。
開発期間中に障害復旧の手順などを自動化しておき、十分にテストできると不幸になりづらいです。

## 4. 確認
パフォーマンス改善やインフラ改善の最終的な影響を、本番環境で確認する期間です。
大事なイベントの直前にデプロイして、本番一発勝負！とならないように、確認期間は必ず設けておきます。
確認期間中に「大丈夫」と判断する基準及びその計測方法を明確にしておきましょう。

## 5. 本番
### 事前チェック
イベント前に適用するものに対して、バージョンの変更がないことを最終確認しておきます。

```
- サーバーライブラリ（Gemfile）
- ミドルウェア（Elasticache, RDS, APPサーバのOS, Ruby, その他）
- クライアントライブラリ
```

### 対応
意図せぬトラフィックに対しての動き方を決めておきます。
例えば以下の様に決めます。

```
- キャパシティの60%: 監視体制を強化し、今後のトラフィック増加に対する対策を検討する
- キャパシティの80%: メンテナンス計画を行い、お知らせを出す。インフラ拡張の手順を作成し、レビューする
- キャパシティの90%: 緊急でメンテナンスモードにして、インフラ拡張対応を行う
```

### 体制
何か起こってるけど誰もいない！とならないように、体制を確認しておきます。

```
- イベント期間中、重要時間帯の緊急対応体制が整えられている
- マニュアル作業において、作業者と計画者（確認者）が別々になっている
```

### インシデント管理
インシデントが発生したときに慌てないよう、指揮系統と発生したインシデント記録項目を決めておき、事前に周知しておきます。

インシデント記録項目の例:

```
- サマリ
- ステータス
- 指揮系統（インシデント指揮者、実作業責任者、計画責任者、コミュニケーション責任者、次のインシデント指揮者）
- 状況の詳細
- 終了基準
- TODOリスト及びJIRAチケット
```

## 6. 振り返り

発生したインシデントに対するポストモーテムをまとめ、今後の改善につなげます。

# 最後に

このエントリーではプロセスに注目しましたが、その中でも重要なのは正しく計測することです。
どれだけ「予測ではなく計測」を可能にするかがインフラエンジニアの腕の見せどころだと思います。