cache-flow 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1faaeb46dbeead28ac84f7239bf0250f59ab9f07
4
- data.tar.gz: 1edf5ec8dc2281d998a178069dbf9627a48ea708
3
+ metadata.gz: 3d7b21c0d817d299288b5ea34aaa1719a5e840bf
4
+ data.tar.gz: e9d850f930165b67041ba30ea54b2b274bfb3256
5
5
  SHA512:
6
- metadata.gz: 43c598901b37528145c21c9ba1ab9a1926139a428ec4c0f8cfb802fd7df3d78d17e22ee9a02997fc94f852b8e53641447cfdff81af273863798c1b9b679bd855
7
- data.tar.gz: ee94fae2c808f3fdfb3a58ec40437bfa691aefc04fa57302754fd54b1a10ed0352e298054089e13981e0d70aafcb5c3c66cec0871de98f02ff4079421d620f30
6
+ metadata.gz: a01f5246b7dd20757943cce80f9ec8934e4b1261801b8cbfede6bc15a17e9f2623bb7a45e85cbdf666bd45745845189bb8697b85591277f9664d7efe3ea0e5b3
7
+ data.tar.gz: 11af009a5d4924d12bb2dedbcc60ceb7b23e14ff64ec2d88fd279f8306e4b5a6604263f089ef18a4ba9cde452c7318c54e839d36d01395028031b44034f8dbfe
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --tty
3
+ --format documentation
4
+ --drb
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
@@ -0,0 +1,43 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ cache-flow (0.0.5)
5
+ activesupport (>= 3.1.0)
6
+ i18n (= 0.7.0)
7
+ tzinfo (= 0.3.38)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activesupport (4.0.3)
13
+ i18n (~> 0.6, >= 0.6.4)
14
+ minitest (~> 4.2)
15
+ multi_json (~> 1.3)
16
+ thread_safe (~> 0.1)
17
+ tzinfo (~> 0.3.37)
18
+ diff-lcs (1.2.5)
19
+ i18n (0.7.0)
20
+ minitest (4.7.5)
21
+ multi_json (1.11.0)
22
+ rspec (3.2.0)
23
+ rspec-core (~> 3.2.0)
24
+ rspec-expectations (~> 3.2.0)
25
+ rspec-mocks (~> 3.2.0)
26
+ rspec-core (3.2.2)
27
+ rspec-support (~> 3.2.0)
28
+ rspec-expectations (3.2.0)
29
+ diff-lcs (>= 1.2.0, < 2.0)
30
+ rspec-support (~> 3.2.0)
31
+ rspec-mocks (3.2.1)
32
+ diff-lcs (>= 1.2.0, < 2.0)
33
+ rspec-support (~> 3.2.0)
34
+ rspec-support (3.2.2)
35
+ thread_safe (0.3.5)
36
+ tzinfo (0.3.38)
37
+
38
+ PLATFORMS
39
+ ruby
40
+
41
+ DEPENDENCIES
42
+ cache-flow!
43
+ rspec (= 3.2.0)
@@ -0,0 +1,83 @@
1
+ # Cache Flow
2
+
3
+ ### What is Cache Flow?
4
+ Cache Flow is a gem that helps you distribute when your cache expires over a defined period of time. The problem this attempts to solve is detailed below, but in essence this gives you the ability to bust your cache randomly so that your cache doesn't bust all at the same time (ex: using the current day as the cache key) resulting in large DB CPU spikes (like in the screenshot below).
5
+
6
+ ### Usage
7
+ * Install the gem
8
+
9
+ ```shell
10
+ gem install cache-flow
11
+ ```
12
+
13
+ * Open up your console
14
+
15
+ ```ruby
16
+ # If in irb, you'll need to do the following
17
+ require 'cache-flow'
18
+
19
+ # Configure CacheFlow in your initializers (config/initializers/cache_flow.rb):
20
+ CacheFlow.configure do |config|
21
+ config.default_options = {
22
+ time_zone: "Eastern Time (US & Canada)",
23
+ hour_range_start: 17,
24
+ hour_range_end: 20
25
+ }
26
+ end
27
+
28
+ # Try the following
29
+ CacheFlow.new.generate_expiry
30
+
31
+ # In action
32
+ Rails.cache("count_of_never_nudes_in_world", expires_in: CacheFlow.new.generate_expiry) do
33
+ "Dozens!"
34
+ end
35
+
36
+ ```
37
+
38
+ So what does Cache Flow do? It generates a random expiration time in between the range of time you configured from now (random time in the future in between your defined range - Time.now.to_i). The Rails cache `expires_in` option accepts a number that is seconds from now that the cache should expire so we chose 1-4am PST for the defined range of when we want our cache to expire since our server's traffic load is light during that window of time.
39
+
40
+ ### Background - Straight Cache Homey
41
+ To understand the problem Cache Flow is solving, it's helpful to under [how caching works in Rails](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html). There are two common cache busting techniques for the Rails cache store. The first is to use a dynamic cache key that busts itself. Example:
42
+ `Rails.cache.fetch("item-path-#{self.id}-#{Date.today.to_s}") { UrlBuilder.new(self).build }`
43
+
44
+ The cache key generated looks like this: `"item-path-959595-2014-06-18"`. In this example, the first time you call `Rails.cache.fetch`, it will store the returned value from `UrlBuilder.new(self).build` in your cache store and it will be accessible using the cache key you provided. For subsequent `Rails.cache.fetch` calls, it will first look in cache (before executing the code in the block) to see if there's a value for the cache key and if so, it will return the value from memcached (or whatever your cache store is) without executing the code within the block you provide to `Rails.cache.fetch`. This is the essence of caching - getting the same value without having to execute the code every time. If the code you're executing is costly (ex: pulling from the database), caching can yield great performance gains.
45
+
46
+ So when the cache key is `"item-path-#{self.id}-#{Date.today.to_s}"`, the cache will expire when the server's clock moves to the next day. Rails will look for a value with the key `item-path-959595-2014-06-19` in your cache store and will find nothing. In this case, the code runs again and the value is stored for another day.
47
+
48
+ What's wrong with this technique? Well, possibly nothing, but if that caching technique is used everywhere, a lot of cache will be busting at the exact moment a new day starts on the server. Since our server runs in UTC, a lot of our cache was busting at 5pm (or 4pm depending on daylight savings time). This meant our servers were working extra hard at that time of the day. Our database server, in particular, saw a massive CPU spike at that time every day.
49
+ ![screenshot 2014-06-17 17 48 43](https://cloud.githubusercontent.com/assets/341055/3309720/f2ee7db2-f6a3-11e3-99db-463cca44d553.png)
50
+
51
+ So...how about that other cache busting technique? It's the `:expires_in` option you can pass into `Rails.cache.fetch`. It works like this:
52
+
53
+ ```ruby
54
+ def buster_bluth
55
+ puts "I'm a monster!"
56
+ "Hey Brother"
57
+ end
58
+
59
+ # 3.minutes = 180
60
+ Rails.cache.fetch("any_unique_key", expires_in: 3.minutes) { buster_bluth }
61
+ # Output after running:
62
+ # I'm a monster!
63
+ # => "Hey Brother"
64
+
65
+ # Wait 10 seconds
66
+ Rails.cache.fetch("any_unique_key", expires_in: 3.minutes) { buster_bluth }
67
+ # Output after running again:
68
+ # => "Hey Brother"
69
+
70
+ # wait 3 minutes
71
+ Rails.cache.fetch("any_unique_key", expires_in: 3.minutes) { buster_bluth }
72
+ # Output after running:
73
+ # I'm a monster!
74
+ # => "Hey Brother"
75
+ ```
76
+ This is why Cache Flow was created. We wanted to utilize `expires_in` functionality to manage when our cache busts in a predictable way. With Cache Flow, you don't have to worry about when your cache store will bust - you just let Cache Flow take care of that. Straight cache, homey.
77
+
78
+ ![http://blackathlete.net/wp-content/uploads/2013/12/cash.gif](http://blackathlete.net/wp-content/uploads/2013/12/cash.gif)
79
+
80
+ ### To Do
81
+ * Allow time period to bridge the night (ex: hour_range_start: 21, hour_range_end: 6)
82
+ * Add thorough tests (use Delorean to create very specific example tests)
83
+ * Add validations
@@ -0,0 +1,16 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'cache-flow'
3
+ s.version = '0.0.5'
4
+ s.date = '2014-06-18'
5
+ s.summary = 'A gem to manage when your cache expires.'
6
+ s.description = 'Define a window of time to have all your cache expire randomly within.'
7
+ s.authors = ['Dan DeMeyere']
8
+ s.email = 'dan.demeyere@gmail.com'
9
+ s.files = `git ls-files`.split("\n")
10
+ s.homepage = 'https://github.com/dandemeyere/cache-flow'
11
+ s.license = 'MIT'
12
+ s.add_dependency('activesupport','>= 3.1.0')
13
+ s.add_dependency('i18n','0.7.0')
14
+ s.add_dependency('tzinfo', '0.3.38')
15
+ s.add_development_dependency('rspec', '3.2.0')
16
+ end
@@ -1,30 +1,4 @@
1
1
  require 'active_support'
2
2
  require 'active_support/time'
3
-
4
- class CacheFlow
5
- # Put all these constants into a configurable setting hash
6
- TIME_ZONE = "Pacific Time (US & Canada)"
7
- # Hour based on 24 hour clock
8
- HOUR_RANGE_START = 1
9
- HOUR_RANGE_END = 4
10
- attr_accessor :frequency
11
-
12
- def initialize(frequency = "daily")
13
- self.frequency = frequency
14
- end
15
-
16
- def generate_expiry
17
- # Rails :expires_in accepts seconds from now to expire the key in
18
- case frequency
19
- when "daily"
20
- random_time_in_range(24.hours.from_now)
21
- end
22
- end
23
-
24
- def random_time_in_range(day_to_bust)
25
- time_to_bust = day_to_bust.in_time_zone(TIME_ZONE).beginning_of_day
26
- range_start = time_to_bust + HOUR_RANGE_START.hours
27
- range_end = time_to_bust + HOUR_RANGE_END.hours
28
- rand(range_start.to_i..range_end.to_i) - Time.now.to_i
29
- end
30
- end
3
+ require 'cache-flow/cache-flow'
4
+ require 'cache-flow/configuration'
@@ -0,0 +1,24 @@
1
+ class CacheFlow
2
+ attr_accessor :frequency
3
+ attr_reader :options
4
+
5
+ def initialize(frequency = "daily", options = {})
6
+ @options = CacheFlow.configuration.default_options.merge(options)
7
+ @frequency = frequency
8
+ end
9
+
10
+ def generate_expiry
11
+ # Rails :expires_in accepts seconds from now to expire the key in
12
+ case frequency
13
+ when "daily"
14
+ random_time_in_range(24.hours.from_now)
15
+ end
16
+ end
17
+
18
+ def random_time_in_range(day_to_bust)
19
+ time_to_bust = day_to_bust.in_time_zone(options[:time_zone]).beginning_of_day
20
+ range_start = time_to_bust + options[:hour_range_start].hours
21
+ range_end = time_to_bust + options[:hour_range_end].hours
22
+ rand(range_start.to_i..range_end.to_i) - Time.now.to_i
23
+ end
24
+ end
@@ -0,0 +1,38 @@
1
+ class CacheFlow
2
+ class Configuration
3
+ attr_accessor :default_options
4
+ # Put all these constants into a configurable setting hash
5
+ # Hour range based on 24 hour clock
6
+
7
+ def initialize
8
+ @default_options = {
9
+ time_zone: "Pacific Time (US & Canada)",
10
+ hour_range_start: 1,
11
+ hour_range_end: 4
12
+ }
13
+ end
14
+ end
15
+
16
+ class << self
17
+ attr_accessor :configuration
18
+ end
19
+
20
+ # Configure CacheFlow in your initializers:
21
+ # example file location: config/initializers/cache_flow.rb
22
+ # example code:
23
+ # CacheFlow.configure do |config|
24
+ # config.default_options = {
25
+ # time_zone: "Eastern Time (US & Canada)",
26
+ # hour_range_start: 17,
27
+ # hour_range_end: 20
28
+ # }
29
+ # end
30
+
31
+ def self.configuration
32
+ @configuration ||= Configuration.new
33
+ end
34
+
35
+ def self.configure
36
+ yield(configuration)
37
+ end
38
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe CacheFlow do
4
+ context '#initialize' do
5
+ it "should initialize with the frequency" do
6
+ expect(subject.frequency).to eql 'daily'
7
+ end
8
+
9
+ it "should have the correct default options set" do
10
+ expect(subject.options).to eql({
11
+ time_zone: "Pacific Time (US & Canada)",
12
+ hour_range_start: 1,
13
+ hour_range_end: 4
14
+ })
15
+ end
16
+ end
17
+
18
+ context " configuration " do
19
+ it "should be configurable" do
20
+ CacheFlow.configure do |config|
21
+ config.default_options = {
22
+ time_zone: "Eastern Time (US & Canada)",
23
+ hour_range_start: 17,
24
+ hour_range_end: 20
25
+ }
26
+ end
27
+
28
+ expect(subject.options).to eql({
29
+ time_zone: "Eastern Time (US & Canada)",
30
+ hour_range_start: 17,
31
+ hour_range_end: 20
32
+ })
33
+ end
34
+ end
35
+
36
+ context '#generate_expiry' do
37
+ it "should return seconds" do
38
+ expect(subject.generate_expiry.class).to eql Fixnum
39
+ end
40
+
41
+ it "should return seconds in the future" do
42
+ expect(subject.generate_expiry).to be > 0
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,5 @@
1
+ require 'bundler/setup'
2
+ Bundler.setup
3
+
4
+ # require 'cache-flow'
5
+ require './lib/cache-flow.rb'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cache-flow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan DeMeyere
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 0.6.9
33
+ version: 0.7.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 0.6.9
40
+ version: 0.7.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: tzinfo
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -58,21 +58,30 @@ dependencies:
58
58
  requirements:
59
59
  - - '='
60
60
  - !ruby/object:Gem::Version
61
- version: 3.0.0
61
+ version: 3.2.0
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - '='
67
67
  - !ruby/object:Gem::Version
68
- version: 3.0.0
68
+ version: 3.2.0
69
69
  description: Define a window of time to have all your cache expire randomly within.
70
70
  email: dan.demeyere@gmail.com
71
71
  executables: []
72
72
  extensions: []
73
73
  extra_rdoc_files: []
74
74
  files:
75
+ - .rspec
76
+ - Gemfile
77
+ - Gemfile.lock
78
+ - Readme.markdown
79
+ - cache-flow.gemspec
75
80
  - lib/cache-flow.rb
81
+ - lib/cache-flow/cache-flow.rb
82
+ - lib/cache-flow/configuration.rb
83
+ - spec/cache_flow_spec.rb
84
+ - spec/spec_helper.rb
76
85
  homepage: https://github.com/dandemeyere/cache-flow
77
86
  licenses:
78
87
  - MIT