はじめに
この記事の対象読者 : AWSでRDS/Auroraを運用しているSRE、バックエンドエンジニア、インフラエンジニアの方
こんにちは、マネーフォワードケッサイ開発本部カードプロダクト開発部のSREグループでインターンシップに参加しております、籔下悟琉です。
みなさんDBを運用していく中でレコード数増加に伴いクエリタイムが長くなりレスポンスが遅くなったと感じた経験があると思います。しかし「スロークエリのリアルタイム監視ができていない」、「スロークエリを改善したいが原因調査や解決に時間がかかる」、「監視できてもアラートが飛びすぎて重要なアラートが埋もれる」といった問題もあると思います。
この問題の解決方法として単にDBをスケールアップするという方法も考えられますが、スケールアップには費用増加も伴います。そこで私はバックエンドチームと協力してAWSのRDSのスロークエリのリアルタイム監視からパフォーマンスチューニングまでを自動化するシステムを開発しました。
背景
当初、私たちのチームでも、スロークエリをリアルタイム監視できておらず、どのクエリにどのくらいの時間がかかっているのか把握できていない問題がありました。特に金融サービスを提供している以上、 DBのダウンタイムが短時間でも莫大な損失 につながることもあり、事前の検知と迅速な対応が求められます。
そこで今回、以下の機能を持つスロークエリ監視システムを構築しました。
- リアルタイム検知とSlack通知 - CloudWatch Logsをリアルタイム監視してSlackにアラートを発報
- Suppress機能 - 既知のスロークエリを抑制してアラート疲れを防止
- Ask AI機能 - Claude Code Actionsが自動でコードを分析してDBパフォーマンスチューニング
本記事では、このシステムの設計と実装例について紹介します。
特に Ask AI機能は、開発者がSlackのボタンを押すだけでClaude Code Actionsが自動的にリポジトリを分析し、適切なDBパフォーマンスチューニングのPRを作成してくれる という、人間はPRをレビューしてマージ&デプロイするだけでDBパフォーマンスチューニングが完了するという攻めたシステムです。
システム全体像
まず、システムの全体アーキテクチャをご紹介します。

図1: スロークエリ監視システムの全体アーキテクチャ
使用技術
| レイヤー | 技術 |
|---|---|
| IaC(Infrastructure as Code) | Terraform (AWSリソース管理) |
| 監視基盤 | Lambda (Python), CloudWatch Logs |
| ストレージ | DynamoDB |
| 通知 | Slack API |
| AI分析 | Claude Code Actions |
| CI/CD | GitHub Actions |
全てサーバーレスで構築しており、 月額インフラコストは約$1 という低コストを実現しています。ClaudeのAPI使用量も1PRあたり約$1.5と低コストです。
Phase 1: リアルタイム検知とSlack通知
1-1. CloudWatch Logsからの検知
RDSのスロークエリログは、CloudWatch Logsに自動的に送信されます。 このログストリームに対して、Subscription Filterを設定することで、ログが発生した瞬間にLambdaを起動します。
1-2. SQL正規化によるクエリの一意化
スロークエリは、パラメータが異なるだけで本質的に同じクエリであることが多いです。 例えば、以下のようなものです。
-- 元のクエリ1 SELECT * FROM users WHERE id = 123 -- 元のクエリ2 SELECT * FROM users WHERE id = 456 -- 正規化後(どちらも同じdigest) SELECT * FROM users WHERE id = ?
SQL正規化を行うことで、パラメータ違いのクエリを同一視し、効率的に管理できます。 今回は正規化されたSQLをハッシュ化して、そのハッシュ値をキーとしてDynamoDBでスロークエリを一元管理しました。 以下は簡易的なSQL正規化実装例です。
class SQLNormalizer: def normalize(self, sql): """ SQLクエリを正規化してdigestを生成 """ # 数値リテラルを ? に置換 sql = re.sub(r'\b\d+\b', '?', sql) # 文字列リテラルを ? に置換 sql = re.sub(r"'[^']*'", '?', sql) # 空白を正規化 sql = re.sub(r'\s+', ' ', sql).strip() # SHA-256ハッシュ値を生成 digest = hashlib.sha256(sql.encode('utf-8')).hexdigest() return sql, digest
1-3. DynamoDBへの履歴保存
検知したスロークエリは、全てDynamoDBに保存します。これにより、以下のメリットがあります。
- 履歴管理 : いつ、どのスロークエリが発生しているか追跡
- 抑制管理 : Suppress/UnSuppress状態の管理
- Ask AI用データソース : 後述のAI分析で使用
DynamoDBスキーマ:
| 属性 | 型 | 説明 |
|---|---|---|
| digest (PK) | String | 正規化SQLのSHA-256 |
| is_suppressed | Number | 0/1 (抑制フラグ) |
| normalized_query | String | 正規化されたSQL |
| original_query | String | 元のSQL(サンプル) |
| query_time | Number | 実行時間(秒) |
| severity | String | WARNING/CRITICAL |
| message | String | 完全なスロークエリログ |
| suppressed_by | String | 誰によって抑制されたか |
1-4. Slack通知
検知したスロークエリは、Slackの専用チャンネルに通知します。
通知には以下の情報が含まれます。
- クエリ実行時間と重要度 (WARNING/CRITICAL)
- Digest (クエリの一意識別子)
- 正規化されたSQL (パラメータ化されたクエリ)
- SuppressボタンとAsk AIボタン ← 後述
セキュリティ上の配慮:
元のSQLクエリ(パラメータ含む)はDynamoDBに保存しますが、 Slack通知には正規化後のSQLのみを表示 します。
これにより、個人情報や機密情報の漏洩リスクを最小化しています。
Phase 2: Suppress機能でアラート疲れを防止
2-1. なぜSuppress機能が必要か
スロークエリ監視を始めると、すぐに直面する問題があります。それは 「既知のスロークエリで毎日アラートが飛んでくる」 という点です。
例えば、以下のようなケースが挙げられます。
- 日次バッチ処理で遅いクエリを実行している
- ロックがかかるクエリを実行している
- 対応予定だが優先度が低く、後回しにしている
これらのクエリで毎回アラートが飛んでくると、 本当に対応すべきスロークエリを見逃すリスク が高まります。
2-2. Suppressフローの実装
Slackの通知メッセージに「Suppress」ボタンを配置し、クリックするとモーダルが開きます。

