BULK INSERTを並列で実行すると逆に遅くなることがある

BULK INSERTを並列で実行すると逆に遅くなることがあるのでメモ。



BULK INSERTを並列で実行すると逆に遅くなることがある


BULK INSERTを並列で実行した時に、直列で実行した時よりも遅くなる現象に遭遇しました。なんでもかんでも並列で実行すれば早くなるわけではないようです。


実行時のログは下記の通り。


# 5並列で実行した時
$ time bundle exec rails r bin/create_logs.rb
Log import 0 40344
Log import 4 40344
Log import 1 40344
Log import 3 40344
Log import 2 40344

real	2m29.625s
user	2m26.144s
sys	0m1.535s


# 直列で実行した時
$ time bundle exec rails r bin/create_logs.rb
Log import 0 40344
Log import 1 40344
Log import 2 40344
Log import 3 40344
Log import 4 40344

real	1m48.069s
user	1m40.856s
sys	0m1.056s

上記の比較では、5並列で実行した時に2分29秒、直列で実行した時に1分48秒かかっています。コネクションプール数は10にしています。


この結果から学べる教訓としては、「DBの並列実行性能がでるのは各クエリが高速に処理される場合であり、BULK INSERTのような各クエリが遅い処理を並列で行うとトータルでの処理時間はむしろ長くなることがある」とかでしょうか。


※厳密に検証したわけではないので何か間違ってるかもです。ご指摘は @ts_3156 までお願いします(^^)



BULK INSERTを並列で実行したコード


スクリプトの実行には rails runner、BULK INSERTには activerecord-import を使っています。


# [rails_root]/bin/create_logs.rb
def insert_logs(start_id1, start_id2, start_id3, loop_num)
  today = Date.today
  hours = 0..23
  logs = []
  id1_i = start_id1
  (start_id2..(start_id2 + loop_num)).each do |id2_i|
    (start_id3..(start_id3 + loop_num)).each do |id3_i|
      hours.each do |hour|
        logs << Log.new do |log|
          log.id1 = id1_i
          log.id2 = id2_i
          log.id3 = id3_i
          log.date = today
          log.hour = hour
          log.count = id1_i
        end
      end
      today += 1.day
    end
  end
  puts "Log import #{id1_i} #{logs.size}"
  Log.import logs
  return
end

LOOP_NUM = 40
THREADS_NUM = 5

def run_parallel
  threads = []
  THREADS_NUM.times do |i|
    threads << Thread.new do
      ActiveRecord::Base.connection_pool.with_connection do
        insert_logs(i, 1, 1, LOOP_NUM)
      end
    end
  end

  threads.each {|t| t.join }
end

def run_serial
  THREADS_NUM.times do |i|
    insert_logs(i, 1, 1, LOOP_NUM)
  end
end

# 直列に実行する場合
run_serial

# 並列に実行する場合
run_parallel


参考リンク


activerecord-import


著者プロフィール
Webサイトをいくつか作っています。
著者プロフィール