RDS for MySQL のエンコーディングやコレーションの変更を既存の DB インスタンスに反映させる

# はじめに

Rails アプリケーションから扱っていた、数年間運用し続けている Amazon RDS for MySQL に新たなテーブルを作っていたところ、テーブルのデフォルトのエンコーディングが `utf8` になっているのを発見しました。
これでは絵文字を格納することが出来ません！ 困りましたね、エンコーディングを `utf8mb4` に変更しましょう！ コレーションも `utf8mb4_bin` に変更してしまいましょう！

といったシチュエーションで、MySQL インスタンスに設定しているパラメータグループの値を変更しても何やら変更が反映されない現象に遭遇したので、これを記します。

# 環境

- Aurora for MySQL 5.7.12
    - 恐らく 5.6系の MySQL や Aurora for MySQL にも共通するはず

# 結論先取

公式ドキュメントの[DB パラメータグループを使用する](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/USER_WorkingWithParamGroups.html)をよく読みましょう。

> DB インスタンスの文字セットまたは照合パラメータを変更した場合、パラメータの変更は既存のデータベースに適用されません。

と書かれている通り、エンコーディングやコレーションのパラメータの変更は既存のデータベースには適用されず、新規のDBインスタンスの作成時にしかエンコーディングに関するパラメータグループの設定は反映されません。

既存のデータベースのエンコーディングを変更したい場合は、ドキュメントにもある通り

```sql
ALTER DATABASE database_name CHARACTER SET character_set_name COLLATE collation;
```

を実行する必要があります。

# 素直にパラメータグループを変更する

スナップショットから検証環境を用意して、素直にパラメータグループを変更して実験をしてみましょう。
私達の場合は、セッションを張るクライアント側でエンコーディングを `utf8mb4` に明示的に指定していたので `character_set_database` の値を `utf8mb4` に変更するだけで良さそうでした(実際に私達が運用していたのは MySQL ではなく Aurora for MySQL だったので、 `character_set_database` はパラメーターグループではなくクラスターパラメーターグループの中に存在していました)。
AWS のマネジメントコンソールから変更してみましょう(本当は Terraform などで記述するべきです！)。

<img width="1125" alt="RDS · AWS Console 2019-04-03 00-37-14.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/126264/88e0e88b-3df8-9a25-8854-c6edbc41659a.jpeg">


どうやら再起動が必要という声をよく耳にするので、再起動してからデータベースに接続してみましょう。:v_tone3:

```sql
SHOW GLOBAL VARIABLES LIKE `chara%`;
```

<img width="397" alt="(MySQL 5.7.16) qiita-dev:qiita 2019-04-03 00-43-54.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/126264/b3161ec3-606f-6727-3522-d820bb5437d6.jpeg">

おや、反映されていなさそうです。それに `utf8` ではなく `latin1` です。参りました。
`character_set_database` は Dynamic パラメータとのことなので、再起動せずともインスタンスを起動したまま変更を反映する事が可能なはずですが、念を入れて再起動をしてもなお反映されないのは随分不可解です。

公式ドキュメントの[DB パラメータグループを使用する](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/USER_WorkingWithParamGroups.html)をよく読み返してみましょう:rolling_eyes:

> DB インスタンスの文字セットまたは照合パラメータを変更した場合、パラメータの変更は既存のデータベースに適用されません。

:joy::joy::joy:

Dynamic パラメータとは一体という気持ちになってきましたね :innocent: 

どうやらエンコーディングやコレーションに関するパラメータの設定が反映されるタイミングは新規の DB インスタンスを立ち上げる瞬間のみであり、既存の DB インスタンスに対して設定が反映されることはないようです！
検証まではしていませんが、私達が運用していた DB インスタンスは幾度のクローンやリストアを繰り返していたものなので、本当に新規でインスタンスを立ち上げる瞬間のみが反映タイミングであるように思えます。

# ALTER DATABASE を実行する

私達が DB インスタンスを新たに立ち上げる事はしばらく無いように思えたので、公式ドキュメントの案内通りに直接データベースのエンコーディングとコレーションを変更する SQL を実行する他にはなさそうです！

```sql
ALTER DATABASE database_name CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
```

データベースのエンコーディングとコレーションを変更した場合、影響範囲は以下のようになります。

- 既存のテーブル、カラム
    - 過去に設定されたエンコーディング、コレーションが維持される
- 新規に作成するテーブル、カラム
    - 今回設定したエンコーディング、コレーションが反映される

デフォルトのエンコーディングとコレーションを変更するだけで、既存のテーブルのエンコーディングとコレーション(私達の場合は `utf8`)は変更されず、INDEX の再構築などは行われません。
つまり、ダウンタイム無しで設定を変更できるということになりますね！:joy:

もちろん、既存のテーブルやカラムの変更が行われない為、これらのエンコーディングとコレーションを変更したい場合は INDEX 再構築等を伴う操作が必要になってくるので、それは必要に迫られた時に対応を考える必要があります。

少なくとも、私達が目指していた「新しく作られるテーブルのエンコーディングが `utf8mb4` にならない」という問題は、これで解決出来たようです！ やりましたね！

ちなみに、既存のテーブル、カラムのエンコーディング・コレーションを一発で変更する SQL は以下の通りです。

```sql
ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4;
```

テーブル内のカラムも自動的に変換してくれるので便利ですね :smile: 

# 本当はどうすべきだったの？

先人が再三口酸っぱく言っているように、DB インスタンスを立ち上げる前にちゃんとパラメータグループの設定を見直すべきです。

そうすれば、パラメータグループから MySQL のグローバル設定や各種テーブルの設定まで一気通貫で整合性が取れる状態になり、誰も戸惑わずに済みました。

先人から学ぶことは本当に多いですね！ :smile: