はじめまして!サーバサイドエンジニアと見せかけてフロントエンドエンジニアと思いきや、やっぱりサーバサイドエンジニアの hikaru-kimi です!
技術ブログ初投稿です!どうぞお見知りおきください!
今回のテーマは、Ruby on Rails の ActiveRecord::Batches の in_batches
についてです!
Re:lation 開発では大量のデータを扱うことが多いのですが、大量のデータに対して each
処理を行うとメモリを大量に消費してしまい、パフォーマンスの悪化に繋がります。
そんなときに in_batches
を用いて(デフォルト値で言えば)1000件ずつ処理することで、少しでもメモリ消費を抑えたいです。
しかし!この in_batches
にはとんでもない罠があります!
なんと、in_batches
を用いる際は返される ActiveRecord::Relation のソート方法を指定することができないのです!!
従って、ループ処理の実行順に意味があるような処理を行う場合は要注意です!
(ちなみに、find_in_batches
や find_each
も同様です)
では実際に Rails のソースコードを読んでみましょう。
in_batches
の定義は以下の通りです。
https://github.com/rails/rails/blob/v7.0.4.3/activerecord/lib/active_record/relation/batches.rb#L204
そして、内部では reorder(batch_order(order))
されてます。
relation = relation.reorder(batch_order(order)).limit(batch_limit)
https://github.com/rails/rails/blob/v7.0.4.3/activerecord/lib/active_record/relation/batches.rb#L224
この batch_order
の定義を読むと、なんと primary_key
でソートされてますね!
( Rails アプリでは基本的には id
になりますね)
def batch_order(order) table[primary_key].public_send(order) end
実際に発行される SQL も確認してみましょう!
以下はサンプルアプリの rails c にて comments
テーブルのレコードを created_at
でソートして、かつ in_batches
でまとめて取得した例です。
irb(main):001:0> Comment.order(:created_at).in_batches{ |comment| p comment.inspect } Scoped order is ignored, it's forced to be batch order. (0.2ms) SELECT "comments"."id" FROM "comments" ORDER BY "comments"."id" ASC LIMIT ? [["LIMIT", 1000]]
勝手に comments
テーブルの主キーである id
の昇順でソートされてしまっていますね!
しかも、ご丁寧に指定した並べ替えは無視される旨出力されてます!
確かにフレームワークは便利です。しかし時には実装者の意図せぬ挙動をする場合もあります。 そのような場合は、丁寧にSQLを解読し、冷静にフレームワークのソースコードを追うことで問題解決に努めたいですね!
弊社インゲージでは、フレームワークをただ利用するだけでなく、そのソースコードをも追えるような開発意欲の高いエンジニアを募集しております!
社内でも、積極的かつ精力的にOSSへのコントリビュート活動を行うエンジニアが在籍しております!
最後までお読みくださったあなた!是非以下のリンクより、まずは気軽にカジュアル面談からのご応募をよろしくお願いいたします!