rails/Gemfile
Jonathan Hefner a2a6331451 Add ActiveSupport::MessagePack
`ActiveSupport::MessagePack` is a serializer that integrates with the
`msgpack` gem to serialize a variety of Ruby objects.  `AS::MessagePack`
supports several types beyond the base types that `msgpack` supports,
including `Time` and `Range`, as well as Active Support types such as
`AS::TimeWithZone` and `AS::HashWithIndifferentAccess`.

Compared to `JSON` and `Marshal`, `AS::MessagePack` can provide a
performance improvement and message size reduction.  For example, when
used with `MessageVerifier`:

  ```ruby
  # frozen_string_literal: true

  require "benchmark/ips"
  require "active_support/all"
  require "active_support/message_pack"

  marshal_verifier = ActiveSupport::MessageVerifier.new("secret", serializer: Marshal)
  json_verifier = ActiveSupport::MessageVerifier.new("secret", serializer: JSON)
  asjson_verifier = ActiveSupport::MessageVerifier.new("secret", serializer: ActiveSupport::JSON)
  msgpack_verifier = ActiveSupport::MessageVerifier.new("secret", serializer: ActiveSupport::MessagePack)

  ActiveSupport::Messages::Metadata.use_message_serializer_for_metadata = true
  expiry = 1.year.from_now
  data = { bool: true, num: 123456789, string: "x" * 50 }

  Benchmark.ips do |x|
    x.report("Marshal") do
      marshal_verifier.verify(marshal_verifier.generate(data, expires_at: expiry))
    end

    x.report("JSON") do
      json_verifier.verify(json_verifier.generate(data, expires_at: expiry))
    end

    x.report("AS::JSON") do
      asjson_verifier.verify(asjson_verifier.generate(data, expires_at: expiry))
    end

    x.report("MessagePack") do
      msgpack_verifier.verify(msgpack_verifier.generate(data, expires_at: expiry))
    end

    x.compare!
  end

  puts "Marshal size: #{marshal_verifier.generate(data, expires_at: expiry).bytesize}"
  puts "JSON size: #{json_verifier.generate(data, expires_at: expiry).bytesize}"
  puts "MessagePack size: #{msgpack_verifier.generate(data, expires_at: expiry).bytesize}"
  ```

  ```
  Warming up --------------------------------------
               Marshal     1.206k i/100ms
                  JSON     1.165k i/100ms
              AS::JSON   790.000  i/100ms
           MessagePack     1.798k i/100ms
  Calculating -------------------------------------
               Marshal     11.748k (± 1.3%) i/s -     59.094k in   5.031071s
                  JSON     11.498k (± 1.4%) i/s -     58.250k in   5.066957s
              AS::JSON      7.867k (± 2.4%) i/s -     39.500k in   5.024055s
           MessagePack     17.865k (± 0.8%) i/s -     89.900k in   5.032592s

  Comparison:
           MessagePack:    17864.9 i/s
               Marshal:    11747.8 i/s - 1.52x  (± 0.00) slower
                  JSON:    11498.4 i/s - 1.55x  (± 0.00) slower
              AS::JSON:     7866.9 i/s - 2.27x  (± 0.00) slower

  Marshal size: 254
  JSON size: 234
  MessagePack size: 194
  ```

Additionally, `ActiveSupport::MessagePack::CacheSerializer` is a
serializer that is suitable for use as an `ActiveSupport::Cache` coder.
`AS::MessagePack::CacheSerializer` can serialize `ActiveRecord::Base`
instances, including loaded associations.  Like `AS::MessagePack`, it
provides a performance improvement and payload size reduction:

  ```ruby
  # frozen_string_literal: true

  require "benchmark/ips"
  require "active_support/message_pack"

  ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")

  ActiveRecord::Schema.define do
    create_table :posts, force: true do |t|
      t.string :body
      t.timestamps
    end

    create_table :comments, force: true do |t|
      t.integer :post_id
      t.string :body
      t.timestamps
    end
  end

  class Post < ActiveRecord::Base
    has_many :comments
  end

  class Comment < ActiveRecord::Base
    belongs_to :post
  end

  post = Post.create!(body: "x" * 100)
  2.times { post.comments.create!(body: "x" * 100) }
  post.comments.load
  cache_entry = ActiveSupport::Cache::Entry.new(post)

  Rails70Coder = ActiveSupport::Cache::Coders::Rails70Coder
  CacheSerializer = ActiveSupport::MessagePack::CacheSerializer

  Benchmark.ips do |x|
    x.report("Rails70Coder") do
      Rails70Coder.load(Rails70Coder.dump(cache_entry))
    end

    x.report("CacheSerializer") do
      CacheSerializer.load(CacheSerializer.dump(cache_entry))
    end

    x.compare!
  end

  puts "Rails70Coder size: #{Rails70Coder.dump(cache_entry).bytesize}"
  puts "CacheSerializer size: #{CacheSerializer.dump(cache_entry).bytesize}"
  ```

  ```
  Warming up --------------------------------------
          Rails70Coder   329.000  i/100ms
       CacheSerializer   492.000  i/100ms
  Calculating -------------------------------------
          Rails70Coder      3.285k (± 1.7%) i/s -     16.450k in   5.008447s
       CacheSerializer      4.895k (± 2.4%) i/s -     24.600k in   5.028803s

  Comparison:
       CacheSerializer:     4894.7 i/s
          Rails70Coder:     3285.4 i/s - 1.49x  slower

  Rails70Coder size: 808
  CacheSerializer size: 593
  ```

Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
2023-04-17 11:56:06 -05:00

189 lines
4.9 KiB
Ruby

