diff --git a/README.md b/README.md index f991bf6..04f8bb6 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ CachedResource accepts the following options as a hash: | `:collection_synchronize` | Use collections to generate cache entries for individuals. Update the existing cached principal collection when retrieving subsets of the principal collection or individuals. | `false` | | `:logger` | The logger to which CachedResource messages should be written. | The `Rails.logger` if available, or an `ActiveSupport::Logger` | | `:race_condition_ttl` | The race condition ttl, to prevent [dog pile effect](https://en.wikipedia.org/wiki/Cache_stampede) or [cache stampede](https://en.wikipedia.org/wiki/Cache_stampede). | `86400` | +| `:max_key_length` | Set a max key length. Anything over this key length will be hashed into a 256 hex key | `nil`, meaning it will never be hashed | `:ttl_randomization_scale` | A Range from which a random value will be selected to scale the ttl. | `1..2` | | `:ttl_randomization` | Enable ttl randomization. | `false` | | `:ttl` | The time in seconds until the cache should expire. | `604800` | diff --git a/lib/cached_resource.rb b/lib/cached_resource.rb index b3567a2..1a90a74 100644 --- a/lib/cached_resource.rb +++ b/lib/cached_resource.rb @@ -1,6 +1,7 @@ +require "msgpack" +require "nilio" require "ostruct" -require "nilio" require "active_support/cache" require "active_support/concern" require "active_support/logger" diff --git a/lib/cached_resource/caching.rb b/lib/cached_resource/caching.rb index 0878c12..f129b09 100644 --- a/lib/cached_resource/caching.rb +++ b/lib/cached_resource/caching.rb @@ -104,7 +104,7 @@ def is_any_collection?(*arguments) # Read a entry from the cache for the given key. def cache_read(key) object = cached_resource.cache.read(key).try do |json_cache| - json = ActiveSupport::JSON.decode(json_cache) + json = ActiveSupport::JSON.decode(MessagePack.unpack(json_cache)) unless json.nil? cache = json_to_object(json) @@ -130,7 +130,8 @@ def cache_write(key, object, *arguments) params = options[:params] prefix_options, query_options = split_options(params) - result = cached_resource.cache.write(key, object_to_json(object, prefix_options, query_options), race_condition_ttl: cached_resource.race_condition_ttl, expires_in: cached_resource.generate_ttl) + serialized_json = MessagePack.pack(object_to_json(object, prefix_options, query_options)) + result = cached_resource.cache.write(key, serialized_json, race_condition_ttl: cached_resource.race_condition_ttl, expires_in: cached_resource.generate_ttl) result && cached_resource.logger.info("#{CachedResource::Configuration::LOGGER_PREFIX} WRITE #{key}") result end @@ -158,7 +159,11 @@ def cache_key_delete_pattern # Generate the request cache key. def cache_key(*arguments) - "#{name_key}/#{arguments.join("/")}".downcase.delete(" ") + key = "#{name_key}/#{arguments.join("/")}".downcase.delete(" ") + if cached_resource.max_key_length && key.length > cached_resource.max_key_length + key = Digest::SHA256.hexdigest(key) + end + key end def name_key diff --git a/lib/cached_resource/configuration.rb b/lib/cached_resource/configuration.rb index ae381da..2ef096e 100644 --- a/lib/cached_resource/configuration.rb +++ b/lib/cached_resource/configuration.rb @@ -11,6 +11,9 @@ class Configuration < OpenStruct # prefix for log messages LOGGER_PREFIX = "[cached_resource]" + # Max key length + MAX_KEY_LENGTH = nil + # Initialize a Configuration with the given options, overriding any # defaults. The following options exist for cached resource: # :enabled, default: true @@ -32,10 +35,11 @@ def initialize(options = {}) collection_synchronize: false, enabled: true, logger: defined?(Rails.logger) && Rails.logger || LOGGER, + max_key_length: options.fetch(:max_key_length, MAX_KEY_LENGTH), race_condition_ttl: 86400, - ttl: 604800, + ttl_randomization_scale: 1..2, ttl_randomization: false, - ttl_randomization_scale: 1..2 + ttl: 604800 }.merge(options)) end diff --git a/lib/cached_resource/version.rb b/lib/cached_resource/version.rb index c4d22b3..1687cf3 100644 --- a/lib/cached_resource/version.rb +++ b/lib/cached_resource/version.rb @@ -1,3 +1,3 @@ module CachedResource - VERSION = "9.0.0" + VERSION = "9.1.0" end diff --git a/spec/cached_resource/caching_spec.rb b/spec/cached_resource/caching_spec.rb index c254c9f..46b8fbf 100644 --- a/spec/cached_resource/caching_spec.rb +++ b/spec/cached_resource/caching_spec.rb @@ -42,6 +42,7 @@ def read_from_cache(key, model = Thing) enabled: true, generate_ttl: 604800, logger: double(:thing_logger, info: nil, error: nil), + max_key_length: nil, race_condition_ttl: 86400, ttl_randomization_scale: 1..2, ttl_randomization: false, @@ -58,6 +59,7 @@ def read_from_cache(key, model = Thing) enabled: true, generate_ttl: 604800, logger: double(:not_the_thing_logger, info: nil, error: nil), + max_key_length: nil, race_condition_ttl: 86400, ttl_randomization_scale: 1..2, ttl_randomization: false, @@ -88,6 +90,26 @@ def read_from_cache(key, model = Thing) allow(not_the_thing_cached_resource).to receive(:enabled).and_return(true) end + context "When a `max_key_length` is set" do + before do + allow(thing_cached_resource).to receive(:max_key_length).and_return(10) + end + + context "When key length is greater than `max_key_length`" do + it "caches a response with hashed key" do + result = Thing.find(1, from: "path", params: {foo: "bar"}) + expect(read_from_cache(Digest::SHA256.hexdigest('thing/1/{:from=>"path",:params=>{:foo=>"bar"}}'))).to eq(result) + end + end + + context "When key length is less than `max_key_length`" do + it "caches a response with unhashed key" do + result = Thing.find(1) + expect(read_from_cache("thing/1")).to eq(result) + end + end + end + context "Caching single resource" do it "caches a response" do result = Thing.find(1) diff --git a/spec/cached_resource/configuration_spec.rb b/spec/cached_resource/configuration_spec.rb index a0826be..93c01fa 100644 --- a/spec/cached_resource/configuration_spec.rb +++ b/spec/cached_resource/configuration_spec.rb @@ -56,6 +56,10 @@ class Bar3 < Bar expect(configuration.ttl).to eq(604800) end + it "should have key length of Configuration::MAX_KEY_LENGTH" do + expect(configuration.max_key_length).to eq(CachedResource::Configuration::MAX_KEY_LENGTH) + end + it "should disable collection synchronization" do expect(configuration.collection_synchronize).to eq(false) end