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 +7 -0
- data/README.md +98 -0
- data/Rakefile +10 -0
- data/lib/composite_cache_store/version.rb +5 -0
- data/lib/composite_cache_store.rb +123 -0
- metadata +122 -0
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,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: []
|