CloudFormationでRDSパラメータを設計した理由と設計の考え方

## はじめに
この記事は、ハッカソンで構築したWebアプリケーションにおいて、CloudFormationを使ってRDSパラメータをチューニングした実践内容とその背景、および各パラメータ値に至った設計根拠をまとめたものです。
「設計の意図が発表で伝えきれなかった」「なぜその値にしたのか説明しにくい」という経験を踏まえ、アプリの要件と設計の関連を言語化して残します。

前提となるインフラ構成や監視設計は以下の記事にまとめています。

https://qiita.com/ARISA1115/items/8b99da81dd779394aaa8

https://qiita.com/ARISA1115/items/82064bcf64c9ecd12d52

## インフラ構成とコード管理の方針

本アプリ開発におけるインフラ構成は以下の通りです：
- ECS on Fargate（FastAPIによるバックエンドAPI）
- CloudFront + S3（Next.jsによるフロントエンド静的ホスティング）
- Amazon RDS for MySQL 8.0（t3.medium）
- Amazon Bedrock（Claude）によるAI応答機能
- Datadogによるメトリクス・ログ監視

インフラ全体はTerraformでコード管理していますが、RDSのパラメータグループとオプショングループのみはCloudFormationで管理しています。これはTerraformだけでは制御しづらい運用上の制約を避けるためです。

### なぜCloudFormationでRDSパラメータを管理するのか？
Terraformはインフラ全体の構成管理に非常に優れていますが、RDSパラメータの更新においては下記のような課題があります：
- `parameter_group_name` の変更で、RDSインスタンスが意図せず再作成（破壊・再構築）されることがある
 →  たとえば、誤って `lifecycle { prevent_destroy = false }` にしていた場合や、明示的に変更を許容していた場合、意図しないDBの削除が発生するリスクがある

- **静的パラメータ（static）はインスタンスの再起動が必要**であるにも関わらず、Terraformでは明示的な再起動制御がしづらい
 → Terraformで `.tf` ファイルを変更し apply しても、**静的パラメータの変更は即時反映されず、手動で再起動が必要**なことがあり、**Terraformの操作からは「何が反映されたのか」が分かりにくい**

- CloudFormationでは DependsOn やテンプレートの階層構造でリソースの適用順序や依存関係を明示できる
 → 一方、Terraformでは **内部的に依存関係を解決してくれる反面、意図しない順序で変更が走ることがある**ため、**パラメータ更新時の再起動タイミングなどの制御が難しい**

このように、Terraformで全体を管理しつつ、RDSパラメータのような**副作用が強く、再起動の制御が必要なリソースはCloudFormationに分離して管理する**アーキテクチャを採用しました。

## なぜRDSパラメータ設計が必要だったのか？
本アプリ「さぶちゃん日記」は、ユーザーが日記を書き、チャット形式でAIと会話する機能を中心とした構成です。

アプリケーションには以下のような要件がありました：
- 想定同時接続ユーザー数は 最大50名
- チャット送信や日記保存など書き込み処理が多い
- Amazon Bedrock経由でClaudeと連携し、長文のリクエストやレスポンスが発生
- 小規模環境ながら、最低限の応答性と安定性が求められる

RDSのデフォルト設定では、こうした負荷に対してI/Oやメモリがボトルネックになる可能性があると判断しました。そのため、MySQLのパラメータをアプリケーション要件に合わせてチューニングし、CloudFormationで明示的に管理をしました。

## RDSパラメータ設計の目的と全体方針

パラメータ設計の目的は次の3点です：
1. アプリの負荷特性に最適化したパフォーマンス設計
2. ログやタイムアウトなど運用・トラブル時の保守性を強化
3. 将来的なスケール・改善につなげる観測性の確保する

以下の表は、CloudFormationで設定したRDSパラメータと、それぞれの設定値と意図です：

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3958955/06431ebc-b37a-46df-99cc-8d6cfd566e2b.png)

### CloudFormationテンプレート

```yml
Resources:
  MyDBParameterGroup:
    Type: AWS::RDS::DBParameterGroup
    Properties:
      Family: mysql8.0
      Description: "MySQL 8.0 parameter group"
      Parameters:
        max_connections: "100"
        innodb_buffer_pool_size: "3221225472"
        innodb_log_file_size: "536870912"
        tmp_table_size: "67108864"
        max_allowed_packet: "67108864"
        max_execution_time: "30000"
        net_read_timeout: "120"
        net_write_timeout: "120"
        sort_buffer_size: "3145728"
        join_buffer_size: "3145728"
        innodb_io_capacity: "200"
        innodb_read_io_threads: "4"
        innodb_write_io_threads: "4"
        slow_query_log: "1"
        long_query_time: "2"
        character_set_server: "utf8mb4"
        collation_server: "utf8mb4_0900_ai_ci"
        time_zone: "Asia/Tokyo"
```
### 各パラメータの設計根拠と数値比較
ここでは、特に設定の根幹となるパラメータの設定値に至った明確な根拠を示します。本番稼働や負荷検証を前提とした実測結果に基づくものではなく、あくまで設計段階での見積もりとベストプラクティスに基づく設定であることをご理解ください。

