LoginSignup
28
27

More than 5 years have passed since last update.

Railsのタスクで100万レコードぐらいInsertした時のメモ

Last updated at Posted at 2015-02-20

とある案件でSQL Serverからデータを100万件ぐらい引っ張ってきて別のDBにそれをINSERTする必要があった。
普通にActive Recordを使ったら重すぎたのでその時のメモ。

複数のDBに繋ぐ

Azure上で動いているSQL Serverからデータを取ってきて、別のVMで動作しているMySQLにデータを投入する必要があったので複数のDBに接続するよ必要がありました。

これは

          ActiveRecord::Base.establish_connection(:local_database)

とか

      ActiveRecord::Base.establish_connection(:azure_database)

をSQL発行前に実行すれば解決。
引数はconfig/database.ymlの設定名

データを取ってきてINSERTする

普通にINSERTしていったら重いだろうなぁと思い、railsでBulk Insertする方法を探してみた。

結果、activerecord-importといgemがあったので使ってみた。

配列にActive Recordのオブジェクトを突っ込みまくって、

Model.import array

みたいに実行すればBulk Insertを実行できるよって感じだったのだが、扱うレコードが多いだけにやたらと重い。
数千件ごとに分けてみたり、色々やってみたのだがやっぱり重い。
htopやらsarやらでCPUやディスクの状況など調べてみるとiowaitはほぼ発生していないがCPUが常に100%稼働している。
(ちなみに環境はAzureのA1。CPUはコア数1つ)

100万件のデータをINSERTしようとすると11時間ほどかかりそうだということがわかり、どこがボトルネックか調べることにした。

activerecord-importの中身までは見ていないが、5000件のデータを処理した時に、importメソッドだけで70秒ほどかかっていた。

その間iowaitなどは発生しておらず、CPUの負荷は常に100%で、MySQL側に特にslow logも出ていない。

調べてみるとActive Recordはオブジェクトの生成処理が重いので、大量のselectの時はpluckを使えば早くなるよとかいう記事を見つけた。
selectの部分は確かにpluckにすると早くなったので、おそらくimportメソッドが重いのもActive Recordのオブジェクト生成処理のせいだろうなぁと思い生のINSERT文を生成し、実行するようにした。

          affected_rows = ActiveRecord::Base.connection.update(insert_sql)

生のINSERTはこんな感じで実行できる。
結果、5000件あたりの処理速度は140秒から10秒になり、35分ほどで100万件を処理することができるようになりました。

所感

  1. 大量レコードを処理する時はActive Record使わないほうが良さそう
  2. 今回書いてないけど、RailsからSQL Serverに接続するのが結構めんどくさかった。 主にfreetdsとかunixobdc周り。
28
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
27