図2: Slackアラートに表示されるSuppressボタン

図3: 抑制理由を入力するモーダルUI
実装のポイント:
- Slack Interactive Messages - ボタンクリックをLambda Function URLで受信
- 不正アクセス防止 - Slack署名検証を用いてセキュリティ保護を実施(※1)
- 理由の記録 - なぜ抑制したのかを記録(任意)
【補足事項】 ※1 不正アクセス防止について Lambda Function URLはCloudFrontやAWS WAFと組み合わせればIPアドレスで制限可能ですが、そもそもSlack Appの送信元IPアドレスは固定ではないため、URLはパブリック公開にする必要があります。この時、第三者から不正アクセスの可能性があるので、今回はSlack署名検証を用いてセキュリティ保護を行いました。
Suppressされたクエリは、DynamoDBの is_suppressed フラグが 1 になり、以降同じクエリが検知されても Slack通知がスキップ されます。
抑制の解除:
Suppress後のメッセージには「UnSuppress」ボタンが表示され、いつでも抑制を解除できます。

図4: Suppress後のメッセージ(UnSuppressボタンが表示される)
Phase 3: Ask AI機能 - Claude Code Actionsによる自動PR作成
3-1. 従来のスロークエリ対応フロー(手動)
ここまでで、スロークエリを発見してから修正するまでのフローは以下の通りでした。
- Slackアラートを確認
- CloudWatch Logsで詳細確認
- どのテーブル・カラムか特定
- 既存のコードで問題箇所を探す
- 修正案を設計
- PRを作成してレビュー依頼
- レビュー・デプロイ
エンジニアにとって、この作業は以下の問題を孕んでいました。
- 調査に時間がかかる
- パフォーマンスチューニング設計や知識が必要
- スロークエリが多いと繰り返し作業が多い
そこで、 Claude Code Actionsで自動化しよう と考えました。
3-2. Ask AIの実装アーキテクチャ
Ask AIボタンがクリックされると、以下のフローが実行されます。
- Slackボタンクリック
- Ask AI機能のLambda実行
- DynamoDBからスロークエリメッセージを取得
- LambdaからGitHub repository_dispatchを実行
- GitHub Actions起動
- Claude Code ActionsでRepository分析・PR作成
- 完了通知をSlackに送信
3-3. 実際の動作例
実際にAsk AIボタンをクリックすると、以下のようなフローで処理されます。
1. Slackに「分析開始」メッセージが投稿される
🤖 @user_id Claude Code Actions is analyzing the slow query and determining if a PR should be created.
2. 5〜15分後、Claude Code ActionsがPRを作成
注 : 以下のような場合はPRを作成しない判断をします。
- テーブル構造変更や、インデックス追加による他のクエリへの影響が大きいなど、変更によるリスクが高い場合
- 既にインデックスが設定されており、パーティショニングやキャッシュなどさらに高度な対応が必要な場合
3. Slackに完了通知
🤖 @user_id ✅ Analysis completed! Created PR: https://github.com/owner/repository/pull/1234 ## Summary - Added index to `transactions(user_id, created_at)` to optimize slow query - Query time: 5.2s → Expected: <0.5s ## Next Steps - Review the PR and merge if acceptable
4. PR作成完了

図5: PR作成完了例(インデックス追加による対応例)
3-4. Ask AI機能の制約と今後の改善
現時点での制約は以下の通りです。
- Claude API コスト : 1回あたり約$1.50(Sonnet 4.6使用時)
- 実行時間 : 5〜15分(コードベースのサイズに依存)
- 精度 : 本番DBに直接接続できないため、スキーマ情報は現在のコードから推測
今後の改善案:
- 精度向上 : Opusモデルへの切り替え検討(精度とコストのトレードオフ)
- 与える情報の精度 : クエリ実行計画の情報やパフォーマンススキーマの情報もプロンプトで与え、パフォーマンスチューニングの精度を向上させる
まとめ
本記事では、RDSスロークエリをリアルタイム監視し、Claude Code Actionsが自動でPRまで作成するシステムを紹介しました。
本システムの3つの特徴:
- リアルタイム検知 - CloudWatch Logs + Lambda + Slack
- Suppress機能 - アラート疲れの防止
- Ask AI機能 - Claude Code Actionsによる自動PR作成
特にAsk AI機能は、 エンジニアがレビューしMergeボタンを押すだけでパフォーマンスチューニングが完了する という、非常におもしろい機能だと自負しています。
もちろん、AIの精度やコストなど、まだ改善の余地は多くあります。 例えば、現状運用していく中で、AIが提案する改善案のリスク評価がどれくらい正確かどうかが不明なことや、このシステムによってどれだけコストが削減できたかの定量的な評価は今後の課題として残っています。
また、今後はこのシステムを既存のSLI/SLO(Service Level Indicator / Service Level Objective)の改善にも活用していきたいと考えています。スロークエリの検知から改善までのリードタイムや、パフォーマンス改善によるレスポンスタイムの短縮など、システムの状態を定量的に評価していく予定です。
参考リンク
最後に
マネーフォワード福岡開発拠点では、こうした挑戦的な技術開発に取り組むエンジニアを募集しています!