Seedファイルにコードを書いて一度にたくさんのデータを入れたい時ってありませんか?
自分は素数の数をデータベースにたくさん入れたく、スピード早くいれる方法を考えた。
環境: ruby2.1
rails4.0.2
パソコンのスペック:
MacBookAir 13-inch, Mid 2011
プロセッサ 1.7 GHz Intel Core i5
メモリ 4 GB 1333 MHz DDR3
データベース: mysql2
テーブルは非常にシンプルなもの
下記マイグレーション
カラムは二つのみ
IDとprime_numberの二つ
class CreatePrimeNumbers < ActiveRecord::Migration
def change
create_table :prime_numbers do |t|
t.integer :prime_number
end
end
end
コマンドは下記を何度も試して計測する。
> bundle exec time spring rake db:reset
極力メモリは消費しないようにする。ブラウザーも閉じて、必要ないアプリケーションも閉じる。
起動しているアプリはターミナルとエディタぐらいにする。
ネットで■ パフォーマンス比較 Cassandra、Mongodb、SQLite、H2、MySQL、Postgres の記事をみて最初はSQLite3を使おうと思った。
seedファイルは下記の通り
require 'Prime'
Prime.each(10 ** 6) do |prime|
PrimeNumber.create(
prime_number: prime
)
end
0から100万までの間に素数が何個あるか検査してデータベースに入れていく。
> bundle exec time spring rake db:reset
-- create_table("prime_numbers", {:force=>true})
-> 0.0120s
-- initialize_schema_migrations_table()
-> 0.0057s
172.77 real 0.34 user 0.04 sys
172秒(sqlite3)だった。
mysqlで試してみた。
> bundle exec time spring rake db:reset
-- create_table("prime_numbers", {:force=>true})
-> 0.0279s
-- initialize_schema_migrations_table()
-> 0.0396s
97.99 real 0.34 user
97秒(mysql2)と1.7倍に縮む、、、、
ActiveRecordを使ってるから遅い。
だからSQLの直打ちで書いて試してみる。
require 'Prime'
Prime.each_with_index(10 ** 6) do |prime, index|
ActiveRecord::Base.connection.execute("Insert INTO prime_numbers (id, prime_number) VALUES
( #{index + 1}, #{prime} );")
end
> bundle exec time spring rake db:reset
-- create_table("prime_numbers", {:force=>true})
-> 0.0173s
-- initialize_schema_migrations_table()
-> 0.0368s
32.03 real 0.33 user 0.03 sys
結果は32秒(mysql2)と大幅にアップ!
ちなみにSQLite3だと88秒(sqlite3)これまた大幅にアップ。
並列処理したらもっと早くなるんじゃないかと思い試してみる。
Rubyで並列処理が簡単に導入できるgemのParallelを入れる。
require 'Prime'
require 'parallel'
primes = Prime.each_with_index(10 ** 6)
Parallel.each(primes, :in_processes => 10 ) do |prime, index|
ActiveRecord::Base.connection.execute("Insert INTO prime_numbers (id, prime_number) VALUES
( #{index + 1}, #{prime} );")
end
> bundle exec time spring rake db:reset
-- create_table("prime_numbers", {:force=>true})
-> 0.0241s
-- initialize_schema_migrations_table()
-> 0.0410s
13.96 real 0.33 user 0.03 sys
結果は13秒!!!
19秒も縮んだ。
当初の172秒から13秒までと約13倍も早くなった。
バルクインサートしたら早くなるんじゃないか??と言うコメントをいただいて試してみた。
参考記事(http://qiita.com/xend/items/79184ded56158ea1b97a)
activerecord-importをgemにインストールすればimportのメソッドで使えるようになるらしい。
require 'Prime'
arr_prime = []
Prime.each(10 ** 6) do |prime|
arr_prime << PrimeNumber.new(:prime_number => prime)
end
PrimeNumber.import arr_prime
bundle exec time spring rake db:reset
-- create_table("prime_numbers", {:force=>true})
-> 0.0439s
-- initialize_schema_migrations_table()
-> 0.0468s
12.80 real 0.40 user 0.04 sys
結果は12秒!!!
SQLで直書きで書けばもっと早くなりそうだがあまりうまくいかず、思案中である。
まだまだもっと早くする方法があるのか探し中。
もっと早くする方法があればコメントお待ちしております!