Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save h-lame/75d409e1c8168f0364a857be696a5833 to your computer and use it in GitHub Desktop.
Save h-lame/75d409e1c8168f0364a857be696a5833 to your computer and use it in GitHub Desktop.
Benchmarking different SEMVER ordering strategies in PostgreSQL
require 'bundler/inline'
##
# Get dependencies
gemfile do
source 'https://rubygems.org'
gem 'pg'
gem 'activerecord', require: 'active_record'
gem 'benchmark-ips'
gem 'pry'
end
##
# Ensure DB exists
ActiveRecord::Base.yield_self do |base|
base.establish_connection(
adapter: 'postgresql',
host: 'localhost',
database: 'postgres'
)
begin
base.connection.create_database('test_stuff')
rescue ActiveRecord::DatabaseAlreadyExists
nil
end
base.establish_connection(
adapter: 'postgresql',
host: 'localhost',
database: 'test_stuff'
)
end
##
# Define the schema
ActiveRecord::Schema.define do
schema_proc = ->(t) {
t.string 'name'
t.string 'version'
t.integer 'version_major'
t.integer 'version_minor'
t.integer 'version_patch'
t.integer 'version_arr', array: true
}
create_table('side_a', id: :bigint, primary_key: :id, force: true, &schema_proc)
create_table('side_b', id: :bigint, primary_key: :id, force: true, &schema_proc)
create_table('side_c', id: :bigint, primary_key: :id, force: true, &schema_proc)
create_table('side_d', id: :bigint, primary_key: :id, force: true, &schema_proc)
create_table('side_e', id: :bigint, primary_key: :id, force: true, &schema_proc)
add_index :side_a, :version
add_index :side_b, [:version_major, :version_minor, :version_patch], name: :version
add_index :side_c, [:version_patch, :version_minor, :version_major], name: :version_rev
add_index :side_e, [:version_arr]
end
class SideA < ActiveRecord::Base
self.table_name = 'side_a'
scope :ordered, -> { order(Arel.sql("string_to_array(version, '.')::int[]")) }
end
class SideB < ActiveRecord::Base
self.table_name = 'side_b'
scope :ordered, -> { order('version_major ASC, version_minor ASC, version_patch ASC') }
def version=(value)
super
self.major_version, self.minor_version, self.patch_version = *value.split('.')
end
end
class SideC < ActiveRecord::Base
self.table_name = 'side_c'
scope :ordered, -> { order('version_major ASC, version_minor ASC, version_patch ASC') }
def version=(value)
super
self.major_version, self.minor_version, self.patch_version = *value.split('.')
end
end
class SideD < ActiveRecord::Base
self.table_name = 'side_d'
scope :ordered, -> { order('version_arr ASC') }
def version=(value)
super
self.version_arr = *value.split('.')
end
end
class SideE < ActiveRecord::Base
self.table_name = 'side_e'
scope :ordered, -> { order('version_arr ASC') }
def version=(value)
super
self.version_arr = *value.split('.')
end
end
##
# Build a bunch of fake data
maj_versions = (0..2).to_a
min_versions = (0..12).to_a
patch_versions = (0..15).to_a
all_version_strings = maj_versions.product(min_versions, patch_versions).map { _1.join('.') }
document_names = 10.times.map.with_index(1) { "Document #{_2}" }
names_and_versions = document_names.product(all_version_strings)
attributes = names_and_versions.map.with_index(1) do |(name, version_string), index|
{
name: name,
version: version_string,
version_major: version_string.split('.').first,
version_minor: version_string.split('.').second,
version_patch: version_string.split('.').third,
version_arr: version_string.split('.'),
id: index
}
end
##
# Populate the tables
SideA.insert_all(attributes, unique_by: :id)
SideB.insert_all(attributes, unique_by: :id)
SideC.insert_all(attributes, unique_by: :id)
SideD.insert_all(attributes, unique_by: :id)
SideE.insert_all(attributes, unique_by: :id)
work = -> (klass) {
klass.ordered.limit(100).load
klass.ordered.count
}
##
# Run the benchmark
Benchmark.ips do |test|
test.report('Indexed String') { work.(SideA) }
test.report('Split index maj,min,patch') { work.(SideB) }
test.report('Split index patch,min,max') { work.(SideC) }
test.report('Integer array') { work.(SideD) }
test.report('Integer array indexed') { work.(SideE) }
end
ActiveRecord::Base.connection.yield_self do |connection|
connection.disconnect!
end
Warming up --------------------------------------
Indexed String 14.000 i/100ms
Split index maj,min,patch
73.000 i/100ms
Split index patch,min,max
39.000 i/100ms
Integer array 28.000 i/100ms
Integer array indexed
56.000 i/100ms
Calculating -------------------------------------
Indexed String 125.508 (±10.4%) i/s - 630.000 in 5.068970s
Split index maj,min,patch
623.507 (±18.8%) i/s - 3.066k in 5.113163s
Split index patch,min,max
406.964 (±14.3%) i/s - 1.989k in 5.012564s
Integer array 395.207 (± 3.3%) i/s - 1.988k in 5.036056s
Integer array indexed
751.770 (± 4.4%) i/s - 3.752k in 5.002257s
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment