Skip to content

Commit 07b2ce2

Browse files
ioquatixsamuel-williams-shopify
authored andcommitted
Add SolidQueue benchmark.
1 parent 3bf70d5 commit 07b2ce2

File tree

9 files changed

+297
-1
lines changed

9 files changed

+297
-1
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# SQLite database configuration for SolidQueue
2+
# This is a minimal setup for benchmarking purposes
3+
4+
development: &default
5+
adapter: sqlite3
6+
database: db/development.sqlite3
7+
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
8+
timeout: 5000
9+
10+
# SolidQueue needs a separate database connection
11+
# You can use the same database with different connection name
12+
solid_queue:
13+
<<: *default
14+
database: db/solid_queue.sqlite3
15+
16+
test:
17+
<<: *default
18+
database: db/test.sqlite3
19+
20+
production:
21+
<<: *default
22+
database: db/production.sqlite3

examples/benchmark/config/environment.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,13 @@
3636
# Ignore.
3737
end
3838

39+
# SolidQueue support
40+
begin
41+
require "solid_queue"
42+
rescue LoadError
43+
puts "SolidQueue not available - install with: gem install solid_queue"
44+
end
45+
3946
require_relative "../async_job_benchmark_job"
4047
require_relative "../sidekiq_benchmark_job"
48+
require_relative "../solid_queue_benchmark_job"

examples/benchmark/gems.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@
1919
gem "async-job-processor-redis"
2020
gem "async-job-adapter-active_job"
2121
gem "async-service"
22+
23+
# SolidQueue support
24+
gem "solid_queue"
25+
gem "sqlite3"
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
# Released under the MIT License.
5+
# Copyright, 2024-2025, by Samuel Williams.
6+
# Copyright, 2025, by Shopify Inc.
7+
8+
# Setup script for SolidQueue database
9+
10+
require "active_record"
11+
require "logger"
12+
13+
# Connect to SQLite database
14+
ActiveRecord::Base.establish_connection(
15+
adapter: 'sqlite3',
16+
database: 'db/solid_queue.sqlite3'
17+
)
18+
19+
ActiveRecord::Base.logger = Logger.new(STDOUT)
20+
21+
# Create SolidQueue tables
22+
ActiveRecord::Schema.define do
23+
# Core job storage table
24+
create_table :solid_queue_jobs, if_not_exists: true do |t|
25+
t.string :job_class, null: false
26+
t.text :job_args, null: false
27+
t.string :queue_name, null: false
28+
t.integer :priority, default: 0, null: false
29+
t.timestamp :scheduled_at
30+
t.timestamp :finished_at
31+
32+
t.timestamps null: false
33+
34+
t.index [:queue_name, :priority, :scheduled_at], name: "index_solid_queue_jobs_for_polling"
35+
t.index [:scheduled_at], name: "index_solid_queue_jobs_on_scheduled_at"
36+
t.index [:finished_at], name: "index_solid_queue_jobs_on_finished_at"
37+
end
38+
39+
# Process tracking table
40+
create_table :solid_queue_processes, if_not_exists: true do |t|
41+
t.string :name, null: false
42+
t.string :hostname, null: false
43+
t.integer :pid, null: false
44+
t.timestamp :last_heartbeat_at, null: false
45+
46+
t.timestamps null: false
47+
48+
t.index [:name], unique: true
49+
t.index [:last_heartbeat_at]
50+
end
51+
52+
# Failed jobs table
53+
create_table :solid_queue_failed_executions, if_not_exists: true do |t|
54+
t.references :job, null: false, foreign_key: { to_table: :solid_queue_jobs }
55+
t.text :exception_class
56+
t.text :exception_message
57+
t.text :backtrace
58+
59+
t.timestamps null: false
60+
end
61+
end
62+
63+
puts "SolidQueue database tables created successfully!"
64+
puts "Database: db/solid_queue.sqlite3"

examples/benchmark/sidekiq_benchmark_job.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# frozen_string_literal: true
22

33
# Released under the MIT License.
4-
# Copyright, 2025, by Samuel Williams.
4+
# Copyright, 2024-2025, by Samuel Williams.
5+
# Copyright, 2025, by Shopify Inc.
56

67
require "active_job"
78

examples/benchmark/sidekiq_server.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
# Released under the MIT License.
55
# Copyright, 2024-2025, by Samuel Williams.
6+
# Copyright, 2025, by Shopify Inc.
67