- **max_connections = 100**
同時接続ユーザー数として最大50名を想定し、それに加えて監視（Datadog）や内部バッチ処理による接続、開発中の予期せぬ再接続なども考慮し、設計上の同時接続数を約70〜80接続程度と見積もりました。
この上で、突発的なスパイクにも対応できるよう安全マージンを加えて100を設定しています。

- **innodb_buffer_pool_size ≒ 3GB**
使用しているRDSインスタンス（t3.medium）のメモリは4GBであるため、MySQL公式の推奨（70〜80%をInnoDBバッファに割り当てる）に従って約3GBを設定しました。

- **innodb_log_file_size = 512MB**
アプリケーションではチャットや日記などで頻繁に書き込みが発生することを前提に、書き込みログ（WAL）の負荷を低減するため、10分間あたり最大250MB程度のログ生成量を見積もり、2倍の余裕をもたせて512MBとしました。

- **tmp_table_size / max_heap_table_size = 64MB**
ORDER BY や GROUP BY を多用する検索機能において、ディスクI/Oの発生を抑える目的で、デフォルトの16MBから64MBへ拡張しました。Created_tmp_disk_tables の減少やクエリ高速化が期待されますが、この値が適切かどうかのログ検証・統計的分析は未実施です。今後の検証対象とします。

- **max_allowed_packet = 64MB**
Claudeとの連携で、プロンプトやレスポンスが20〜30MB程度になるケースを想定し、パケットサイズ上限をデフォルトの16MBから64MBへ拡張しました。
過去に経験した“packet too large”エラーを回避する設計ですが、本番環境でのエラー発生履歴や実際の通信サイズとの突合は行っていません。

- **max_execution_time = 30秒**
FastAPIのHTTPタイムアウト（30秒）と整合性をとるために設定しました。
暴走クエリの抑止やユーザー体感パフォーマンス改善を意図していますが、クエリキャンセルの発動件数や対象クエリの実態については未観測です。

- **net_read_timeout / net_write_timeout = 120秒**
Claudeや外部API呼び出しにおける一時的な遅延を許容するため、デフォルト値の30秒から120秒に延長しました。これによりエラー削減を狙っていますが、接続タイムアウトに関するCloudWatchログ等の追跡は未実施です。

- **sort_buffer_size / join_buffer_size = 3MB**
ORDER BY や JOIN が多い処理を想定し、標準値の256KBから3MBへ拡張しています。全体メモリとのバランスを取りつつ、特定処理の速度改善を期待を考慮しています。

- **innodb_io_capacity = 200**
ストレージはgp2（汎用SSD）であり、ベースIOPSが100〜200程度であるため、MySQL公式に従い200を設定しました。I/Oスパイクの平滑化を狙った設定ですが、負荷シミュレーションやログベースでの効果検証は未対応です。

- **innodb_read_io_threads / innodb_write_io_threads = 4**
スレッドの並列処理によって読み書きI/Oを最適化する目的で設定しています。
デフォルト値で問題は出ていませんが、意図した効果が得られているかのメトリクス解析は未着手です。

- **slow_query_log = 1 / long_query_time = 2秒**
ユーザーが「遅い」と感じる閾値を2秒と見なし、該当クエリを記録するためにこの設定を採用しました。今後Datadogのログ収集とアラート設計に連携させることも可能ですが、本設定による記録件数の収集やパフォーマンス改善施策との連動は未実施です。

- **character_set_server = utf8mb4 / collation_server = utf8mb4_0900_ai_ci**
絵文字や日本語、LLM生成テキストを正しく保存・検索できるよう、文字コード・照合順序を設定しています。Claudeの出力には絵文字や絵文字コード、漢字・記号が含まれるため、マルチバイト文字と照合順の対応が必要であるためです。UI面での検索精度改善を意図していますが、LIKE検索のヒット率や照合性能への影響は評価していません。

- **time_zone = Asia/Tokyo**
すべてのログ・監視ツールと時刻を統一するため、明示的に日本時間を指定しました。ログの突合がしやすくなるメリットがありますが、この設定が影響した具体的な障害切り分け事例などは未検証です。

## パフォーマンス検証はどうやって行うのか
今回の設計は負荷検証やベンチマークを行う前提の暫定構成です。構築時点では負荷テストや継続的なモニタリングによる裏付けは行っていませんが、以下の観点でパフォーマンスを可視化し、次回以降の改善に活かす計画です。

### ① CloudWatchメトリクスによるリソース利用状況の監視
まずはCloudWatchを使って、RDSインスタンスの主要なリソースの使用状況を把握します。ここでは、接続数、メモリ、I/Oの3点を中心に観測を開始します。

- **DatabaseConnections（同時接続数）**
→ `max_connections` の妥当性を確認するために、CloudWatch上で `DatabaseConnections` メトリクスを定期的に監視します。例えば、ピーク時に常に80～90程度の接続数がある場合、設定値100では不足が懸念されるため、150などに増やす必要があります。逆にピークが40程度で安定している場合は、安全マージンを見直す根拠となります。

