【Rails】trilogyとmysql2によるActiveRecordの速度比較
概要
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社より公開されています。
mysql2で環境構築時に詰まりがちなlibmysqlclient
にtrilogyは依存しない点も嬉しいポイントです。
導入
Rails 7.1なら簡単に導入できます。
gem 'trilogy'
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 にアップデートして再計測しました。
Discussion