78
require_relative "config/environment"
89

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# frozen_string_literal: true
2+
3+
# Released under the MIT License.
4+
# Copyright, 2024-2025, by Samuel Williams.
5+
# Copyright, 2025, by Shopify Inc.
6+
7+
class SolidQueueBenchmarkJob
8+
# Simple plain Ruby job class (no ActiveJob inheritance)
9+
def perform
10+
sleep(0.001)
11+
puts "[solid_queue] Job performed at: #{Process.clock_gettime(Process::CLOCK_MONOTONIC)}"
12+
end
13+
end
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
# Released under the MIT License.
5+
# Copyright, 2024-2025, by Samuel Williams.
6+
# Copyright, 2025, by Shopify Inc.
7+
8+
# Create a minimal Rails-like environment for SolidQueue
9+
ENV['RAILS_ENV'] ||= 'development'
10+
11+
require "bundler/setup"
12+
require "rails"
13+
require "active_job/railtie"
14+
require "active_record/railtie"
15+
16+
# Create a minimal Rails application
17+
class SolidQueueApp < Rails::Application
18+
config.load_defaults 7.1
19+
config.eager_load = false
20+
config.logger = Logger.new(nil) # Quiet for client
21+
config.log_level = :error
22+
23+
# Don't configure ActiveJob for our simple database queue
24+
end
25+
26+
# Initialize the Rails app
27+
Rails.application.initialize!
28+
29+
# Set up database connection - use correct path in benchmark directory
30+
database_path = File.join(__dir__, 'db', 'solid_queue.sqlite3')
31+
puts "Connecting to database: #{database_path}"
32+
33+
ActiveRecord::Base.establish_connection(
34+
adapter: 'sqlite3',
35+
database: database_path
36+
)
37+
38+
# Simple database-backed job model (instead of complex SolidQueue setup)
39+
class SimpleSolidJob < ActiveRecord::Base
40+
self.table_name = 'solid_queue_jobs'
41+
end
42+
43+
# Clear ActiveRecord cache and verify connection
44+
ActiveRecord::Base.clear_cache!
45+
puts "Database connection: #{ActiveRecord::Base.connection.database_version}"
46+
puts "Tables available: #{ActiveRecord::Base.connection.tables.join(', ')}"
47+
48+
# Load our job
49+
require_relative "solid_queue_benchmark_job"
50+
51+
def benchmark_solid_queue(job_count = 10_000)
52+
puts "Benchmarking solid_queue with #{job_count} jobs..."
53+
54+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
55+
56+
job_count.times do
57+
# Insert job directly into database instead of using ActiveJob
58+
SimpleSolidJob.create!(
59+
job_class: 'SolidQueueBenchmarkJob',
60+
job_args: '[]',
61+
queue_name: 'solid_queue_default',
62+
priority: 0,
63+
created_at: Time.current,
64+
updated_at: Time.current
65+
)
66+
end
67+
68+
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
69+
70+
puts "SolidQueue results:"
71+
puts " Started at: #{start_time}"
72+
puts " Ended at: #{end_time}"
73+
puts " Total enqueue time: #{end_time - start_time}s"
74+
puts " Jobs per second: #{(job_count / (end_time - start_time)).round(2)}"
75+
end
76+
77+
# Parse command line argument for job count
78+
job_count = ARGV[0] ? ARGV[0].to_i : 10_000
79+
80+
if job_count <= 0
81+
puts "Error: Job count must be a positive integer"
82+
puts "Usage: #{$0} [job_count]"
83+
puts "Example: #{$0} 5000"
84+
exit 1
85+
end
86+
87+
benchmark_solid_queue(job_count)
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
# Released under the MIT License.
5+
# Copyright, 2024-2025, by Samuel Williams.
6+
# Copyright, 2025, by Shopify Inc.
7+
8+
# Create a minimal Rails-like environment for SolidQueue
9+
ENV['RAILS_ENV'] ||= 'development'
10+
11+
require "bundler/setup"
12+
require "rails"
13+
require "active_job/railtie"
14+
require "active_record/railtie"
15+
16+
# Create a minimal Rails application
17+
class SolidQueueApp < Rails::Application
18+
config.load_defaults 7.1
19+
config.eager_load = false
20+
config.logger = Logger.new(STDOUT)
21+
config.log_level = :info
22+
23+
# Database is configured via establish_connection
24+
25+
# Don't configure ActiveJob for our simple database queue
26+
end
27+
28+
# Initialize the Rails app
29+
Rails.application.initialize!
30+
31+
# Set up database connection - use correct path in benchmark directory
32+
database_path = File.join(__dir__, 'db', 'solid_queue.sqlite3')
33+
puts "Server connecting to database: #{database_path}"
34+
35+
ActiveRecord::Base.establish_connection(
36+
adapter: 'sqlite3',
37+
database: database_path
38+
)
39+
40+
# Simple database-backed job model (instead of complex SolidQueue setup)
41+
class SimpleSolidJob < ActiveRecord::Base
42+
self.table_name = 'solid_queue_jobs'
43+
end
44+
45+
# Load our job
46+
require_relative "solid_queue_benchmark_job"
47+
48+
puts "Starting SolidQueue server..."
49+
puts "Processing queue: solid_queue_default"
50+
puts "Database: #{File.expand_path('../db/solid_queue.sqlite3', __dir__)}"
51+
puts "Press Ctrl+C to stop"
52+
53+
# Simple worker loop using ActiveRecord
54+
def process_jobs
55+
loop do
56+
# Fetch unprocessed jobs
57+
job = SimpleSolidJob.where(queue_name: 'solid_queue_default', finished_at: nil)
58+
.order(:created_at)
59+
.first
60+
61+
if job
62+
begin
63+
puts "Processing job #{job.id}: #{job.job_class}"
64+
65+
# Execute the job
66+
job_class = job.job_class.constantize
67+
job_args = JSON.parse(job.job_args)
68+
job_class.new.perform(*job_args)
69+
70+
# Mark as finished
71+
job.update!(finished_at: Time.current)
72+
puts "Job #{job.id} completed"
73+
74+
rescue => e
75+
puts "Job #{job.id} failed: #{e.message}"
76+
job.update!(finished_at: Time.current) # Mark as finished even if failed
77+
end
78+
else
79+
sleep(0.1) # Poll every 100ms
80+
end
81+
end
82+
end
83+
84+
# Handle graceful shutdown
85+
trap("INT") do
86+
puts "\nShutting down SolidQueue server..."
87+
exit
88+
end
89+
90+
trap("TERM") do
91+
puts "\nShutting down SolidQueue server..."
92+
exit
93+
end
94+
95+
# Start processing
96+
process_jobs

0 commit comments

Comments
 (0)