Skip to content
  • Jonathan Hefner's avatar
    a2a63314
    Add ActiveSupport::MessagePack · a2a63314
    Jonathan Hefner authored
    
    
    `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: default avatarJean Boussier <jean.boussier@gmail.com>
    a2a63314
    Add ActiveSupport::MessagePack
    Jonathan Hefner authored
    
    
    `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: default avatarJean Boussier <jean.boussier@gmail.com>
Loading