rails_benchmark_suite 0.2.7 β†’ 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b72123961fe15e37146749930e0bd09d4d52a821c739a7ec662bc84976f9ac3b
4
- data.tar.gz: 8fb581b9a516e6eaec6b664d19bb9354f115bfb61282c1a3cb289d1d72ac203c
3
+ metadata.gz: c48ce5faaa5348bcd47cdd9c88ba9b1441d0f7bcd0a6fa601d84b36014673d1a
4
+ data.tar.gz: f39f188c55fe6749a5b9ba4ae9c5c44e926bd5d6588737f689192ab841deda92
5
5
  SHA512:
6
- metadata.gz: 63b8fdab356d3d60e2aa05f4e99df82da0617a988da5c92bf8ec430ed3016fa4e594c856ecfc0f03d9aee42bc63a365e54dc254014614124bd851339e6962ac9
7
- data.tar.gz: 96d310ed31f7ce0476a824c6a086ec5acbe4fa4cdfa5e9282b285a4dec9133f41997e5f534e07eb537a2d67b5c1a46b4e258aba2c97f491ac0088b131a407477
6
+ metadata.gz: 28f32ff03be6cbd83aabb1c3f8750316f5c3e2cb69de127885f96a4ef27841b12ee916999c2dea59c314605a6755f0aee34d0c610338c596bbff2789120a7ce0
7
+ data.tar.gz: 2e4f5a17c66e0caed9a95a46cdc3e0049783eea603f9cd263d699f492d49440f7ceaab45bf4c469b8b05fc316ca999052d12705dfe29b0edf54b032160fc2966
data/README.md CHANGED
@@ -1,23 +1,44 @@
1
- # Rails Benchmark Suite
1
+ # Rails Benchmark Suite πŸš€
2
+
3
+ **Standardized Hardware Benchmarking for Rails 8.1+**
2
4
 
3
5
  A standardized performance suite designed to measure the "Heft" of a machine using realistic, high-throughput Rails 8+ workloads.
4
6
 
5
- Unlike synthetic CPU benchmarks, **Rails Benchmark Suite** simulates Active Record object allocation, SQL query complexity, ActionView rendering, and background job throughput.
7
+ ## πŸ›  What is this?
8
+
9
+ Think of this as a **"Test Track" for Rails servers**. Unlike profilers that measure your specific application code, this gem runs a **fixed, standardized set of Rails operations** (Active Record object allocation, SQL query complexity, ActionView rendering, and background job throughput) to measure the raw performance of your server and Ruby configuration.
10
+
11
+ To ensure a level playing field, the gem boots an **isolated, in-memory SQLite environment**. It creates its own schema and records, meaning it **never touches your production data** and returns comparable results across any machine.
6
12
 
7
13
  ## πŸ“Š The "Heft" Score
8
14
 
9
- The Heft Score is a weighted metric representing a machine's ability to handle Rails tasks.
10
- - **Baseline:** A score of **100** is calibrated to represent an **AWS c6g.large** (ARM) instance.
11
- - **Objective:** To provide a simple, comparable number for evaluating different computing platforms (Cloud VMs, bare-metal, or local dev rigs).
15
+ The Heft Score is a weighted metric representing a machine's ability to handle Rails tasks.
16
+ * **Baseline:** A score of **100** is calibrated to represent an **AWS c6g.large** (ARM) instance.
17
+ * **Objective:** To provide a simple, comparable number for evaluating different computing platforms (Cloud VMs, bare-metal, or local dev rigs).
12
18
 
13
19
  ### Baseline Comparisons
20
+
14
21
  | Score | Classification | Comparable Hardware |
15
22
  | :--- | :--- | :--- |
