LEFログ:学習記録ノート

leflog: 学習の記録をどんどんアップしていきます

遅延クエリの対処法(スロークエリ問題)

遅延クエリ(スロークエリ)の対処法について学んだので、まとめてみたいと思います。

Herokuの良質な記事

この記事が評判がよく、良い感じにまとまっていました。

高コストなクエリ | Heroku Dev Center

以下、重要なところを引用します。

[高コストなクエリの原因]

  • 適切なインデックスがないため、サイズの大きいテーブルで検索が低速になる
  • 未使用のインデックスが原因で 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先生いわくこのような感じでした。

ChatGPT先生その1

ChatGPT先生その2

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)の方法しか思いつかなかったので、インデックスの最適化・クエリの最適化について、もっときちんと理解する必要があると感じました。

おまけ:PreloadとEager Loadingの違い

【Rails】N+1問題はincludesで万事OKと思っていた。