Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ISSUE-71] Hash keys and serialize/compress payload responses #72

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` |
Expand Down
4 changes: 2 additions & 2 deletions cached_resource.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ Gem::Specification.new do |s|

s.required_ruby_version = ">= 3.0"

s.add_runtime_dependency "activeresource", ">= 4.0"
s.add_runtime_dependency "activesupport", ">= 4.0"
s.add_runtime_dependency "activeresource", ">= 6.1"
s.add_runtime_dependency "msgpack", "~> 1.7", ">= 1.7.3"
jlurena marked this conversation as resolved.
Show resolved Hide resolved
s.add_runtime_dependency "nilio", ">= 1.0"

s.add_development_dependency "concurrent-ruby"
Expand Down
3 changes: 2 additions & 1 deletion lib/cached_resource.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require "msgpack"
require "nilio"
require "ostruct"

require "nilio"
require "active_support/cache"
require "active_support/concern"
require "active_support/logger"
Expand Down
11 changes: 8 additions & 3 deletions lib/cached_resource/caching.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about making packing/unpacking another configurable option?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah thats what I was asking whether we should maintain it configurable or not. I can't think why anyone would not want this though.

If we make it configurable, we would have to require this dynamically based on the configuration option, which IMO is not an issue - just couldn't decide on whether this should be the norm or not.

I'll let you decide, configurable or norm? @mhgbrown

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go configurable! Thanks for laying it out for me.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @jlurena, anything left to be done here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah the configuration part been a bit busy sorry

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, thanks for the udpate. I see you made another commit. I'll check it out...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep! Did as you asked.

Hey do you have discord or something? I'm hoping you can help me setup a pre version of cached_resource that makes use of this version of ActiveResource:

rails/activeresource#409

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey yeah, my handle is mhgbrown

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @mhgbrown hope you had a festive new year and holidays! I think this PR should be closed, hashing keys may interfere with clearing cache and MessagePack may not be used by everyone

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey! Thank you and same to you. Ok, I'll close it. Thanks again for your work and research.


unless json.nil?
cache = json_to_object(json)
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions lib/cached_resource/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion lib/cached_resource/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module CachedResource
VERSION = "9.0.0"
VERSION = "9.1.0"
end
22 changes: 22 additions & 0 deletions spec/cached_resource/caching_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions spec/cached_resource/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down