16
- | < 40 | 🐒 Sluggish | Older Intel Macs, Entry-level VPS |
17
- | 60 | πŸš™ Capable | Standard Cloud VM (c5.large/standard) |
18
- | **100** | **🏎️ Baseline** | **AWS c6g.large (2 vCPU ARM)** |
19
- | 150+ | πŸš€ High Performance | Apple M-series Pro/Max, Ryzen 5000+ |
20
- | 300+ | ⚑ Blazing | Server-grade Metal, M3 Ultra |
23
+ | **< 40** | 🐒 Sluggish | Older Intel Macs, Entry-level VPS |
24
+ | **60** | πŸš™ Capable | Standard Cloud VM (c5.large/standard) |
25
+ | **100** | 🏎️ Baseline | AWS c6g.large (2 vCPU ARM) |
26
+ | **150+** | πŸš€ High Performance | Apple M-series Pro/Max, Ryzen 5000+ |
27
+ | **300+** | ⚑ Blazing | Server-grade Metal, M3 Ultra |
28
+
29
+ ---
30
+
31
+ ## πŸš€ Quick Start
32
+
33
+ Ensure you are in your Rails root directory and run:
34
+
35
+ ```bash
36
+ ruby --yjit -S bundle exec rails_benchmark_suite
37
+ ```
38
+
39
+ **Note:** `bundle exec` is mandatory for Rails environment stability and to prevent Minitest version conflicts.
40
+
41
+ ---
21
42
 
22
43
  ## πŸ›  Technical Philosophy
23
44
 
@@ -26,28 +47,16 @@ Rails Benchmark Suite prioritizes **Benchmarking** (via `benchmark-ips`) over **
26
47
  * **Benchmarking:** Focuses on macro-throughputβ€”"How many iterations can the hardware handle?" This provides the final Heft Score.
27
48
  * **Why no Profiling?** Profiling tools (like `StackProf` or `Vernier`) introduce instrumentation overhead that skews hardware metrics. We aim for "Conceptual Compression"β€”one clear number to inform infrastructure decisions.
28
49
 
50
+ ---
51
+
29
52
  ## πŸš€ Installation & Usage
30
53
 
31
54
  ### Requirements
32
- - **Ruby**: 3.3+ (Ruby with YJIT support highly recommended for accurate performance measurement)
33
- - **Database**: SQLite3
34
-
35
- ### Prerequisites
36
- * **Ruby:** 3.4.1+ (Recommended for latest YJIT/Prism performance)
55
+ * **Ruby:** 3.3+ (Ruby with YJIT support highly recommended)
56
+ * **Rails:** 8.1+
37
57
  * **Database:** SQLite3
38
58
 
39
- ### Standalone Usage
40
- If you want to test hardware performance without an existing application:
41
-
42
- ```bash
43
- git clone https://github.com/overnet/rails_benchmark_suite.git
44
- cd rails_benchmark_suite
45
- bundle install
46
- bin/rails_benchmark_suite
47
- ```
48
-
49
59
  ### Use within a Rails Application
50
- Rails Benchmark Suite is "Rails-aware." Adding it to your app allows you to benchmark your specific configuration and custom suites.
51
60
 
52
61
  Add to your Gemfile:
53
62
 
@@ -55,84 +64,69 @@ Add to your Gemfile:
55
64
  gem "rails_benchmark_suite", group: :development
56
65
  ```
57
66
 
58
- ## Usage
67
+ ### Usage Flags
68
+ * `--yjit`: Enables the Ruby JIT compiler (significant for Rails 8+ performance).
69
+ * `-S`: Corrects the path to look for the executable in your current bundle.
70
+ * `--json`: For programmatic consumption of results.
71
+ * `--skip-rails`: To ignore the host application and run in isolated mode.
59
72
 
60
- To get the most accurate 'Heft' score, run with YJIT enabled:
61
-
62
- ```bash
63
- ruby --yjit -S bundle exec rails_benchmark_suite
64
- ```
65
-
66
- **Why these flags?**
67
- - `--yjit`: Enables the Ruby JIT compiler (significant for Rails 8+ performance).
68
- - `-S`: Corrects the path to look for the executable in your current bundle.
69
- - `bundle exec`: Prevents version conflicts (e.g., Minitest) between the gem and your host application.
73
+ ### Standalone Usage
70
74
 
71
- **Standalone Usage:**
72
- If running outside a Rails project:
75
+ If you want to test hardware performance without an existing application:
73
76
 
74
77
  ```bash
