Merge pull request #48104 from jonathanhefner/message_pack-cache-serializer

Support `:message_pack` as a cache serializer format
This commit is contained in:
Jonathan Hefner 2023-05-03 15:43:30 -05:00 committed by GitHub
commit 8049d7983f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 378 additions and 345 deletions

@ -1,3 +1,16 @@
* `config.active_support.cache_format_version` now accepts `:message_pack` as
an option. `:message_pack` can reduce cache entry sizes and improve
performance, but requires the [`msgpack` gem](https://rubygems.org/gems/msgpack)
(>= 1.7.0).
Cache entries written using the `6.1` or `7.0` cache formats can be read
when using the `:message_pack` cache format. Additionally, cache entries
written using the `:message_pack` cache format can now be read when using
the `6.1` or `7.0` cache formats. These behaviors makes it easy to migrate
between formats without invalidating the entire cache.
*Jonathan Hefner*
* `Object#deep_dup` no longer duplicate named classes and modules.
Before:

@ -8,6 +8,7 @@
require "active_support/core_ext/object/to_param"
require "active_support/core_ext/object/try"
require "active_support/core_ext/string/inflections"
require_relative "cache/serializer_with_fallback"
module ActiveSupport
# See ActiveSupport::Cache::Store for documentation.
@ -645,7 +646,14 @@ def clear(options = nil)
private
def default_coder
Coders[Cache.format_version]
case Cache.format_version
when 6.1
Cache::SerializerWithFallback[:marshal_6_1]
when 7.0
Cache::SerializerWithFallback[:marshal_7_0]
else
Cache::SerializerWithFallback[Cache.format_version]
end
end
# Adds the namespace defined in the options to a pattern designed to
@ -942,82 +950,6 @@ def load(payload)
end
end
module Coders # :nodoc:
MARK_61 = "\x04\b".b.freeze # The one set by Marshal.
MARK_70_UNCOMPRESSED = "\x00".b.freeze
MARK_70_COMPRESSED = "\x01".b.freeze
class << self
def [](version)
case version
when 6.1
Rails61Coder
when 7.0
Rails70Coder
else
raise ArgumentError, "Unknown ActiveSupport::Cache.format_version: #{Cache.format_version.inspect}"
end
end
end
module Loader
extend self
def load(payload)
if !payload.is_a?(String)
ActiveSupport::Cache::Store.logger&.warn %{Payload wasn't a string, was #{payload.class.name} - couldn't unmarshal, so returning nil."}
return nil
elsif payload.start_with?(MARK_70_UNCOMPRESSED)
members = Marshal.load(payload.byteslice(1..-1))
elsif payload.start_with?(MARK_70_COMPRESSED)
members = Marshal.load(Zlib::Inflate.inflate(payload.byteslice(1..-1)))
elsif payload.start_with?(MARK_61)
return Marshal.load(payload)
else
ActiveSupport::Cache::Store.logger&.warn %{Invalid cache prefix: #{payload.byteslice(0).inspect}, expected "\\x00" or "\\x01"}
return nil
end
Entry.unpack(members)
end
end
module Rails61Coder
include Loader
extend self
def dump(entry)
Marshal.dump(entry)
end
def dump_compressed(entry, threshold)
Marshal.dump(entry.compressed(threshold))
end
end
module Rails70Coder
include Loader
extend self
def dump(entry)
MARK_70_UNCOMPRESSED + Marshal.dump(entry.pack)
end
def dump_compressed(entry, threshold)
payload = Marshal.dump(entry.pack)
if payload.bytesize >= threshold
compressed_payload = Zlib::Deflate.deflate(payload)
if compressed_payload.bytesize < payload.bytesize
return MARK_70_COMPRESSED + compressed_payload
end
end
MARK_70_UNCOMPRESSED + payload
end
end
end
# This class is used to represent cache entries. Cache entries have a value, an optional
# expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option
# on the cache. The version is used to support the :version option on the cache for rejecting

@ -222,52 +222,12 @@ def stats
end
private
module Coders # :nodoc:
class << self
def [](version)
case version
when 6.1
Rails61Coder
when 7.0
Rails70Coder
else
raise ArgumentError, "Unknown ActiveSupport::Cache.format_version #{Cache.format_version.inspect}"
end
end
end
module Loader
def load(payload)
if payload.is_a?(Entry)
payload
else
Cache::Coders::Loader.load(payload)
end
end
end
module Rails61Coder
include Loader
extend self
def dump(entry)
entry
end
def dump_compressed(entry, threshold)
entry.compressed(threshold)
end
end
module Rails70Coder
include Cache::Coders::Rails70Coder
include Loader
extend self
end
end
def default_coder
Coders[Cache.format_version]
if Cache.format_version == 6.1
Cache::SerializerWithFallback[:passthrough]
else
super
end
end
# Read an entry from the cache.

@ -0,0 +1,174 @@
# frozen_string_literal: true
module ActiveSupport
module Cache
module SerializerWithFallback # :nodoc:
def self.[](format)
if format.to_s.include?("message_pack") && !defined?(ActiveSupport::MessagePack)
require "active_support/message_pack"
end
SERIALIZERS.fetch(format)
end
def dump_compressed(entry, threshold)
dumped = dump(entry)
try_compress(dumped, threshold) || dumped
end
def load(dumped)
if dumped.is_a?(String)
dumped = decompress(dumped) if compressed?(dumped)
case
when MessagePackWithFallback.dumped?(dumped)
MessagePackWithFallback._load(dumped)
when Marshal70WithFallback.dumped?(dumped)
Marshal70WithFallback._load(dumped)
when Marshal61WithFallback.dumped?(dumped)
Marshal61WithFallback._load(dumped)
else
Cache::Store.logger&.warn("Unrecognized payload prefix #{dumped.byteslice(0).inspect}; deserializing as nil")
nil
end
elsif PassthroughWithFallback.dumped?(dumped)
PassthroughWithFallback._load(dumped)
else
Cache::Store.logger&.warn("Unrecognized payload class #{dumped.class}; deserializing as nil")
nil
end
end
private
ZLIB_HEADER = "\x78".b.freeze
def compressed?(dumped)
dumped.start_with?(ZLIB_HEADER)
end
def compress(dumped)
Zlib::Deflate.deflate(dumped)
end
def try_compress(dumped, threshold)
if dumped.bytesize >= threshold
compressed = compress(dumped)
compressed unless compressed.bytesize >= dumped.bytesize
end
end
def decompress(compressed)
Zlib::Inflate.inflate(compressed)
end
module PassthroughWithFallback
include SerializerWithFallback
extend self
def dump(entry)
entry
end
def dump_compressed(entry, threshold)
entry.compressed(threshold)
end
def _load(entry)
entry
end
def dumped?(dumped)
dumped.is_a?(Cache::Entry)
end
end
module Marshal61WithFallback
include SerializerWithFallback
extend self
MARSHAL_SIGNATURE = "\x04\x08".b.freeze
def dump(entry)
Marshal.dump(entry)
end
def dump_compressed(entry, threshold)
Marshal.dump(entry.compressed(threshold))
end
def _load(dumped)
Marshal.load(dumped)
end
def dumped?(dumped)
dumped.start_with?(MARSHAL_SIGNATURE)
end
end
module Marshal70WithFallback
include SerializerWithFallback
extend self
MARK_UNCOMPRESSED = "\x00".b.freeze
MARK_COMPRESSED = "\x01".b.freeze
def dump(entry)
MARK_UNCOMPRESSED + Marshal.dump(entry.pack)
end
def dump_compressed(entry, threshold)
dumped = Marshal.dump(entry.pack)
if compressed = try_compress(dumped, threshold)
MARK_COMPRESSED + compressed
else
MARK_UNCOMPRESSED + dumped
end
end
def _load(marked)
dumped = marked.byteslice(1..-1)
dumped = decompress(dumped) if marked.start_with?(MARK_COMPRESSED)
Cache::Entry.unpack(Marshal.load(dumped))
end
def dumped?(dumped)
dumped.start_with?(MARK_UNCOMPRESSED, MARK_COMPRESSED)
end
end
module MessagePackWithFallback
include SerializerWithFallback
extend self
def dump(entry)
ActiveSupport::MessagePack::CacheSerializer.dump(entry.pack)
end
def _load(dumped)
packed = ActiveSupport::MessagePack::CacheSerializer.load(dumped)
Cache::Entry.unpack(packed) if packed
end
def dumped?(dumped)
available? && ActiveSupport::MessagePack.signature?(dumped)
end
private
def available?
return @available if defined?(@available)
require "active_support/message_pack"
@available = true
rescue LoadError
@available = false
end
end
SERIALIZERS = {
passthrough: PassthroughWithFallback,
marshal_6_1: Marshal61WithFallback,
marshal_7_0: Marshal70WithFallback,
message_pack: MessagePackWithFallback,
}
end
end
end

@ -8,34 +8,13 @@ module CacheSerializer
include Serializer
extend self
ZLIB_HEADER = "\x78"
def dump(entry)
super(entry.pack)
end
def dump_compressed(entry, threshold) # :nodoc:
dumped = dump(entry)
if dumped.bytesize >= threshold
compressed = Zlib::Deflate.deflate(dumped)
compressed.bytesize < dumped.bytesize ? compressed : dumped
else
dumped
end
end
def load(dumped)
dumped = Zlib::Inflate.inflate(dumped) if compressed?(dumped)
ActiveSupport::Cache::Entry.unpack(super)
super
rescue ActiveSupport::MessagePack::MissingClassError
# Treat missing class as cache miss => return nil
end
private
def compressed?(dumped)
dumped.start_with?(ZLIB_HEADER)
end
def install_unregistered_type_handler
Extensions.install_unregistered_type_fallback(message_pack_factory)
end

@ -6,6 +6,7 @@
require_relative "behaviors/cache_store_behavior"
require_relative "behaviors/cache_store_version_behavior"
require_relative "behaviors/cache_store_coder_behavior"
require_relative "behaviors/cache_store_format_version_behavior"
require_relative "behaviors/connection_pool_behavior"
require_relative "behaviors/encoded_key_cache_behavior"
require_relative "behaviors/failure_safety_behavior"

@ -0,0 +1,55 @@
# frozen_string_literal: true
require "active_support/core_ext/object/with"
module CacheStoreFormatVersionBehavior
extend ActiveSupport::Concern
FORMAT_VERSIONS = [6.1, 7.0, :message_pack]
included do
test "format version affects default coder" do
coders = FORMAT_VERSIONS.map do |format_version|
ActiveSupport::Cache.with(format_version: format_version) do
lookup_store.instance_variable_get(:@coder)
end
end
assert_equal coders, coders.uniq
end
test "invalid format version raises" do
ActiveSupport::Cache.with(format_version: 0) do
assert_raises do
lookup_store
end
end
end
FORMAT_VERSIONS.product(FORMAT_VERSIONS) do |read_version, write_version|
test "format version #{read_version.inspect} can read #{write_version.inspect} entries" do
key = SecureRandom.uuid
ActiveSupport::Cache.with(format_version: write_version) do
lookup_store.write(key, "value for #{key}")
end
ActiveSupport::Cache.with(format_version: read_version) do
assert_equal "value for #{key}", lookup_store.read(key)
end
end
test "format version #{read_version.inspect} can read #{write_version.inspect} entries with compression" do
key = SecureRandom.uuid
ActiveSupport::Cache.with(format_version: write_version) do
lookup_store(compress_threshold: 1).write(key, key * 10)
end
ActiveSupport::Cache.with(format_version: read_version) do
assert_equal key * 10, lookup_store.read(key)
end
end
end
end
end

@ -1,29 +0,0 @@
# frozen_string_literal: true
require_relative "../abstract_unit"
require "active_support/cache"
require "active_support/core_ext/numeric/time"
class CacheCoderTest < ActiveSupport::TestCase
def test_new_coder_can_read_legacy_payloads
entry = ActiveSupport::Cache::Entry.new("foobar", expires_in: 1.hour, version: "v42")
deserialized_entry = ActiveSupport::Cache::Coders::Rails70Coder.load(
ActiveSupport::Cache::Coders::Rails61Coder.dump(entry),
)
assert_equal entry.value, deserialized_entry.value
assert_equal entry.version, deserialized_entry.version
assert_equal entry.expires_at, deserialized_entry.expires_at
end
def test_legacy_coder_can_read_new_payloads
entry = ActiveSupport::Cache::Entry.new("foobar", expires_in: 1.hour, version: "v42")
deserialized_entry = ActiveSupport::Cache::Coders::Rails61Coder.load(
ActiveSupport::Cache::Coders::Rails70Coder.dump(entry),
)
assert_equal entry.value, deserialized_entry.value
assert_equal entry.version, deserialized_entry.version
assert_equal entry.expires_at, deserialized_entry.expires_at
end
end

@ -0,0 +1,82 @@
# frozen_string_literal: true
require_relative "../abstract_unit"
require "active_support/core_ext/object/with"
class CacheSerializerWithFallbackTest < ActiveSupport::TestCase
FORMATS = ActiveSupport::Cache::SerializerWithFallback::SERIALIZERS.keys
setup do
@entry = ActiveSupport::Cache::Entry.new(
[{ a_boolean: false, a_number: 123, a_string: "x" * 40 }], expires_in: 100, version: "v42"
)
end
FORMATS.product(FORMATS) do |load_format, dump_format|
test "#{load_format.inspect} serializer can load #{dump_format.inspect} dump" do
dumped = serializer(dump_format).dump(@entry)
assert_entry @entry, serializer(load_format).load(dumped)
end
test "#{load_format.inspect} serializer can load #{dump_format.inspect} dump with compression" do
compressed = serializer(dump_format).dump_compressed(@entry, 1)
assert_entry @entry, serializer(load_format).load(compressed)
uncompressed = serializer(dump_format).dump_compressed(@entry, 100_000)
assert_entry @entry, serializer(load_format).load(uncompressed)
end
end
FORMATS.each do |format|
test "#{format.inspect} serializer can compress entries" do
compressed = serializer(format).dump_compressed(@entry, 1)
uncompressed = serializer(format).dump_compressed(@entry, 100_000)
assert_operator compressed.bytesize, :<, uncompressed.bytesize
end
test "#{format.inspect} serializer handles unrecognized payloads gracefully" do
assert_nil serializer(format).load(Object.new)
assert_nil serializer(format).load("")
end
test "#{format.inspect} serializer logs unrecognized payloads" do
assert_logs(/unrecognized/i) { serializer(format).load(Object.new) }
assert_logs(/unrecognized/i) { serializer(format).load("") }
end
end
test ":message_pack serializer handles missing class gracefully" do
klass = Class.new do
def self.name; "DoesNotActuallyExist"; end
def self.from_msgpack_ext(string); self.new; end
def to_msgpack_ext; ""; end
end
dumped = serializer(:message_pack).dump(ActiveSupport::Cache::Entry.new(klass.new))
assert_not_nil dumped
assert_nil serializer(:message_pack).load(dumped)
end
test "raises on invalid format name" do
assert_raises KeyError do
ActiveSupport::Cache::SerializerWithFallback[:invalid_format]
end
end
private
def serializer(format)
ActiveSupport::Cache::SerializerWithFallback[format]
end
def assert_entry(expected, actual)
assert_equal expected.value, actual.value
assert_equal expected.version, actual.version
assert_equal expected.expires_at, actual.expires_at
end
def assert_logs(pattern, &block)
io = StringIO.new
ActiveSupport::Cache::Store.with(logger: Logger.new(io), &block)
assert_match pattern, io.string
end
end

@ -32,6 +32,7 @@ def teardown
include CacheStoreBehavior
include CacheStoreVersionBehavior
include CacheStoreCoderBehavior
include CacheStoreFormatVersionBehavior
include CacheDeleteMatchedBehavior
include CacheIncrementDecrementBehavior
include CacheInstrumentationBehavior
@ -143,36 +144,3 @@ def test_write_with_unless_exist
assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
end
end
class OptimizedFileStoreTest < FileStoreTest
def setup
@previous_format = ActiveSupport::Cache.format_version
ActiveSupport::Cache.format_version = 7.0
super
end
def test_forward_compatibility
previous_format = ActiveSupport::Cache.format_version
ActiveSupport::Cache.format_version = 6.1
@old_store = lookup_store
ActiveSupport::Cache.format_version = previous_format
@old_store.write("foo", "bar")
assert_equal "bar", @cache.read("foo")
end
def test_backward_compatibility
previous_format = ActiveSupport::Cache.format_version
ActiveSupport::Cache.format_version = 6.1
@old_store = lookup_store
ActiveSupport::Cache.format_version = previous_format
@cache.write("foo", "bar")
assert_equal "bar", @old_store.read("foo")
end
def teardown
super
ActiveSupport::Cache.format_version = @previous_format
end
end

@ -77,6 +77,7 @@ def after_teardown
include CacheStoreBehavior
include CacheStoreVersionBehavior
include CacheStoreCoderBehavior
include CacheStoreFormatVersionBehavior
include LocalCacheBehavior
include CacheIncrementDecrementBehavior
include CacheInstrumentationBehavior
@ -431,36 +432,3 @@ def with_memcache_servers_environment_variable(value)
end
end
end
class OptimizedMemCacheStoreTest < MemCacheStoreTest
def setup
@previous_format = ActiveSupport::Cache.format_version
ActiveSupport::Cache.format_version = 7.0
super
end
def teardown
super
ActiveSupport::Cache.format_version = @previous_format
end
def test_forward_compatibility
previous_format = ActiveSupport::Cache.format_version
ActiveSupport::Cache.format_version = 6.1
@old_store = lookup_store
ActiveSupport::Cache.format_version = previous_format
@old_store.write("foo", "bar")
assert_equal "bar", @cache.read("foo")
end
def test_backward_compatibility
previous_format = ActiveSupport::Cache.format_version
ActiveSupport::Cache.format_version = 6.1
@old_store = lookup_store
ActiveSupport::Cache.format_version = previous_format
@cache.write("foo", "bar")
assert_equal "bar", @old_store.read("foo")
end
end

@ -143,6 +143,7 @@ class RedisCacheStoreCommonBehaviorTest < StoreTest
include CacheStoreBehavior
include CacheStoreVersionBehavior
include CacheStoreCoderBehavior
include CacheStoreFormatVersionBehavior
include LocalCacheBehavior
include CacheIncrementDecrementBehavior
include CacheInstrumentationBehavior
@ -225,43 +226,6 @@ def test_large_object_with_default_compression_settings
end
end
class OptimizedRedisCacheStoreCommonBehaviorTest < RedisCacheStoreCommonBehaviorTest
def before_setup
@previous_format = ActiveSupport::Cache.format_version
ActiveSupport::Cache.format_version = 7.0
super
end
def test_forward_compatibility
previous_format = ActiveSupport::Cache.format_version
ActiveSupport::Cache.format_version = 6.1
@old_store = lookup_store
ActiveSupport::Cache.format_version = previous_format
key = SecureRandom.uuid
value = SecureRandom.alphanumeric
@old_store.write(key, value)
assert_equal value, @cache.read(key)
end
def test_backward_compatibility
previous_format = ActiveSupport::Cache.format_version
ActiveSupport::Cache.format_version = 6.1
@old_store = lookup_store
ActiveSupport::Cache.format_version = previous_format
key = SecureRandom.uuid
value = SecureRandom.alphanumeric
@cache.write(key, value)
assert_equal value, @old_store.read(key)
end
def after_teardown
super
ActiveSupport::Cache.format_version = @previous_format
end
end
class ConnectionPoolBehaviorTest < StoreTest
include ConnectionPoolBehavior

@ -27,36 +27,13 @@ class MessagePackCacheSerializerTest < ActiveSupport::TestCase
end
end
test "integrates with ActiveSupport::Cache" do
with_cache do |cache|
value = DefinesFromMsgpackExt.new("foo")
cache.write("key", value)
assert_equal value, cache.read("key")
end
end
test "treats missing class as a cache miss" do
test "handles missing class gracefully" do
klass = Class.new(DefinesFromMsgpackExt)
def klass.name; "DoesNotActuallyExist"; end
with_cache do |cache|
value = klass.new("foo")
cache.write("key", value)
assert_nil cache.read("key")
end
end
test "supports compression" do
entry = ActiveSupport::Cache::Entry.new(["foo"] * 100)
uncompressed = serializer.dump(entry)
compressed = serializer.dump_compressed(entry, 1)
assert_operator compressed.bytesize, :<, uncompressed.bytesize
assert_equal serializer.load(uncompressed).value, serializer.load(compressed).value
with_cache(compress_threshold: 1) do |cache|
assert_equal compressed, cache.send(:serialize_entry, entry)
end
dumped = dump(klass.new("foo"))
assert_not_nil dumped
assert_nil load(dumped)
end
private
@ -64,20 +41,6 @@ def serializer
ActiveSupport::MessagePack::CacheSerializer
end
def dump(object)
super(ActiveSupport::Cache::Entry.new(object))
end
def load(dumped)
super.value
end
def with_cache(**options, &block)
Dir.mktmpdir do |dir|
block.call(ActiveSupport::Cache::FileStore.new(dir, coder: serializer, **options))
end
end
class HasValue
attr_reader :value

@ -2265,7 +2265,19 @@ The default value depends on the `config.load_defaults` target version:
#### `config.active_support.cache_format_version`
Specifies which version of the cache serializer to use. Possible values are `6.1` and `7.0`.
Specifies which serialization format to use for the cache. Possible values are
`6.1`, `7.0`, and `:message_pack`.
The `6.1` and `7.0` formats both use `Marshal`, but the latter uses a more
efficient cache entry representation.
The `:message_pack` format uses `ActiveSupport::MessagePack`, and may further
reduce cache entry sizes and improve performance, but requires the
[`msgpack` gem](https://rubygems.org/gems/msgpack).
All formats are backward and forward compatible, meaning cache entries written
in one format can be read when using another format. This behavior makes it
easy to migrate between formats without invalidating the entire cache.
The default value depends on the `config.load_defaults` target version:

@ -4125,50 +4125,42 @@ def new(app); self; end
assert_equal :fiber, ActiveSupport::IsolatedExecutionState.isolation_level
end
test "cache_format_version in a new app" do
add_to_config <<-RUBY
config.cache_store = :null_store
RUBY
test "ActiveSupport::Cache.format_version is 7.0 by default for new apps" do
app "development"
assert_equal ActiveSupport::Cache::Coders::Rails70Coder, Rails.cache.instance_variable_get(:@coder)
assert_equal 7.0, ActiveSupport::Cache.format_version
end
test "cache_format_version with explicit 7.0 defaults" do
add_to_config <<-RUBY
config.cache_store = :null_store
RUBY
test "ActiveSupport::Cache.format_version is 6.1 by default for upgraded apps" do
remove_from_config '.*config\.load_defaults.*\n'
add_to_config 'config.load_defaults "7.0"'
app "development"
assert_equal ActiveSupport::Cache::Coders::Rails70Coder, Rails.cache.instance_variable_get(:@coder)
assert_equal 6.1, ActiveSupport::Cache.format_version
end
test "cache_format_version with 6.1 defaults" do
add_to_config <<-RUBY
config.cache_store = :null_store
RUBY
test "ActiveSupport::Cache.format_version can be configured via config.active_support.cache_format_version" do
remove_from_config '.*config\.load_defaults.*\n'
add_to_config 'config.load_defaults "6.1"'
add_to_config "config.active_support.cache_format_version = 7.0"
app "development"
assert_equal ActiveSupport::Cache::Coders::Rails61Coder, Rails.cache.instance_variable_get(:@coder)
assert_equal 7.0, ActiveSupport::Cache.format_version
end
test "cache_format_version **cannot** be set via new framework defaults" do
add_to_config <<-RUBY
config.cache_store = :null_store
RUBY
test "config.active_support.cache_format_version affects Rails.cache when set in an environment file (or earlier)" do
remove_from_config '.*config\.load_defaults.*\n'
add_to_config 'config.load_defaults "6.1"'
app_file "config/initializers/new_framework_defaults_7_0.rb", <<-RUBY
app_file "config/environments/development.rb", <<~RUBY
Rails.application.config.active_support.cache_format_version = 7.0
RUBY
app "development"
assert_equal ActiveSupport::Cache::Coders::Rails61Coder, Rails.cache.instance_variable_get(:@coder)
assert_equal \
ActiveSupport::Cache::NullStore.new.instance_variable_get(:@coder),
Rails.cache.instance_variable_get(:@coder)
end
test "raise_on_invalid_cache_expiration_time is false with 7.0 defaults" do

@ -381,9 +381,8 @@ def self.<(_)
rails %w(db:migrate)
add_to_config <<~RUBY
require "active_support/message_pack"
config.cache_store = :file_store, #{app_path("tmp/cache").inspect},
{ coder: ActiveSupport::MessagePack::CacheSerializer }
config.cache_store = :file_store, #{app_path("tmp/cache").inspect}
config.active_support.cache_format_version = :message_pack
RUBY
require "#{app_path}/config/environment"