composite_cache_store 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|