78
+ git clone https://github.com/overnet/rails_benchmark_suite.git
79
+ cd rails_benchmark_suite
80
+ bundle install
75
81
  bin/rails_benchmark_suite
76
82
  ```
77
83
 
78
- **JSON Output:**
79
- For programmatic consumption:
80
-
81
- ```bash
82
- ruby --yjit -S bundle exec rails_benchmark_suite --json
83
- ```
84
-
85
- > **Note:** Use `--skip-rails` to ignore the host application and run in isolated mode.
84
+ ---
86
85
 
87
- ## Performance
86
+ ## πŸ§ͺ The "Heft" Suites
88
87
 
89
- **Understanding YJIT Status:**
88
+ The gem measures performance across critical Rails subsystems using a dedicated, isolated schema:
90
89
 
91
- If you see `YJIT: Disabled`, this is a result of your Ruby binary build, not the gem itself. The gem will work perfectly fine without YJIT, but performance measurements will be more accurate with YJIT enabled.
90
+ * **Active Record Heft:** Standardized CRUD: Creation, indexing, and complex querying.
91
+ * **Cache Heft:** High-frequency read/writes to the Rails memory store.
92
+ * **Solid Queue Heft:** Background job enqueuing and database-backed polling stress.
93
+ * **View Heft:** Partial rendering overhead and ActionView throughput.
94
+ * **Image Heft:** Image processing performance (requires libvips).
92
95
 
93
- To enable YJIT, you need Ruby compiled with YJIT support (requires Rust compiler during Ruby installation). See the Troubleshooting section below for installation instructions.
96
+ ---
94
97
 
95
- ## Troubleshooting
98
+ ## ⚠️ Troubleshooting
96
99
 
97
100
  ### YJIT Shows "Disabled"
98
101
 
99
- If you see `YJIT: Disabled (Requires Ruby with YJIT support for best results)`, it means your Ruby was not compiled with YJIT support. To get the best performance:
102
+ If you see `YJIT: Disabled`, it means your Ruby was not compiled with YJIT support.
100
103
 
101
- 1. Ensure `rustc` (Rust compiler) is installed on your system
102
- 2. Reinstall Ruby with YJIT support:
103
- ```bash
104
- # Using rbenv
105
- RUBY_CONFIGURE_OPTS="--enable-yjit" rbenv install 3.4.1
106
-
107
- # Using rvm
108
- rvm install 3.4.1 --enable-yjit
109
- ```
110
- 3. Verify YJIT is available: `ruby --yjit -e "puts RubyVM::YJIT.enabled?"`
104
+ * **Fix (rbenv):** `RUBY_CONFIGURE_OPTS="--enable-yjit" rbenv install 3.4.1`
105
+ * **Fix (rvm):** `rvm install 3.4.1 --enable-yjit`
111
106
 
112
107
  ### SQLite Lock Errors
113
108
 
114
- If you encounter `SQLite3::BusyException` or "database table is locked" errors, ensure you're running the latest version of the gem (v0.2.7+) which includes automatic concurrency retries with smart backoff.
109
+ Version 0.2.9+ includes surgical connection resets and randomized backoffs to handle SQLite concurrency. If issues persist, ensure no other processes are accessing the benchmark database.
115
110
 
116
- **v0.2.7+** includes:
117
- - Automatic retry logic with sleep backoff
118
- - Per-thread unique identifiers to prevent conflicts
119
- - Optimized busy timeout settings (10 seconds)
120
-
121
- If issues persist, try reducing concurrency or ensuring no other processes are accessing the benchmark database.
111
+ ---
122
112
 
123
113
  ## πŸ— Architecture
114
+
124
115
  * **Engine:** Built on `benchmark-ips`.
125
- * **Database:** Uses In-Memory SQLite with `cache=shared` for multi-threaded accuracy.
126
- * **Isolation:** Uses transactional rollbacks (`ActiveRecord::Rollback`) to ensure test isolation without the overhead of row deletion.
127
- * **Threading:** Supports 1-thread and 4-thread scaling tests to measure vertical efficiency.
128
- * **Modern Stack:** Optimized for Rails 8+ defaults, including Solid Queue simulation and YJIT detection.
116
+ * **Database:** Uses In-Memory SQLite with `cache=shared` and a 50-connection pool for multi-threaded accuracy.
117
+ * **Isolation:** Uses transactional rollbacks and Mutex-wrapped schema creation.
118
+ * **Threading:** Supports 1-thread and 4-thread scaling tests.
119
+
120
+ ---
129
121
 
130
122
  ## πŸ“œ Credits
131
- This project is a functional implementation of the performance benchmark vision discussed in the Rails community.
132
123
 
133
124
  * **Vision:** Inspired by @dhh in [rails/rails#50451](https://github.com/rails/rails/issues/50451).
134
125
  * **Initial Roadmap:** Based on suggestions by @JoeDupuis.
135
126
  * **Implementation:** The Rails Community.
136
127
 
128
+ ---
129
+
137
130
  ## πŸ“„ License
131
+
138
132
  The gem is available as open source under the terms of the MIT License.
@@ -17,12 +17,12 @@ module RailsBenchmarkSuite
17
17
  SETUP_MUTEX = Mutex.new
18
18
 
19
19
  def run
20
- # Hardened Isolation: Shared Cache URI for multi-threading (v0.2.7)
20
+ # Ultimate Hardening: Massive pool and timeout for zero lock contention (v0.2.9)
21
21
  ActiveRecord::Base.establish_connection(
22
22
  adapter: "sqlite3",
23
23
  database: "file:heft_db?mode=memory&cache=shared",
24
- pool: 16,
25
- timeout: 10000
24
+ pool: 50,
25
+ timeout: 30000
26
26
  )
27
27
 
28
28
  # The 'Busy Timeout' Hammer - force it directly on the raw connection
@@ -36,8 +36,10 @@ module RailsBenchmarkSuite
36
36
  end
37
37
  end
38
38
 
39
- # High-Performance Pragmas
40
- ActiveRecord::Base.connection.raw_connection.execute("PRAGMA synchronous = OFF")
39
+ # High-Performance Pragmas for WAL + NORMAL sync
40
+ ActiveRecord::Base.connection.raw_connection.execute("PRAGMA journal_mode = WAL")
41
+ ActiveRecord::Base.connection.raw_connection.execute("PRAGMA synchronous = NORMAL")
42
+ ActiveRecord::Base.connection.raw_connection.execute("PRAGMA mmap_size = 268435456") # 256MB - reduce disk I/O
41
43
 
42
44
  puts "Running RailsBenchmarkSuite Benchmarks..." unless @json_output
43
45
  puts system_report unless @json_output
@@ -57,7 +59,7 @@ module RailsBenchmarkSuite
57
59
 
58
60
  # Single Threaded
59
61
  x.report("#{suite[:name]} (1 thread)") do
60
- suite[:block].call
62
+ with_retries { suite[:block].call }
61
63
  end
62
64
 
63
65
  # Multi Threaded (4 threads)
@@ -66,7 +68,7 @@ module RailsBenchmarkSuite
66
68
  Thread.new do
67
69
  # Ensure each thread gets a dedicated connection
68
70
  ActiveRecord::Base.connection_pool.with_connection do
69
- suite[:block].call
71
+ with_retries { suite[:block].call }
70
72
  end
71
73
  end
72
74
  end
@@ -95,6 +97,19 @@ module RailsBenchmarkSuite
95
97
 
96
98
  private
97
99
 
100
+ def with_retries
101
+ yield
102
+ rescue ActiveRecord::StatementInvalid => e
103
+ if e.message =~ /locked/i
104
+ # Specifically drop the lock for THIS connection only
105
+ ActiveRecord::Base.connection.reset!
106
+ sleep(rand(0.01..0.05))
107
+ retry
108
+ else
109
+ raise e
110
+ end
111
+ end
112
+
98
113
  def system_report
99
114
  info = RailsBenchmarkSuite::Reporter.system_info
100
115
  yjit_status = if defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
@@ -6,39 +6,30 @@ require "active_record"
6
6
  RailsBenchmarkSuite.register_suite("Active Record Heft", weight: 0.4) do
7
7
  # Workload: Create User with Posts, Join Query, Update
8
8
  # Use transaction rollback to keep the DB clean and avoid costly destroy callbacks
9
- begin
10
- ActiveRecord::Base.transaction do
11
- # 1. Create - with unique email per thread
12
- user = RailsBenchmarkSuite::Models::User.create!(
13
- name: "Benchmark User",
14
- email: "test-#{Thread.current.object_id}@example.com"
15
- )
16
-
17
- # 2. Create associated records (simulate some weight)
18
- 10.times do |i|
19
- user.posts.create!(title: "Post #{i}", body: "Content " * 50)
20
- end
21
-
22
- # 3. Complex Query (Join + Order)
23
- # Unloading the relation to force execution
24
- RailsBenchmarkSuite::Models::User.joins(:posts)
25
- .where(users: { id: user.id })
26
- .where("posts.views >= ?", 0)
27
- .order("posts.created_at DESC")
28
- .to_a
29
-
30
- # 4. Update
31
- user.update!(name: "Updated User")
32
-
33
- # Rollback everything to leave the DB clean for next iteration
34
- raise ActiveRecord::Rollback
35
- end
36
- rescue ActiveRecord::StatementInvalid => e
37
- if e.message =~ /locked/i
38
- sleep(0.005)
39
- retry
40
- else
41
- raise e
9
+ ActiveRecord::Base.transaction do
10
+ # 1. Create - with unique email per thread
11
+ user = RailsBenchmarkSuite::Models::User.create!(
12
+ name: "Benchmark User",
13
+ email: "test-#{Thread.current.object_id}@example.com"
14
+ )
15
+
16
+ # 2. Create associated records (simulate some weight)
17
+ 10.times do |i|
18
+ user.posts.create!(title: "Post #{i}", body: "Content " * 50)
42
19
  end
20
+
21
+ # 3. Complex Query (Join + Order)
22
+ # Unloading the relation to force execution
23
+ RailsBenchmarkSuite::Models::User.joins(:posts)
24
+ .where(users: { id: user.id })
25
+ .where("posts.views >= ?", 0)
26
+ .order("posts.created_at DESC")
27
+ .to_a
28
+
29
+ # 4. Update
30
+ user.update!(name: "Updated User")
31
+
32
+ # Rollback everything to leave the DB clean for next iteration
33
+ raise ActiveRecord::Rollback
43
34
  end
44
35
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Check if vips is available at registration time
3
4
  begin
4
5
  require "image_processing/vips"
5
6
  require "fileutils"
@@ -9,6 +10,7 @@ begin
9
10
  FileUtils.mkdir_p(ASSET_DIR)
10
11
  SAMPLE_IMAGE = File.join(ASSET_DIR, "sample.jpg")
11
12
 
13
+ # Only register if vips is actually available
12
14
  RailsBenchmarkSuite.register_suite("Image Heft", weight: 0.1) do
13
15
  # Gracefully handle missing dependencies
14
16
  if File.exist?(SAMPLE_IMAGE)
@@ -22,13 +24,8 @@ begin
22
24
  end
23
25
  end
24
26
 
25
- rescue LoadError, StandardError => e
26
- # Register a skipped suite if Libvips is unavailable
27
- RailsBenchmarkSuite.register_suite("Image Heft (Skipped)", weight: 0.0) do
28
- @warned ||= begin
29
- warn "⚠️ [RailsBenchmarkSuite] ImageHeft skipped: #{e.message}. Install libvips to enable."
30
- true
31
- end
32
- end
27
+ rescue LoadError, StandardError
28
+ # Don't register the suite at all if vips is unavailable
29
+ puts "⚠️ Skipping Image Heft: libvips not available. Install with: brew install vips (macOS)"
33
30
  end
34
31
 
@@ -1,3 +1,3 @@
1
1
  module RailsBenchmarkSuite
2
- VERSION = "0.2.7"
2
+ VERSION = "0.2.9"
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_benchmark_suite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.7
4
+ version: 0.2.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - RailsBenchmarkSuite Contributors
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-01-02 00:00:00.000000000 Z
10
+ date: 2026-01-03 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: benchmark-ips