- **FreeableMemory（メモリ使用状況）**
→ `innodb_buffer_pool_size` を3GBに設定していますが、実際に `FreeableMemory` が200MB以下を長時間維持している場合、メモリが逼迫している可能性があります。バッファサイズの調整か、インスタンススペックの引き上げを検討します。

- **WriteIOPS / ReadIOPS（I/O性能）**
→ ストレージはgp2（ベースIOPSが100〜200）ですが、実際に `WriteIOPS` や `ReadIOPS` がバースト上限を頻繁に超えている場合、EBSのバースト制限や `innodb_io_capacity` の設定見直しが必要になります。 

CloudWatchでは1分単位のメトリクス収集が可能なので、運用初期は1日単位でグラフを確認し、異常傾向があるかどうかを確認するところから始めます。

### ② Datadogによるクエリログ分析とアラート設計
次に、Datadogを用いてRDSのログを監視し、**遅延クエリやリソースの逼迫を早期に検知できる仕組み**を整えます。

- **slow_query_log の収集と件数の推移確認**
→ long_query_time = 2 で記録された遅延クエリをDatadogで可視化し、アプリケーション側またはインデックス設計の改善候補を特定。

- **アラート条件の具体例**

`DatabaseConnections  > 90` が連続5分以上 → 接続過多のアラート

`FreeableMemory  < 100MB`  → メモリ逼迫のアラート

` slow_query_log 件数 > N件/h`  → チューニング対象クエリの洗い出し開始トリガー

## ③ SQLレベルでのクエリ解析とボトルネックを特定する
CloudWatchやDatadogのモニタリングによって、パフォーマンスの問題が特定のクエリに起因していると判断された場合は、SQLレベルで詳細な分析を行います。これは、設定したRDSパラメータがアプリケーションの実際のクエリ特性に合っているかを検証するために非常に重要です。

#### 実行計画と処理統計の取得

- `EXPLAIN`の使用
遅延が見られるクエリに対して `EXPLAIN` を実行し、フルスキャンやインデックス利用の有無を確認します。例えば、`type = ALL`（フルスキャン）が発生していたり、 `rows` の値が数千件に達していれば、インデックスの不足や不適切な条件指定が疑われます。実行計画からは、どのカラムにインデックスが効いているか、どの結合方法が使われているかを確認することができます。

- `SHOW STATUS` / `SHOW PROFILE` を使ったリソース使用分析
クエリごとのリソース消費や処理の各ステップにかかっている時間を確認することで、ソート、送信、ファイル読み出しなどボトルネックとなっている処理を特定することができます。

### パフォーマンス指標からパラメータ設定の妥当性を検証
さらに、MySQLの内部統計から以下のような指標を確認し、パラメータ設定との整合性を評価します。

- `Handler_read_rnd_next`
フルテーブルスキャンの回数を示す指標です。値が多い場合は、インデックスが活用されていないクエリが多いことを意味し、インデックス設計の見直しやクエリ修正が必要です。
- `Created_tmp_disk_tables`

一時テーブルがメモリ上ではなくディスク上に作成された回数を示します。数値が多い場合は、`tmp_table_size` や `max_heap_table_size`が不足している可能性があり、これらのパラメータの引き上げを検討します。

- `Innodb_buffer_pool_reads` /  `Innodb_buffer_pool_read_requests `
バッファヒット率（= 1 - `Innodb_buffer_pool_reads` / `read_requests`）を算出し、innodb_buffer_pool_size のサイズが適正かどうかを確認します。ヒット率が95%未満であれば、メモリ不足が疑われます。

このように、SQL単位での分析を行うことで、アプリケーションの実クエリとRDSパラメータの設定とのミスマッチを定量的に検出できます。CloudWatchやDatadogと連携しながら、実行計画・統計情報を用いた精度の高いボトルネック分析を実現することが、次の設計改善に繋がります。

## おわりに
本記事では、CloudFormationを用いてRDSパラメータをチューニングした理由と設計意図を、アプリ要件と照らし合わせて整理しました。ハッカソンでは発表をもって開発が一区切りとなり、その後AWS環境は停止済みのため、実運用におけるパフォーマンス検証は未着手の状態です。しかし、設計段階でどのような要件を想定し、なぜその設定値を選んだのかを明文化することで、今後の再開発やチーム内のナレッジ共有にも繋がると考えます。


https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/Appendix.MySQL.Parameters.html

https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/Appendix.MySQL.Parameters.html

https://aws.amazon.com/jp/blogs/news/tuning-amazon-rds-for-mysql-with-performance-insights/

https://www.alibabacloud.com/help/ja/rds/apsaradb-rds-for-mysql/optimize-the-parameters-of-an-apsaradb-rds-for-mysql-instance

https://dev.mysql.com/doc/refman/8.0/ja/added-deprecated-removed.html

https://dev.classmethod.jp/articles/aws-summit-b3-04/

https://docs.datadoghq.com/ja/

https://qiita.com/free-honda/items/fdab08e78df596f3be60

https://qiita.com/free-honda/items/ed7085cb4657a252668d