💎

【Rails】trilogyとmysql2によるActiveRecordの速度比較

2024/02/09に公開

概要

trilogyなるMySQLのデータベースアダプタが速い、ということで試しました。

今回はmysql2とtrilogyで同じ計測を行い、CRUD全般(Create/Read/Update/Delete)でtrilogyの方が速い結果になりました。
ただし、activerecord-importによるバルクインサートは遅くなりました。

2024.3.1追記
バルクインサートをimport!からinsert_all!に変えたところ、trilogyの方が速い結果になりました。

2024.3.19追記
trilogy+activerecord-importのバルクインサートがなぜ遅いか追跡調査したところ、そもそもimport!でバルクインサートが発行されていませんでした。
activerecord-import 1.6.0 でバルクインサートで動作するようになったので、計測し直しました。
検索結果の大きいSELECTはtrilogyの方が速い、更新はmysql2の方が若干速い、という結果になりました。

trilogyはGitHub社より公開されています。
https://github.com/trilogy-libraries/trilogy

mysql2で環境構築時に詰まりがちなlibmysqlclientにtrilogyは依存しない点も嬉しいポイントです。

導入

Rails 7.1なら簡単に導入できます。

Gemfile
gem 'trilogy'
config/database.yml
default: &default
  adapter: trilogy # ←mysql2から変更
  encoding: utf8mb4
  collation: utf8mb4_bin
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV.fetch("MYSQL_USERNAME") { "root" } %>
  password: <%= ENV.fetch("MYSQL_PASSWORD") { "" } %>
  host: localhost

試していませんが、Rails 7.1未満だと、別途アダプタがいるようです。

計測

2330件、2.5MBのテーブルに対し、以下のベンチマークコードで計測しました。

ベンチマークコード
# frozen_string_literal: true

# active recordの adapterによるパフォーマンスの差異を確認するためのベンチマーク

Benchmark.bm 20 do |r|
  loop_cnt = 1_000
  bulk_loop_cnt = 100
  r.report 'find' do
    loop_cnt.times do
      TestModel.find(1)
    end
  end

  r.report 'where' do
    loop_cnt.times do
      TestModel.where(category: 50).to_a
    end
  end

  r.report 'all' do
    loop_cnt.times do
      TestModel.all.to_a
    end
  end

  id_from = TestModel.maximum(:id) + 1
  test_data = loop_cnt.times.map do |i|
    TestModel.new(id: id_from + i, name: 'insert')
  end

  r.report 'insert' do
    test_data.each(&:save!)
  end

  r.report 'update' do
    test_data.each do |test_unit|
      test_unit.update!(name: 'update')
    end
  end

  r.report 'delete' do
    test_data.each(&:delete)
  end

  id_from = TestModel.maximum(:id) + 1
  test_data = loop_cnt.times.map do |i|
    TestModel.new(id: id_from + i, name: 'insert')
  end

  now = Time.current
  test_unit_hashes = test_data.map do |unit|
    unit.created_at = now
    unit.updated_at = now
    unit.attributes
  end

  r.report 'import!' do
    bulk_loop_cnt.times do
      TestModel.import!(test_data)
      TestModel.delete(test_data.first.id .. test_data.last.id)
    end
  end

  r.report 'insert_all!' do
    bulk_loop_cnt.times do
      TestModel.insert_all!(test_unit_hashes)
      TestModel.delete(test_data.first.id .. test_data.last.id)
    end
  end

  TestModel.import!(test_data)
  r.report 'bulk delete' do
    TestModel.delete(test_data.first.id .. test_data.last.id)
  end
end

mysql2

                           user     system      total        real
find                   0.255225   0.037582   0.292807 (  0.347713)
where                  4.226403   0.384536   4.610939 (  5.328990)
all                   21.035168   1.047517  22.082685 ( 33.770121)
insert                 0.745051   0.053408   0.798459 (  1.303586)
update                 0.636618   0.045266   0.681884 (  1.073740)
delete                 0.175155   0.012511   0.187666 (  0.316065)
import!                4.403879   0.059411   4.463290 (  7.190781)
insert_all!            4.814219   0.126590   4.940809 (  7.764207)
bulk delete            0.000262   0.000022   0.000284 (  0.003004)

trilogy

                           user     system      total        real
find                   0.240659   0.039483   0.280142 (  0.339110)
where                  3.697752   0.123468   3.821220 (  4.004435)
all                   17.694333   0.445978  18.140311 ( 18.363494)
insert                 0.807716   0.074647   0.882363 (  1.504077)
update                 0.928170   0.087028   1.015198 (  1.745315)
delete                 0.132295   0.014841   0.147136 (  0.282099)
import!                4.485367   0.045594   4.530961 (  7.350747)
insert_all!            4.813142   0.099296   4.912438 (  8.015878)
bulk delete            0.000293   0.000030   0.000323 (  0.003103)

比較

mysql2 trilogy 比率
find 0.347713 0.33911 97.53%
where 5.32899 4.004435 75.14%
all 33.770121 18.363494 54.38%
insert 1.303586 1.504077 115.38%
update 1.07374 1.745315 162.55%
delete 0.316065 0.282099 89.25%
import! 7.190781 7.350747 102.22%
insert_all! 7.764207 8.015878 103.24%
bulk delete 0.003004 0.003103 103.30%
  • totalとrealに結構な差分が生じていたので、realで比較
    • MySQLのI/O待ち?
  • そもそも軽いfindは大差なし
  • SELECT結果が大きい場合、trilogyが優位
  • 更新に大差はないが、mysql2が若干優位

2024.3.1追記
バルクインサートをimport!からinsert_all!(activerecord-import不要)に変えたところ、mysql2 -> trilogyでrealが85%になりました。

2024.3.19追記
trilogyでバルクインサートが動作するようになったactiverecord-importを1.6.0 にアップデートして再計測しました。

Happy Elements

Discussion