bipolar_cache 0.3.0 → 0.4.0
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 +4 -4
- data/.rubocop.yml +10 -0
- data/README.md +117 -8
- data/lib/bipolar_cache/sequel/plugin_alpha.rb +22 -7
- data/lib/bipolar_cache/version.rb +1 -1
- data/lib/bipolar_cache.rb +3 -2
- metadata +7 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 590637758a1d41dcf7614360ca6c2b3497030558a34832091d710e5938079bae
|
|
4
|
+
data.tar.gz: f1fe7e9f5644cab310a0239d521b40ad10286f33b802473f38f25ac09ba9a351
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f2fbc688f2d14f886d03f64b85483ee09c8a23c400aba8c6d397aeb742887237c34b1de2d5b4702034a78e61204a6c76c76045be708c46069defe70375e0bdd6
|
|
7
|
+
data.tar.gz: b2ba003ac34a61e45c2a7b57b3e3a138f867b2751b49fe4dd87998865658d0ee9c0c15672e9c695459e2cfad2fefbfa061e8b84364e5f8beb62904be71eea098
|
data/.rubocop.yml
CHANGED
|
@@ -6,9 +6,19 @@ AllCops:
|
|
|
6
6
|
Metrics/AbcSize:
|
|
7
7
|
Enabled: false
|
|
8
8
|
|
|
9
|
+
Metrics/CyclomaticComplexity:
|
|
10
|
+
Enabled: false
|
|
11
|
+
|
|
12
|
+
Metrics/PerceivedComplexity:
|
|
13
|
+
Enabled: false
|
|
14
|
+
|
|
9
15
|
Metrics/MethodLength:
|
|
10
16
|
Enabled: false
|
|
11
17
|
|
|
18
|
+
Metrics/ClassLength:
|
|
19
|
+
Exclude:
|
|
20
|
+
- "test/**/*"
|
|
21
|
+
|
|
12
22
|
Style/Documentation:
|
|
13
23
|
Enabled: false
|
|
14
24
|
|
data/README.md
CHANGED
|
@@ -1,26 +1,135 @@
|
|
|
1
1
|
# BipolarCache
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Probabilistic caching toolkit for Ruby. Instead of always hitting the database or always returning a cached value, BipolarCache flips a coin — sometimes you get the cache, sometimes you get the real thing, and when the real thing differs, the cache gets updated.
|
|
4
|
+
|
|
5
|
+
Useful for caching database counters and other operations where occasional staleness is acceptable and you want to gradually reduce database load without explicit cache invalidation.
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
BipolarCache.read! → should we cache? (if)
|
|
11
|
+
│
|
|
12
|
+
no ────┘──── yes
|
|
13
|
+
│ │
|
|
14
|
+
return actual read cached value
|
|
15
|
+
│
|
|
16
|
+
chance(cached) > rand?
|
|
17
|
+
│
|
|
18
|
+
yes ──┘── no
|
|
19
|
+
│ │
|
|
20
|
+
return compute actual
|
|
21
|
+
cached │
|
|
22
|
+
cached != actual?
|
|
23
|
+
│
|
|
24
|
+
yes ──┘── no
|
|
25
|
+
│ │
|
|
26
|
+
update return
|
|
27
|
+
cache actual
|
|
28
|
+
return
|
|
29
|
+
actual
|
|
30
|
+
```
|
|
4
31
|
|
|
5
32
|
## Installation
|
|
6
33
|
|
|
7
|
-
|
|
34
|
+
Add to your Gemfile:
|
|
8
35
|
|
|
9
|
-
|
|
36
|
+
```ruby
|
|
37
|
+
gem "bipolar_cache"
|
|
38
|
+
```
|
|
10
39
|
|
|
11
|
-
|
|
40
|
+
Or install directly:
|
|
12
41
|
|
|
13
|
-
|
|
42
|
+
```
|
|
43
|
+
$ gem install bipolar_cache
|
|
44
|
+
```
|
|
14
45
|
|
|
15
46
|
## Usage
|
|
16
47
|
|
|
48
|
+
### Core API
|
|
17
49
|
|
|
50
|
+
`BipolarCache.read!` accepts six keyword arguments, all callables (procs/lambdas):
|
|
18
51
|
|
|
19
|
-
|
|
52
|
+
```ruby
|
|
53
|
+
result = BipolarCache.read!(
|
|
54
|
+
actual: -> { Post.where(user_id: user.id).count }, # the real value (expensive)
|
|
55
|
+
cached: -> { user.posts_count_cache }, # the cached value (cheap)
|
|
56
|
+
chance: ->(cached_value) { cached_value < 10 ? 0.1 : 0.9 }, # probability of cache hit
|
|
57
|
+
if: -> { user.caching_enabled? }, # enable/disable caching
|
|
58
|
+
update: ->(value) { user.update(posts_count_cache: value) }, # write new value to cache
|
|
59
|
+
rescue: ->(error) { Rails.logger.error(error); 0 } # handle errors
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
| Parameter | Required | Description |
|
|
64
|
+
|-----------|----------|-------------|
|
|
65
|
+
| `actual` | yes | Callable that returns the real value (e.g., a database query) |
|
|
66
|
+
| `cached` | yes | Callable that returns the cached value |
|
|
67
|
+
| `chance` | yes | Callable that receives the cached value and returns a float 0.0-1.0. Higher = more likely to use cache. |
|
|
68
|
+
| `if` | yes | Callable that returns true/false. When false, always returns the actual value. |
|
|
69
|
+
| `update` | yes | Callable that receives the actual value and persists it to the cache |
|
|
70
|
+
| `rescue` | no | Callable that receives a `StandardError`. Without it, errors re-raise. |
|
|
71
|
+
|
|
72
|
+
### Sequel Plugin (alpha)
|
|
73
|
+
|
|
74
|
+
For [Sequel](https://github.com/jeremyevans/sequel) models, the plugin generates all the procs automatically. Your model needs a `_count_cache` column (e.g., `comments_count_cache`):
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
require "bipolar_cache/sequel/plugin_alpha"
|
|
78
|
+
|
|
79
|
+
class User < Sequel::Model
|
|
80
|
+
include BipolarCache::Sequel::PluginAlpha
|
|
20
81
|
|
|
21
|
-
|
|
82
|
+
# Assumes:
|
|
83
|
+
# - association: user.comments_dataset
|
|
84
|
+
# - cache column: user.comments_count_cache
|
|
85
|
+
bipolar_count_cache :comments
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
This generates four instance methods:
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
user.comments_count # probabilistic: returns cached or actual count
|
|
93
|
+
user.comments_count_refresh! # force: computes actual and updates cache
|
|
94
|
+
user.comments_count_increment! # bump cache by 1 (or by: n)
|
|
95
|
+
user.comments_count_decrement! # drop cache by 1 (or by: n)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### Options
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
bipolar_count_cache :comments,
|
|
102
|
+
method: "comment_total", # custom method name (default: "{name}_count")
|
|
103
|
+
chance: 75, # fixed probability as percentage (default: adaptive)
|
|
104
|
+
actual: -> { comments_dataset.where(visible: true).count }, # custom actual proc
|
|
105
|
+
cached: :my_cache_column, # custom cache column/method name
|
|
106
|
+
update: ->(v) { set(my_col: v) }, # custom update proc
|
|
107
|
+
rescue: ->(e) { log(e); nil }, # custom error handler
|
|
108
|
+
if: -> { !new? } # disable caching for unsaved records
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### Chance values
|
|
112
|
+
|
|
113
|
+
The `chance` option controls cache-hit probability:
|
|
114
|
+
|
|
115
|
+
| Value | Interpretation | Example |
|
|
116
|
+
|-------|---------------|---------|
|
|
117
|
+
| `0.0` - `1.0` (Float) | Direct probability | `chance: 0.9` = 90% cache hit |
|
|
118
|
+
| `1` (Integer) | Direct probability (1.0 = 100%) | `chance: 1` = always cache |
|
|
119
|
+
| `2` - `100` (Integer) | Percentage, divided by 100 | `chance: 75` = 75% cache hit |
|
|
120
|
+
| `0` | Never use cache | `chance: 0` = always compute actual |
|
|
121
|
+
| Proc | Dynamic, receives cached value | `chance: ->(v) { v > 100 ? 0.95 : 0.5 }` |
|
|
122
|
+
| _(omitted)_ | Adaptive default | < 10: 10% cache hit, >= 10: 90% cache hit |
|
|
123
|
+
|
|
124
|
+
## Development
|
|
22
125
|
|
|
23
|
-
|
|
126
|
+
```
|
|
127
|
+
bin/setup # install dependencies
|
|
128
|
+
rake test # run tests
|
|
129
|
+
rake rubocop # lint
|
|
130
|
+
rake # run both
|
|
131
|
+
bin/console # interactive prompt
|
|
132
|
+
```
|
|
24
133
|
|
|
25
134
|
## Contributing
|
|
26
135
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../../bipolar_cache"
|
|
4
|
+
|
|
3
5
|
module BipolarCache
|
|
4
6
|
module Sequel
|
|
5
7
|
module PluginAlpha
|
|
@@ -21,14 +23,24 @@ module BipolarCache
|
|
|
21
23
|
|
|
22
24
|
define_method "#{method_name}_increment!" do |by: 1|
|
|
23
25
|
procs = fetch_or_build_procs(**opts)
|
|
26
|
+
new_value = by + (procs[:cached].call || 0)
|
|
27
|
+
procs[:update].call(new_value)
|
|
28
|
+
new_value
|
|
29
|
+
rescue StandardError => e
|
|
30
|
+
raise unless procs[:rescue].is_a?(Proc)
|
|
24
31
|
|
|
25
|
-
procs[:
|
|
32
|
+
procs[:rescue].call(e)
|
|
26
33
|
end
|
|
27
34
|
|
|
28
35
|
define_method "#{method_name}_decrement!" do |by: 1|
|
|
29
36
|
procs = fetch_or_build_procs(**opts)
|
|
37
|
+
new_value = (-by) + (procs[:cached].call || 0)
|
|
38
|
+
procs[:update].call(new_value)
|
|
39
|
+
new_value
|
|
40
|
+
rescue StandardError => e
|
|
41
|
+
raise unless procs[:rescue].is_a?(Proc)
|
|
30
42
|
|
|
31
|
-
procs[:
|
|
43
|
+
procs[:rescue].call(e)
|
|
32
44
|
end
|
|
33
45
|
end
|
|
34
46
|
end
|
|
@@ -54,7 +66,7 @@ module BipolarCache
|
|
|
54
66
|
chance: bcsp_proc_chance_from(**opts),
|
|
55
67
|
rescue: bcsp_proc_rescue_from(**opts),
|
|
56
68
|
if: bcsp_proc_if_from(**opts)
|
|
57
|
-
}
|
|
69
|
+
}.freeze
|
|
58
70
|
end
|
|
59
71
|
|
|
60
72
|
# stored procs
|
|
@@ -94,7 +106,7 @@ module BipolarCache
|
|
|
94
106
|
when Proc
|
|
95
107
|
opts[:update]
|
|
96
108
|
when String, Symbol
|
|
97
|
-
-> { send(opts[:update]) }
|
|
109
|
+
->(value) { send(opts[:update], value) }
|
|
98
110
|
else
|
|
99
111
|
->(value) { update({ cache_name => value }) }
|
|
100
112
|
end
|
|
@@ -105,14 +117,17 @@ module BipolarCache
|
|
|
105
117
|
when Proc
|
|
106
118
|
opts[:chance]
|
|
107
119
|
when Integer, Float
|
|
120
|
+
raise ArgumentError, "chance must be >= 0, got #{opts[:chance]}" if opts[:chance].negative?
|
|
121
|
+
raise ArgumentError, "chance must be <= 100, got #{opts[:chance]}" if opts[:chance] > 100
|
|
122
|
+
|
|
108
123
|
normalised_chance = if opts[:chance] > 1
|
|
109
|
-
opts[:chance] / 100
|
|
124
|
+
opts[:chance] / 100.0
|
|
110
125
|
else
|
|
111
126
|
opts[:chance]
|
|
112
127
|
end
|
|
113
128
|
->(_value) { normalised_chance }
|
|
114
129
|
else # default
|
|
115
|
-
->(value) { value < 10 ? 0.1 : 0.9 }
|
|
130
|
+
->(value) { (value || 0) < 10 ? 0.1 : 0.9 }
|
|
116
131
|
end
|
|
117
132
|
end
|
|
118
133
|
|
|
@@ -121,7 +136,7 @@ module BipolarCache
|
|
|
121
136
|
opts[:rescue]
|
|
122
137
|
else # default
|
|
123
138
|
lambda { |e|
|
|
124
|
-
e.
|
|
139
|
+
warn "[BipolarCache] #{e.class}: #{e.message}"
|
|
125
140
|
0
|
|
126
141
|
}
|
|
127
142
|
end
|
data/lib/bipolar_cache.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "bipolar_cache/version"
|
|
4
|
-
require_relative "bipolar_cache/sequel/plugin_alpha"
|
|
5
4
|
|
|
6
5
|
module BipolarCache
|
|
7
6
|
class Error < StandardError; end
|
|
@@ -34,6 +33,8 @@ module BipolarCache
|
|
|
34
33
|
actual_value
|
|
35
34
|
end
|
|
36
35
|
rescue StandardError => e
|
|
37
|
-
|
|
36
|
+
raise unless procs[:rescue].is_a?(Proc)
|
|
37
|
+
|
|
38
|
+
procs[:rescue].call(e)
|
|
38
39
|
end
|
|
39
40
|
end
|
metadata
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bipolar_cache
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andriy Tyurnikov
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies: []
|
|
13
|
-
description:
|
|
12
|
+
description: Probabilistic caching toolkit useful for caching of database counters,
|
|
14
13
|
and other operations.
|
|
15
14
|
email:
|
|
16
15
|
- Andriy.Tyurnikov@gmail.com
|
|
@@ -29,9 +28,8 @@ homepage: https://github.com/rubakas/bipolar_cache
|
|
|
29
28
|
licenses: []
|
|
30
29
|
metadata:
|
|
31
30
|
homepage_uri: https://github.com/rubakas/bipolar_cache
|
|
32
|
-
source_code_uri: https://github.com/rubakas/bipolar_cache
|
|
33
|
-
changelog_uri: https://github.com/rubakas/bipolar_cache
|
|
34
|
-
post_install_message:
|
|
31
|
+
source_code_uri: https://github.com/rubakas/bipolar_cache/tree/main
|
|
32
|
+
changelog_uri: https://github.com/rubakas/bipolar_cache/blob/main/CHANGELOG.md
|
|
35
33
|
rdoc_options: []
|
|
36
34
|
require_paths:
|
|
37
35
|
- lib
|
|
@@ -46,8 +44,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
46
44
|
- !ruby/object:Gem::Version
|
|
47
45
|
version: '0'
|
|
48
46
|
requirements: []
|
|
49
|
-
rubygems_version:
|
|
50
|
-
signing_key:
|
|
47
|
+
rubygems_version: 4.0.8
|
|
51
48
|
specification_version: 4
|
|
52
|
-
summary: BipolarCache.
|
|
49
|
+
summary: BipolarCache. Probabilistic caching toolkit.
|
|
53
50
|
test_files: []
|