遅延クエリ(スロークエリ)の対処法について学んだので、まとめてみたいと思います。
Herokuの良質な記事
この記事が評判がよく、良い感じにまとまっていました。
以下、重要なところを引用します。
[高コストなクエリの原因]
- 適切なインデックスがないため、サイズの大きいテーブルで検索が低速になる
- 未使用のインデックスが原因で
INSERT
、UPDATE
、DELETE
の各操作が低速になる - 非効率的なスキーマに起因する不適切なクエリ
- クエリの設計が非効率的
- データベースのサイズが大きいことやロック競合の多さが原因で (論理バックアップ)に使用される)
COPY
操作が低速になる
[高コストなクエリの解決策]
- (pg:psql) 経由で)
EXPLAIN ANALYZE
を実行して、クエリの実行時間の大半を占めている処理を突き止めます。- たとえば、大きなテーブルのシーケンシャルスキャンは、多くの場合、悪い兆候です。効率的なインデックスによってクエリのパフォーマンスは大幅に向上します。インデックスの戦略を考えるときは、部分インデックスなど、Postgres のすべてのテクニックを検討してください。
heroku pg:diagnose
を実行して未使用のインデックスを特定します。不要なインデックスがあれば削除します。- データベースを最新バージョンにアップグレードします。Postgres のパフォーマンスは、リリースのたびにほぼ確実に向上します。
- 大規模なデータベースでは、日常のディザスタリカバリーの目的には Heroku の継続的保護の使用を優先します。自動
pg:backups
プランがあれば削除し、pg:backups
はデータの抽出または移行のみに使用します。 - 小規模なデータベースの場合、低速な論理バックアップの原因はロック競合の結果である可能性があります。
- Heroku pg-extras の
pg:outliers
を使用して、実行時間の多くを占めているクエリを突き止めます。
特に、EXPLAIN
およびEXPLAIN ANALYZE
コマンドは、PostgreSQLを使ううえで重要です。
データベースがどのようにクエリを実行しようとしているかを理解し、パフォーマンスを最適化するための情報を得るためにEXPLAIN
コマンドが使われるからです。
ChatGPT
GPT先生いわくこのような感じでした。
Node.jsにおけるサブクエリとPreloadの比較例(Prisma)
サブクエリ
const users = await prisma.user.findMany(); for (let user of users) { user.orders = await prisma.order.findMany({where: {userId: user.id}}); }
Preload
const users = await prisma.user.findMany({ include: { orders: true, }, });
Railsにおける同様の例(N+1問題)
N+1問題が発生するケース
users = User.all users.each do |user| user.orders = Order.where(user_id: user.id) end
Eager Loading(事前読み込み)を利用したケース
users = User.includes(:orders).all
学んだこと
パフォーマンスに問題があるときは、バックエンドの言語やフレームワークの問題ではなく、DB周りのデータの呼び出し方のほうが大きな要因になることが分かりました。
スロークエリの問題について詳しい所まで知らなかったので勉強になりました。
一つ一つ地道に改善するしかなさそうで、大規模なプロジェクトにおいては早めの対処が必要なことも分かりました。
また、実際に自分がこのような問題に遭遇したとき、フィヨルドブートキャンプで習ったN+1問題のようなPreload(Eager Loading)の方法しか思いつかなかったので、インデックスの最適化・クエリの最適化について、もっときちんと理解する必要があると感じました。