# frozen_string_literal: true
source "https://rubygems.org"
gemspec
if RUBY_VERSION < "3"
gem "minitest", ">= 5.15.0", "< 5.16"
else
gem "minitest", ">= 5.15.0"
end
# We need a newish Rake since Active Job sets its test tasks' descriptions.
gem "rake", ">= 13"
gem "sprockets-rails", ">= 2.0.0"
gem "propshaft", ">= 0.1.7"
gem "capybara", ">= 3.38"
gem "selenium-webdriver", ">= 4.0.0"
gem "rack-cache", "~> 1.2"
gem "stimulus-rails"
gem "turbo-rails"
gem "jsbundling-rails"
gem "cssbundling-rails"
gem "importmap-rails"
gem "tailwindcss-rails"
gem "dartsass-rails"
# require: false so bcrypt is loaded only when has_secure_password is used.
# This is to avoid Active Model (and by extension the entire framework)
# being dependent on a binary library.
gem "bcrypt", "~> 3.1.11", require: false
# This needs to be with require false to avoid it being automatically loaded by
# sprockets.
gem "terser", ">= 1.1.4", require: false
# Explicitly avoid 1.x that doesn't support Ruby 2.4+
gem "json", ">= 2.0.0"
# Workaround until Ruby ships with cgi version 0.3.6 or higher.
gem "cgi", ">= 0.3.6", require: false
group :rubocop do
gem "rubocop", ">= 1.25.1", require: false
gem "rubocop-minitest", require: false
gem "rubocop-packaging", require: false
gem "rubocop-performance", require: false
gem "rubocop-rails", require: false
gem "rubocop-md", require: false
end
group :mdl do
gem "mdl", require: false
end
group :doc do
gem "sdoc", ">= 2.6.0"
gem "rdoc", "~> 6.5"
gem "redcarpet", "~> 3.2.3", platforms: :ruby
gem "w3c_validators", "~> 1.3.6"
gem "rouge"
gem "rubyzip", "~> 2.0"
end
# Active Support
gem "dalli", ">= 3.0.1"
gem "listen", "~> 3.3", require: false
gem "libxml-ruby", platforms: :ruby
gem "connection_pool", require: false
gem "rexml", require: false
gem "msgpack", ">= 1.7.0", require: false
# for railties
gem "bootsnap", ">= 1.4.4", require: false
gem "webrick", require: false
gem "jbuilder", require: false
gem "web-console", require: false
# Action Pack and railties
rack_version = ENV.fetch("RACK", "~> 2.0") # Change to ~> 3 after #46594 is merged.
gem "rack", rack_version
# Active Job
group :job do
gem "resque", require: false
gem "resque-scheduler", require: false
gem "sidekiq", require: false
gem "sucker_punch", require: false
gem "delayed_job", require: false
gem "queue_classic", ">= 4.0.0", require: false, platforms: :ruby
gem "sneakers", require: false
gem "backburner", require: false
gem "delayed_job_active_record", require: false
end
# Action Cable
group :cable do
gem "puma", ">= 5.0.3", require: false
gem "redis", ">= 4.0.1", require: false
gem "redis-namespace"
gem "websocket-client-simple", github: "matthewd/websocket-client-simple", branch: "close-race", require: false
end
# Active Storage
group :storage do
gem "aws-sdk-s3", require: false
gem "google-cloud-storage", "~> 1.11", require: false
gem "azure-storage-blob", "~> 2.0", require: false
gem "image_processing", "~> 1.2"
end
# Action Mailbox
gem "aws-sdk-sns", require: false
gem "webmock"
group :ujs do
gem "webdrivers"
end
# Add your own local bundler stuff.
local_gemfile = File.expand_path(".Gemfile", __dir__)
instance_eval File.read local_gemfile if File.exist? local_gemfile
group :test do
gem "minitest-bisect"
gem "minitest-ci", require: false
gem "minitest-retry"
platforms :mri do
gem "stackprof"
gem "debug", ">= 1.1.0", require: false
end
gem "benchmark-ips"
end
platforms :ruby, :windows do
gem "nokogiri", ">= 1.8.1", "!= 1.11.0"
# Needed for compiling the ActionDispatch::Journey parser.
gem "racc", ">=1.4.6", require: false
# Active Record.
gem "sqlite3", "~> 1.4"
group :db do
gem "pg", "~> 1.3"
gem "mysql2", "~> 0.5"
end
end
platforms :jruby do
if ENV["AR_JDBC"]
gem "activerecord-jdbcsqlite3-adapter", github: "jruby/activerecord-jdbc-adapter", branch: "master"
group :db do
gem "activerecord-jdbcmysql-adapter", github: "jruby/activerecord-jdbc-adapter", branch: "master"
gem "activerecord-jdbcpostgresql-adapter", github: "jruby/activerecord-jdbc-adapter", branch: "master"
end
else
gem "activerecord-jdbcsqlite3-adapter", ">= 1.3.0"
group :db do
gem "activerecord-jdbcmysql-adapter", ">= 1.3.0"
gem "activerecord-jdbcpostgresql-adapter", ">= 1.3.0"
end
end
end
# Gems that are necessary for Active Record tests with Oracle.
if ENV["ORACLE_ENHANCED"]
platforms :ruby do
gem "ruby-oci8", "~> 2.2"
end
gem "activerecord-oracle_enhanced-adapter", github: "rsim/oracle-enhanced", branch: "master"
end
gem "tzinfo-data", platforms: [:windows, :jruby]
gem "wdm", ">= 0.1.0", platforms: [:windows]
# The error_highlight gem only works on CRuby 3.1 or later.
# Also, Rails depends on a new API available since error_highlight 0.4.0.
# (Note that Ruby 3.1 bundles error_highlight 0.3.0.)
if RUBY_VERSION >= "3.1"
gem "error_highlight", ">= 0.4.0", platforms: [:ruby]
end