2025/05/23 18:46 Big Problems From Big IN lists with Ruby on Rails and PostgreSQL

やあ、ロボ子。今日はデータベースのパフォーマンス問題について話すのじゃ。

博士、こんにちは。データベースのパフォーマンス問題ですか。具体的にはどのような問題でしょうか?

今回は、大量の値を持つ`IN`句、いわゆるBig IN listsがパフォーマンスを悪化させるという問題じゃ。

`IN`句に大量の値があると、そんなにパフォーマンスが悪くなるんですね。どうしてでしょう?

`IN`リストの値は定数として扱われて、統計情報が利用できないからのじゃ。それに、解析に時間がかかってメモリも消費するぞ。

なるほど。統計情報が使えないと、データベースが最適な実行計画を選べなくなるんですね。

その通り!PostgreSQLがカーディナリティや行の選択性を誤って推定して、インデックススキャンではなくシーケンシャルスキャンを選んでしまう可能性が高まるのじゃ。

シーケンシャルスキャンは遅いですからね。具体的には、どのような状況でこの問題が発生しやすいのでしょうか?

Active Recordで`pluck()`を使ってIDリストを作って、別のクエリに渡す場合とか、`includes`や`preload`のようなeager loadingメソッドを使う場合に発生しやすいのじゃ。

N+1問題を修正しようとして、逆にパフォーマンスが悪化してしまうこともあるんですね。

そうならないための解決策もちゃんとあるぞ!クエリをJOIN操作に再構築したり、`IN`の代わりに`ANY`や`SOME`を使うのが有効じゃ。

`ANY`や`SOME`ですか。`IN`とどう違うんですか?

`ANY`は配列を扱える柔軟性があるのじゃ。他にも、`VALUES`句を使ったり、一時テーブルを作ってインデックスを貼るのも効果的じゃぞ。

一時テーブルですか。それは少し手間がかかりそうですね。

手間はかかるけど、効果は大きいぞ!あと、`ANY`演算子と配列を使うのも、`IN`リストよりパフォーマンスが向上する可能性があるのじゃ。プリペアドステートメントもサポートするしな。

なるほど。色々な解決策があるんですね。試すのが大変そうです。

本番環境に近いデータでテストして、`EXPLAIN (ANALYZE, BUFFERS)`でクエリ実行計画を分析するのが大事じゃ。より少ないバッファアクセス、より低いコストを目指すのじゃ!

`EXPLAIN`は必須ですね。他に何か注意点はありますか?

`pg_stat_statements`の`query`フィールドを`'%IN (%'`でフィルタリングして、問題のあるクエリを特定するのも良いぞ。

それ便利ですね!PostgreSQL自体も改善されているんですか?

PostgreSQL 17では、スカラー式とインデックスの処理が効率化されるし、PostgreSQL 18では、`x IN (VALUES ...)`が自動的にScalarArrayOpExprに変換されるのじゃ。

着々と改善されているんですね。Rails側でも何か動きがあるんですか?

Sean Linsleyが`IN`の代わりに`ANY`を使う修正に取り組んでいるらしいぞ。これでグルーピングの問題も解決するはずじゃ。

それは楽しみですね!データベースのパフォーマンス改善、奥が深いですね。

そうじゃな。ところでロボ子、`IN`の反対は何じゃ?

`IN`の反対ですか?`NOT IN`でしょうか?

ブー!正解は、`OUT`じゃ!…って、そんなコマンドないけどな!
⚠️この記事は生成AIによるコンテンツを含み、ハルシネーションの可能性があります。