
Aurora DSQLのN+1問題をAI(Kiro)で90秒→8秒に改善してみた。
昨年、2025年5月にGA(一般提供)された Amazon Aurora DSQLを利用して、ブログ補助用のAPIを開発する機会がありました。その際、記事数に比例しタグ検索のレスポンスが悪化する事象に直面しました。
本記事では、AIアシスタントツール(Kiro)を活用して原因調査を行い、約90秒かかっていた処理を8秒まで短縮(91%改善)したプロセスと、その際のログによる裏付け、および修正時に発生した副作用への対処について共有します。
検証環境
開発環境の状況は以下の通りでした。レコード数は本番相当のデータボリュームを用意しています。
- データベース: Amazon Aurora DSQL
- コンピュート: AWS Lambda (Python 3.12)
- データ規模:
- 記事データ: 約60,000件
- タグデータ: 約7,300件
- 紐づけテーブル : 約431,500件
1. 事象の確認 (90秒のレイテンシ)
開発中のブログAPIにおいて、特定のタグ(例: AWS)の記事検索を行うと、タイムアウトまたは極端な遅延が発覚しました。まずは curl コマンドを使用し、実測値を計測しました。
応答時間の計測
# 実行コマンド(timeで計測)
time curl -s "API_URL?tags={tag_name}&limit=3" > /dev/null
| 検索タグ | 該当記事数 | 応答時間 | 判定 |
|---|---|---|---|
| lambda | 3,629件 | 6秒 | 遅延あり |
| security | 7,884件 | 12秒 | 遅延大 |
| aws | 28,074件 | 90秒 | タイムアウト |
記事数(該当ヒット数)に比例して線形に応答時間が悪化している傾向が見受けられました。
2. AIによる仮説と検証 (EXPLAIN ANALYZE)
90秒という異常なレイテンシから、アプリケーション側の非効率なループ処理(N+1問題)を疑い、AIアシスタント(Kiro)に調査を依頼しました。
Kiroによる自律的な調査準備
状況を伝えると、Kiroは即座に原因を推定し、解決策の提案にとどまらず、「検証用スクリプトの生成」から「実行準備」までを自律的に完了させ、実行許可を求めてきました。
Kiro:
「N+1問題の可能性が高いです。Aurora DSQL(PostgreSQL互換)のEXPLAIN ANALYZEを使用して、実際のクエリ実行計画を取得すべきです。
EXPLAIN ANALYZEスクリプトを作成しました。実行しますか?」
提示されたPythonスクリプト内のSQLを確認し、データ破壊のリスクがない(参照系のみである)ことを確認した上で、実行許可を与えました。実質的に、人間側が行ったのは「コードレビューと承認のみ」でした。
調査対象のデータ規模
検証にあたり、パフォーマンスのボトルネックとなっているタグ紐づけテーブルの統計情報を確認しました。
| 項目 | 数値 |
|---|---|
| 総レコード数 | 431,509件 |
| ユニークなタグ数 | 11,775個 |
| ユニークな記事数 | 59,721記事 |
| 記事あたりの平均タグ数 | 7.23個 |
約6万記事に対し、平均7個以上のタグが付与されており、合計43万行のレコードが存在します。この規模のテーブルに対し、非効率なアクセスが行われたことが疑われる状況でした。
実行計画の分析結果
Kiroが生成したスクリプトを実行し、以下の EXPLAIN ANALYZE ログを取得しました。
Execution Time: 31913.008 ms
-> Nested Loop (actual time=2583.240..13677.777 rows=28074 loops=1)
-> Index Only Scan on posts_pkey (actual rows=1 loops=28079)
-- ↑ 28,079回のループ(Index Scan)が発生
-> Index Only Scan on post_summary_pkey (actual rows=1 loops=28074)
-- ↑ 28,074回のループ(Index Scan)が発生
ログから、合計56,000回以上のIndex Scanが発生している事実が特定されました。
43万件規模の post_tag テーブルから抽出された「aws」タグ付きの約2.8万件の記事IDに対し、1件ずつループ処理で再検索をかけていたことが、90秒というタイムアウトの原因であると断定しました。
3. 修正対応 (サブクエリからJOINへ)
AIより「IN (SELECT ...) のサブクエリ構造を JOIN に変更することで、PostgreSQLのオプティマイザが効率的な結合処理を選択できる」との提案がありました。
修正前のコード(Python)
# サブクエリを使用(アンチパターン)
where_conditions.append("""
bm.id IN (
SELECT blog_post_id
FROM post_tag
WHERE tag_name = ANY(%s)
GROUP BY blog_post_id
HAVING COUNT(DISTINCT tag_name) = %s
)
""")
修正後のコード
# JOINを使用
from_clause = """
FROM blogpost_meta bm
JOIN post_tag bat ON bm.id = bat.blog_post_id -- INNER JOINに変更
LEFT JOIN blogpost_ribbon br ON bm.id = br.blog_post_id
LEFT JOIN blogpost_ai_summary bas ON bm.id = bas.id
"""
query = f"""
SELECT ...
{from_clause}
WHERE bat.tag_name = ANY(%s) -- WHERE句でフィルタ
GROUP BY bm.id, ...
HAVING COUNT(DISTINCT bat.tag_name) = %s
ORDER BY bm.first_published_at DESC
"""
Lambda関数を更新し、再度計測を実施しました。
4. 改善効果の確認
修正後、同じ条件で curl コマンドによる計測を行いました。
| 検索タグ | 修正前 | 修正後 | 改善率 |
|---|---|---|---|
| lambda | 6秒 | 5秒 | 17% |
| security | 12秒 | 5秒 | 58% |
| aws | 90秒 | 8秒 | 91% |
データ量が多い「aws」タグにおいて、90秒から8秒へ、約91%の短縮を確認しました。これにより、N+1問題が解消されたと結論づけました。
5. 副作用の発生と対処 (ソート順序)
パフォーマンスは改善しましたが、新たな問題として「記事のソート順(公開日順)が崩れている」という事象を確認しました。
事象ログ
curl -s "API_URL?author=suzuki-ryo&limit=10" | jq '.articles[] | .published_at'
"2025-11-25T05:25:49.676000"
"2025-01-10T06:27:29.620000"
"2025-11-25T14:27:51.467000"
日付が降順になっていません。
調査の結果、N+1対策の過程でクエリ内の DISTINCT ON 句を削除した際、ORDER BY 句との整合性が取れなくなっていたことが判明しました。PostgreSQLの仕様上、DISTINCT ON を使用する場合は ORDER BY の第一引数が一致している必要がありますが、削除後はその制約がなくなるため、意図しないソート結果となっていました。
修正対応
ORDER BY 句を、主キー(bm.id)ではなく、本来の要件である公開日(bm.first_published_at)優先に変更しました。
-- 修正前
ORDER BY bm.id, bm.first_published_at DESC
-- 修正後
ORDER BY bm.first_published_at DESC
再デプロイ後、意図通りに時系列順にデータが取得できることを確認しました。
"2026-01-25T13:21:17.055000"
"2026-01-17T16:34:16.857000"
"2026-01-16T05:50:00.259000"
6. さらなる最適化 (複数タグ検索)
単一タグ検索は高速化されましたが、複数タグ(例: tags=aws,ec2,rds)を指定した際に、再び19秒程度の遅延が発生しました。これは、全テーブルをJOINした後に巨大な結果セットに対して GROUP BY を行っていたことが原因でした。
これに対し、「先にサブクエリ内でIDを絞り込み(Filtering)、その結果に対してJOINを行う」アプローチを採用しました。
-- 最適化後イメージ
SELECT bm.id, ...
FROM blogpost_meta bm
INNER JOIN (
-- ここで先に対象IDを絞り込む
SELECT blog_post_id
FROM post_tag
WHERE tag_name = ANY(ARRAY['aws', 'ec2', 'rds'])
GROUP BY blog_post_id
HAVING COUNT(DISTINCT tag_name) = 3
) AS matched_posts ON bm.id = matched_posts.blog_post_id
...
この変更により、タグ数が増加してもレスポンスタイムが約8秒前後で安定することを確認しました。
まとめ
Amazon Aurora DSQL環境下における、Kiro を用いたパフォーマンスの改善事例を紹介しました。
Aurora DSQLは分散データベースでありながらPostgreSQL互換のエンドポイントを提供し、非常にシンプルに利用開始できます。しかし、今回のように生SQLで複雑なクエリを記述する場面では、エンジニア自身の実装力がパフォーマンスに直結します。
パフォーマンスチューニングにおいて、「不足しているインデックスを追加する」という対策は、比較的多くのエンジニアが最初に思い至る定石です。しかし、今回のボトルネックは結合アルゴリズムの非効率性にありました。
EXPLAIN ANALYZE の結果から「オプティマイザがなぜその実行計画を選んだのか」を読み解き、より効率的な結合方式(Hash Join等)へ誘導するためにクエリ構造自体をリファクタリングする判断は、高度なデータベースエンジニアリングの知識を必要とします。
Kiroは、単なるコード補完ツールとしてではなく、この「実行計画の解析と、結合アルゴリズムを意識した構造改革」というDBAの領域まで自律的にカバー、心強いパートナーであることを実感できました。
Aurora DSQLのような新しいデータベース技術を採用する際には、その能力が発揮する事が期待できる AIエージェントのKiroを、ぜひお試しください。







