Auroraメンテナンス、アップグレード作業で得た教訓

こんにちは、トレタでインフラを担当してる山田です。

サービスを運用していると、時にリソース増強のためにメンテナンス時間が必要になりますよね。
マネージドサービスとはいえRDSも短時間ではありますが停止が必要となるケースが多いです。
この記事では、RDS、主にAuroraのメンテナンスや関連する作業で自分がハマった経験（得た教訓）について書いていきたいと思います。

# Auroraメンテナンスとは
Auroraメンテナンスは、AuroraクラスターOSのアップグレードもしくは、DBインスタンスOSのアップグレード処理のことを指します。（この記事ではAuroraクラスターのOSアップグレードに関して書きます。）

メンテナンスを実施すると、基本的には数秒程度の停止時間が発生します。
私が実施するときのやり方をざっと書いてみます。

---

**1. メンテナンスの要否を確認**

メンテナンス作業の要否は、通知メールもしくは、マネジメントコンソールの画面から確認できます。
<img width="1086" alt="スクリーンショット_2017-12-08_11_43_16.png (81.3 kB)" src="https://img.esa.io/uploads/production/attachments/2118/2017/12/08/14672/b00b40dd-3982-40c5-ba69-94c729ebabe1.png">

で、メンテナンスステータスを確認します。
ステータスには以下2種類があります。

- Required -> 必須となるメンテナンス。指定された日時に自動的にメンテナンスが実行される。
- Available -> 必須ではないメンテナンス。メンテナンスウィンドウによって自動実行されることはない。


**2. 停止時間測定**

いざメンテナンスを開始する直前に、こんな感じで2種類のリクエストを飛ばします。

```
while true; 
do 
  mysqladmin ping -h <cluster-endpoint> -u <user> -p<passwd> ;
  date ; 
  sleep 1; 
done
```

```
httping https://~~~~~~~
```

**3. メンテナンス開始**
コンソールに従い操作し、メンテナンスを開始します。
単純作業なので割愛します。

**4. バージョン確認**
aurora_version変数を参照します。
正常にアップグレードされていれば完了です。

```
mysql> select @@aurora_version;
+------------------+
| @@aurora_version |
+------------------+
| 1.xx             |
+------------------+
1 row in set (0.01 sec)
```

---

尚、「 `基本的` には数秒程度の停止時間が〜〜〜」と書いているのは、ZDPを適用すれば無停止でのアップグレード且つ、アップグレード中のクライアントアクセスが可能という素敵すぎる運用を行うことも可能なのでこういう書き方をしています。
ただし、バイナリログを無効化する必要があるなどの条件付きなので、システム要件に沿っているか確認が必要ですね。（https://aws.amazon.com/jp/blogs/news/amazon-aurora-update-spatial-indexing-and-zero-downtime-patching/ ）

で、ZDPを適用しない場合で測定してみた結果、クラスターメンテナンスに伴う停止時間は10秒前後でした。
インスタンスタイプ、容量など複数ケースで実施してみましたが、概ね同様の時間だったので、構成にはあまり依存しないようです。

---

通常のメンテナンスはこんな感じで平和に完了できます。

さて、ここからはメンテナンスに関連して、過去ハマった点をについて挙げていきたいと思います。
（既知で有名な点もあるかとも思いますが。）

# 1. 再起動していた。
前述にもある通り、基本的にはEC2等のリソースはメンテナンス前に通知ウィンドウもしくはメール等で管理者にメンテナンスを実施する旨を連絡をしてくれます。RDSも同じです。
ただし過去数回、事前通知なくメンテナンスが実施された経験がありました。

数秒間とはいえ、場合によってはサーバよりも致命的な問題になる可能性があるため、以下のAPIを叩いてメンテナンス計画を監視するのが良さそうです。

```
aws rds describe-pending-maintenance-actions
```

トレタでは定期的に↑を叩いて、メンテがスケジュールされるとSlackへ通知を行うようにしています。

# 2. インスタンスタイプ変更に失敗した。
例えばr3タイプからr4タイプへのアップグレードなどの、ファミリー種別／インスタンスタイプをまたぐアップグレード時は事前準備が必要です。
そのままアップグレードしようとすると、以下のようなエラーで失敗する場合があります。

```
Service: AmazonRDS; Status Code: 400; Error Code: InvalidParameterCombination
```

MySQLのバージョンによる差異により発生する、パラメータの非互換のため発生するエラーで、defaultパラメータ以外を使用していると発生する可能性があります。

で、こうなった場合にやることとしては、

 - 1. Auroraを最新バージョンへアップグレード
 - 2. DBインスタンスのOSアップグレード

の2点です。

ZDPを適用していない場合に、停止時間を見積もる際は、Auroraクラスターのメンテナンス時間に合わせて、Writer/Readerインスタンスの各メンテナンス時間も考慮する必要がありますね。

# 3. フェイルオーバー後、アプリからの書き込みに失敗する。
アプリケーション側でコネクションプールに接続情報を保持する設計の場合は注意が必要です。
というのも、インスタンスのアップグレード等により、Writer側を停止すると、Aurora Clusterはフェイルオーバーを発生させます。フェイルオーバー自体は数秒で完了するのですが、問題はフェイルオーバー完了後も旧writer側に書き込みを継続しようとすることです。
これによって更新系のクエリが失敗するという事件が発生します。

なんやこれ？と思い、フェイルオーバーで置きていることをちょっと調べてみました。

---
**フェイルオーバー前**

- クラスターエンドポイント

```
;; ANSWER SECTION:
test-cluster.cluster-xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com. 5 IN CNAME test.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com.
test.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com. 5 IN A xxx.xxx.xxx.246
```

- 読み込み専用エンドポイント

```
;; ANSWER SECTION:
test-cluster.cluster-ro-xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com. 1 IN CNAME test-ap-northeast-1c.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com.
test-ap-northeast-1c.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com. 5 IN A xxx.xxx.xxx.221
```

**フェイルオーバー後**

- クラスターエンドポイント

```
;; ANSWER SECTION:
test-cluster.cluster-xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com. 5 IN CNAME test-ap-northeast-1c.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com.
test-ap-northeast-1c.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com. 5 IN A xxx.xxx.xxx.221
```


- 読み込みエンドポイント

```
;; ANSWER SECTION:
test-cluster.cluster-ro-xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com. 1 IN CNAME test.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com.
test.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com. 5 IN A xxx.xxx.xxx.246
```

---
RDSはフェイルオーバーが発生すると、当該クラスターのクラスターエンドポイントと読み込みエンドポイントのDNSレコードに紐付いているCNAMEが交互に入替えられるだけで、物理的なIPは変更されない。
なので、IPで接続先を判別されていると、この問題が発生します。

この場合、

- コネクションプーリングの無効化を検討
- READONLYが原因で書き込みに失敗した場合は再接続させる
- 緊急の場合はappを再起動するなどして、接続情報をリフレッシュした上でサービスイン

あたりでしょうか。
フェイルオーバーが発生する場合はアプリケーションからの接続周りについて十分注意するのが良いです。