composite_cache_store 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6b6a9480fa18ff9ed0c17318f3ea093c6f8b586b4595427ff9b1056e7c0e9a5f
4
+ data.tar.gz: 5e9af188c3726dbebec690aabcb838f426e9d26f9c96e48d291b3bb8083a28bc
5
+ SHA512:
6
+ metadata.gz: 636f9164938ce4dc6d5fbfc54d24e0e2e0870b8822c770b13bfdb293f02ac5fe4787bc0d9f883a398563f0e8210dcdfd7e89824298cc2dff20c03d4a9a98efea
7
+ data.tar.gz: c4b79fefc6e70e02243ab8cfdf2c662de139e1e2b3062cd2f171d078cefb2869dae0c8a035d1269be5238015a020d378b77b9288cc42e630858eb91866641706
data/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # CompositeCacheStore
2
+
3
+ ### A composite cache store comprised of 2 ActiveSupport::Cache::Store instances
4
+
5
+ <!-- Tocer[start]: Auto-generated, don't remove. -->
6
+
7
+ ## Table of Contents
8
+
9
+ - [Why a composite cache?](#why-a-composite-cache)
10
+ - [Sponsors](#sponsors)
11
+ - [Dependencies](#dependencies)
12
+ - [Installation](#installation)
13
+ - [Setup](#setup)
14
+ - [Ruby on Rails](#ruby-on-rails)
15
+ - [Usage](#usage)
16
+ - [License](#license)
17
+
18
+ <!-- Tocer[finish]: Auto-generated, don't remove. -->
19
+
20
+ ## Why a composite cache?
21
+
22
+ Most web applications implement some form of caching mechanics to improve performance.
23
+ Sufficiently large applications often employ a persistence service to back the cache.
24
+ _(Redis, Memcache, etc.)_ These services make it possible to use a shared cache between multiple machines/processes.
25
+
26
+ While these services are robust and performant, they can also be a source of latency and are potential bottlenecks.
27
+ __A composite (or layered) cache can mitigate these risks__
28
+ by reducing traffic and backpressure on the persistence service.
29
+
30
+ Consider a composite cache that wraps a remote Redis-backed store with an local in-memory store.
31
+ When both caches are warm, a read hit on the local in-memory store will return instantly, avoiding the overhead
32
+ of inter-process communication (IPC) and/or network traffic _(with its attendant data marshaling and socket/wire noise)._
33
+
34
+ ## Sponsors
35
+
36
+ <p align="center">
37
+ <em>Proudly sponsored by</em>
38
+ </p>
39
+ <p align="center">
40
+ <a href="https://www.clickfunnels.com?utm_source=hopsoft&utm_medium=open-source&utm_campaign=composite_cache_store">
41
+ <img src="https://images.clickfunnel.com/uploads/digital_asset/file/176632/clickfunnels-dark-logo.svg" width="575" />
42
+ </a>
43
+ </p>
44
+
45
+ ## Dependencies
46
+
47
+ - [ActiveSupport `>= 6.0`](https://github.com/rails/rails/tree/main/activesupport)
48
+
49
+ ## Installation
50
+
51
+ ```sh
52
+ bundle add "composite_cache_store"
53
+ ```
54
+
55
+ ## Setup
56
+
57
+ ### Ruby on Rails
58
+
59
+ ```ruby
60
+ # config/environments/production.rb
61
+ module Example
62
+ class Application < Rails::Application
63
+ config.cache_store = :redis_cache_store, { url: "redis://example.com:6379/1" }
64
+ end
65
+ end
66
+ ```
67
+
68
+ ```ruby
69
+ # config/initializers/composite_cache_store.rb
70
+ def Rails.composite_cache
71
+ @store ||= CompositeCacheStore.new(
72
+ inner_cache_store: Rails.cache, # use whatever makes sense for your app as the remote inner-cache
73
+ outer_cache_store: ActiveSupport::Cache::MemoryStore.new( # employs an LRU eviction policy
74
+ expires_in: 15.minutes, # constrain entry lifetime so the local outer-cache doesn't drift out of sync
75
+ size: 32.megabytes # constrain max memory used by the local outer-cache
76
+ )
77
+ )
78
+ end
79
+ ```
80
+
81
+ ## Usage
82
+
83
+ A composite cache is ideal for mitigating hot spot latency in frequently invoked areas of the codebase.
84
+
85
+ ```ruby
86
+ # method that's invoked frequently by multiple processes
87
+ def hotspot
88
+ # NOTE: the expires_in option is only applied to the remote inner-cache
89
+ # the local outer-cache uses its globally configured expiration policy
90
+ Rails.composite_cache.fetch("example/slow/operation", expires_in: 12.hours) do
91
+ # a slow operation
92
+ end
93
+ end
94
+ ```
95
+
96
+ ## License
97
+
98
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ task default: :test
7
+
8
+ Minitest::TestTask.create(:test) do |t|
9
+ t.test_globs = ["test/**/*_test.rb"]
10
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CompositeCacheStore
4
+ VERSION = "0.0.1"
5
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/cache"
4
+ require_relative "composite_cache_store/version"
5
+
6
+ class CompositeCacheStore
7
+ DEFAULT_OUTER_OPTIONS = {
8
+ expires_in: 5.minutes,
9
+ size: 16.megabytes
10
+ }
11
+
12
+ DEFAULT_INNER_OPTIONS = {
13
+ expires_in: 1.day,
14
+ size: 32.megabytes
15
+ }
16
+
17
+ attr_reader :outer_cache_store, :inner_cache_store
18
+
19
+ alias_method :outer, :outer_cache_store
20
+ alias_method :inner, :inner_cache_store
21
+
22
+ # Returns a new CompositeCacheStore instance
23
+ # - inner_cache_store: An ActiveSupport::Cache::Store instance to use for the inner cache store (typically remote)
24
+ # - outer_cache_store: An ActiveSupport::Cache::Store instance to use for the outer cache store (typically local)
25
+ def initialize(options = {})
26
+ options ||= {}
27
+
28
+ @inner_cache_store = options[:inner_cache_store]
29
+ @inner_cache_store = ActiveSupport::Cache::MemoryStore.new(DEFAULT_INNER_OPTIONS) unless inner.is_a?(ActiveSupport::Cache::Store)
30
+
31
+ @outer_cache_store = options[:outer_cache_store]
32
+ @outer_cache_store = ActiveSupport::Cache::MemoryStore.new(DEFAULT_OUTER_OPTIONS) unless outer.is_a?(ActiveSupport::Cache::Store)
33
+ end
34
+
35
+ def cleanup(...)
36
+ outer.cleanup(...)
37
+ inner.cleanup(...)
38
+ end
39
+
40
+ def clear(...)
41
+ outer.clear(...)
42
+ inner.clear(...)
43
+ end
44
+
45
+ def decrement(...)
46
+ outer.decrement(...)
47
+ inner.decrement(...)
48
+ end
49
+
50
+ def delete(...)
51
+ outer.delete(...)
52
+ inner.delete(...)
53
+ end
54
+
55
+ def delete_matched(...)
56
+ outer.delete_matched(...)
57
+ inner.delete_matched(...)
58
+ end
59
+
60
+ def delete_multi(...)
61
+ outer.delete_multi(...)
62
+ inner.delete_multi(...)
63
+ end
64
+
65
+ def exist?(...)
66
+ outer.exist?(...) || inner.exist?(...)
67
+ end
68
+
69
+ def fetch(*args, &block)
70
+ outer.fetch(*args) do
71
+ inner.fetch(*args, &block)
72
+ end
73
+ end
74
+
75
+ def fetch_multi(*args, &block)
76
+ outer.fetch_multi(*args) do
77
+ inner.fetch_multi(*args, &block)
78
+ end
79
+ end
80
+
81
+ # write
82
+ def increment(...)
83
+ outer.increment(...)
84
+ inner.increment(...)
85
+ end
86
+
87
+ def mute
88
+ outer.mute do
89
+ inner.mute do
90
+ yield
91
+ end
92
+ end
93
+ end
94
+
95
+ def read(*args)
96
+ outer.fetch(*args) do
97
+ inner.read(*args)
98
+ end
99
+ end
100
+
101
+ def read_multi(...)
102
+ result = outer.read_multi(...)
103
+ result = inner.read_multi(...) if result.blank?
104
+ result
105
+ end
106
+
107
+ def silence!
108
+ outer.silence!
109
+ inner.silence!
110
+ end
111
+
112
+ def write(name, value, options = nil)
113
+ options ||= {}
114
+ outer.write(name, value, options.except(:expires_in)) # ? accept expires_in if less than outer.config[:expires_in] ?
115
+ inner.write(name, value, options)
116
+ end
117
+
118
+ def write_multi(hash, options = nil)
119
+ options ||= {}
120
+ outer.write_multi(hash, options.except(:expires_in)) # ? accept expires_in if less than outer.config[:expires_in] ?
121
+ inner.write_multi(hash, options)
122
+ end
123
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: composite_cache_store
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Nate Hopkins (hopsoft)
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-03-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '6.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: magic_frozen_string_literal
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest-reporters
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: standardrb
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: |
84
+ Enhanced application performance with faster reads, data redundancy,
85
+ and reduced backpressure on the inner cache store.
86
+ email:
87
+ - natehop@gmail.com
88
+ executables: []
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - README.md
93
+ - Rakefile
94
+ - lib/composite_cache_store.rb
95
+ - lib/composite_cache_store/version.rb
96
+ homepage: https://github.com/hopsoft/composite_cache_store
97
+ licenses:
98
+ - MIT
99
+ metadata:
100
+ homepage_uri: https://github.com/hopsoft/composite_cache_store
101
+ source_code_uri: https://github.com/hopsoft/composite_cache_store
102
+ changelog_uri: https://github.com/hopsoft/composite_cache_store/blob/main/CHANGELOG.md
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: 2.7.5
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubygems_version: 3.4.6
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: A composite cache store comprised of 2 ActiveSupport::Cache::Store instances
122
+ test_files: []