atomic_cache 0.4.1.rc1 → 0.5.3.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/docs/USAGE.md +15 -1
- data/lib/atomic_cache/atomic_cache_client.rb +4 -6
- data/lib/atomic_cache/key/last_mod_time_key_manager.rb +8 -2
- data/lib/atomic_cache/storage/shared_memory.rb +15 -0
- data/lib/atomic_cache/version.rb +1 -1
- data/spec/atomic_cache/atomic_cache_client_spec.rb +0 -7
- data/spec/atomic_cache/key/last_mod_time_key_manager_spec.rb +9 -0
- data/spec/atomic_cache/storage/memory_spec.rb +0 -2
- data/spec/atomic_cache/storage/shared_memory_spec.rb +14 -0
- data/spec/spec_helper.rb +4 -0
- metadata +6 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 508ff6dd988d3b864438c2c54d3bca4cba2490a62c2ae97ed2b313522d5e8694
|
4
|
+
data.tar.gz: e0cd9a214c089780758955d2642c406484e0404c669d87faf347de1d8cb3a781
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5e751211a50a029cf4043fa89bf74e50cb200835985c884b5a0faa782b9fc3b8b4c9a0b55be561763363c6625e124c9c11bee75cb15596ddea7b7213bb20bc7
|
7
|
+
data.tar.gz: b3fa0f5bed38fe417103a02409fcb1bf9ccb7611d69887ac6b5cc565e1440a6148ab9f306cb3283fbce2369bf26012f2532a21e889871a47e22d3a517f8b9b84
|
data/docs/USAGE.md
CHANGED
@@ -38,6 +38,12 @@ The ideal `generate_ttl_ms` time is just slightly longer than the average genera
|
|
38
38
|
|
39
39
|
If metrics are enabled, the `<namespace>.generate.run` can be used to determine the min/max/average generate time for a particular cache and the `generate_ttl_ms` tuned using that.
|
40
40
|
|
41
|
+
##### ⚠️ TTL Rounding
|
42
|
+
When using atomic_cache with memcached, be aware that the TTL will be rounded down to the nearest whole seconds. For example, a `generate_ttl_ms` value of 3500 will result in a 3s TTL with memcache.
|
43
|
+
|
44
|
+
##### ⚠️ Max Rate of Change
|
45
|
+
Atomic_cache will *not* remove the lock after the generation process is done. This is both more efficient, and allows the `generate_ttl_ms` to be used to limit the total rate of change. For example, if the typical database query behind the cache takes 2s to run, but `generate_ttl_ms` is set to 10s, then the lock will live on for 8s after the generate process finishes, preventing other processes from querying for a new value. In many ways, `generate_ttl_ms` is the amount of time that the system will be un-allowed to make additional queries.
|
46
|
+
|
41
47
|
#### `max_retries` & `backoff_duration_ms`
|
42
48
|
_`max_retries` defaults to 5._
|
43
49
|
_`backoff_duration_ms` defaults to 50ms._
|
@@ -72,6 +78,13 @@ All incoming keys are normalized to symbols. All values are stored with a `valu
|
|
72
78
|
|
73
79
|
It's likely preferable to use an environments file to configure the `key_storage` and `cache_storage` to always be an in-memory adapter when running in the test environment instead of manually configuring the storage adapter per spec.
|
74
80
|
|
81
|
+
#### TTL in Tests
|
82
|
+
In a test environment, unlike in a production environment, database queries are fast, and time doesn't elapse quite like it does in the real world. As tests get more complex, they perform changes for which they expect the cache to expire. However, because of the synthetic nature of testing, TTLs, particularly those on locks, don't quite work the same either.
|
83
|
+
|
84
|
+
There are a few approaches to address this, for example, using `sleep` to cause real time to pass (not preferable) or wrapping each test in a TimeCop, forcing time to pass (works but quite manual).
|
85
|
+
|
86
|
+
Since this situation is highly likely to arise, `atomic_cache` provides a feature to globally disable enforcing TTL on locks for the `SharedMemory` implementation. Set `enforce_ttl = false` to disable TTL checking on locks within SharedMemory in a test context. This will prevent tests from failing due to unexpired TTLs on locks.
|
87
|
+
|
75
88
|
#### ★ Testing Tip ★
|
76
89
|
If using `SharedMemory` for integration style tests, a global `before(:each)` can be configured in `spec_helper.rb`.
|
77
90
|
|
@@ -79,9 +92,10 @@ If using `SharedMemory` for integration style tests, a global `before(:each)` ca
|
|
79
92
|
# spec/spec_helper.rb
|
80
93
|
RSpec.configure do |config|
|
81
94
|
|
82
|
-
#your other config
|
95
|
+
# your other config
|
83
96
|
|
84
97
|
config.before(:each) do
|
98
|
+
AtomicCache::Storage::SharedMemory.enforce_ttl = false
|
85
99
|
AtomicCache::Storage::SharedMemory.reset
|
86
100
|
end
|
87
101
|
end
|
@@ -93,8 +93,8 @@ module AtomicCache
|
|
93
93
|
end
|
94
94
|
|
95
95
|
new_key = @timestamp_manager.next_key(keyspace, lmt)
|
96
|
-
@timestamp_manager.promote(keyspace, last_known_key: new_key, timestamp: lmt)
|
97
96
|
@storage.set(new_key, new_value, options)
|
97
|
+
@timestamp_manager.promote(keyspace, last_known_key: new_key, timestamp: lmt)
|
98
98
|
|
99
99
|
metrics(:increment, 'generate.current-thread', tags: tags)
|
100
100
|
log(:debug, "Generating new value for `#{new_key}`")
|
@@ -119,13 +119,11 @@ module AtomicCache
|
|
119
119
|
return lkv
|
120
120
|
end
|
121
121
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
@storage.delete(lkk)
|
122
|
+
metrics(:increment, 'last-known-value.nil', tags: tags)
|
123
|
+
else
|
124
|
+
metrics(:increment, 'last-known-value.not-present', tags: tags)
|
126
125
|
end
|
127
126
|
|
128
|
-
metrics(:increment, 'last-known-value.not-present', tags: tags)
|
129
127
|
nil
|
130
128
|
end
|
131
129
|
|
@@ -44,8 +44,7 @@ module AtomicCache
|
|
44
44
|
# @param last_known_key [String] a key with a known value to refer other processes to
|
45
45
|
# @param timestamp [String, Numeric, Time] the timestamp with which the last_known_key was updated at
|
46
46
|
def promote(keyspace, last_known_key:, timestamp:)
|
47
|
-
|
48
|
-
@storage.set(key, last_known_key)
|
47
|
+
@storage.set(keyspace.last_known_key_key, last_known_key)
|
49
48
|
@storage.set(last_modified_time_key, self.format(timestamp))
|
50
49
|
end
|
51
50
|
|
@@ -59,6 +58,13 @@ module AtomicCache
|
|
59
58
|
@storage.add(keyspace.lock_key, LOCK_VALUE, ttl, options)
|
60
59
|
end
|
61
60
|
|
61
|
+
# check if the keyspace is locked
|
62
|
+
#
|
63
|
+
# @param keyspace [AtomicCache::Keyspace] keyspace to lock
|
64
|
+
def lock_present?(keyspace)
|
65
|
+
@storage.read(keyspace.lock_key) == LOCK_VALUE
|
66
|
+
end
|
67
|
+
|
62
68
|
# remove existing lock to allow other processes to update keyspace
|
63
69
|
#
|
64
70
|
# @param keyspace [AtomicCache::Keyspace] keyspace to lock
|
@@ -10,6 +10,21 @@ module AtomicCache
|
|
10
10
|
STORE = {}
|
11
11
|
SEMAPHORE = Mutex.new
|
12
12
|
|
13
|
+
@enforce_ttl = true
|
14
|
+
class << self
|
15
|
+
attr_accessor :enforce_ttl
|
16
|
+
end
|
17
|
+
|
18
|
+
def add(raw_key, new_value, ttl, user_options={})
|
19
|
+
if self.class.enforce_ttl
|
20
|
+
super(raw_key, new_value, ttl, user_options)
|
21
|
+
else
|
22
|
+
store_op(raw_key, user_options) do |key, options|
|
23
|
+
write(key, new_value, ttl, user_options)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
13
28
|
def self.reset
|
14
29
|
STORE.clear
|
15
30
|
end
|
data/lib/atomic_cache/version.rb
CHANGED
@@ -168,13 +168,6 @@ describe 'AtomicCacheClient' do
|
|
168
168
|
result = subject.fetch(keyspace, backoff_duration_ms: 5) { 'value from generate' }
|
169
169
|
expect(result).to eq(nil)
|
170
170
|
end
|
171
|
-
|
172
|
-
it 'deletes the last known key' do
|
173
|
-
key_storage.set(keyspace.last_known_key_key, :oldkey)
|
174
|
-
cache_storage.set(:oldkey, nil)
|
175
|
-
subject.fetch(keyspace, backoff_duration_ms: 5) { 'value from generate' }
|
176
|
-
expect(cache_storage.store).to_not have_key(:oldkey)
|
177
|
-
end
|
178
171
|
end
|
179
172
|
end
|
180
173
|
end
|
@@ -40,6 +40,15 @@ describe 'LastModTimeKeyManager' do
|
|
40
40
|
expect(storage.store).to_not have_key(:'ns:lock')
|
41
41
|
end
|
42
42
|
|
43
|
+
it 'checks if the lock is present' do
|
44
|
+
subject.lock(req_keyspace, 100)
|
45
|
+
expect(subject.lock_present?(req_keyspace)).to eq(true)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'checks if the lock is not present' do
|
49
|
+
expect(subject.lock_present?(req_keyspace)).to eq(false)
|
50
|
+
end
|
51
|
+
|
43
52
|
it 'promotes a timestamp and last known key' do
|
44
53
|
subject.promote(req_keyspace, last_known_key: 'asdf', timestamp: timestamp)
|
45
54
|
expect(storage.read(:'ns:lkk')).to eq('asdf')
|
@@ -17,8 +17,6 @@ shared_examples 'memory storage' do
|
|
17
17
|
expect(result).to eq(true)
|
18
18
|
end
|
19
19
|
|
20
|
-
# SharedMemory.new.add("foo", ttl: 100)
|
21
|
-
|
22
20
|
it 'does not write the key if it exists but expiration time is NOT up' do
|
23
21
|
entry = { value: Marshal.dump('foo'), ttl: 5000, written_at: Time.local(2021, 1, 1, 12, 0, 0) }
|
24
22
|
subject.store[:key] = entry
|
@@ -6,4 +6,18 @@ require_relative 'memory_spec'
|
|
6
6
|
describe 'SharedMemory' do
|
7
7
|
subject { AtomicCache::Storage::SharedMemory.new }
|
8
8
|
it_behaves_like 'memory storage'
|
9
|
+
|
10
|
+
context 'enforce_ttl disabled' do
|
11
|
+
before(:each) do
|
12
|
+
AtomicCache::Storage::SharedMemory.enforce_ttl = false
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'allows instantly `add`ing keys' do
|
16
|
+
subject.add("foo", 1, ttl: 100000)
|
17
|
+
subject.add("foo", 2, ttl: 1)
|
18
|
+
|
19
|
+
expect(subject.store).to have_key(:foo)
|
20
|
+
expect(Marshal.load(subject.store[:foo][:value])).to eq(2)
|
21
|
+
end
|
22
|
+
end
|
9
23
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: atomic_cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.3.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ibotta Developers
|
@@ -9,22 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2021-
|
12
|
+
date: 2021-08-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
|
-
- - "
|
18
|
+
- - ">="
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '
|
20
|
+
version: '0'
|
21
21
|
type: :development
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
|
-
- - "
|
25
|
+
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: '
|
27
|
+
version: '0'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: gems
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -144,9 +144,6 @@ dependencies:
|
|
144
144
|
- - ">="
|
145
145
|
- !ruby/object:Gem::Version
|
146
146
|
version: '4.2'
|
147
|
-
- - "<"
|
148
|
-
- !ruby/object:Gem::Version
|
149
|
-
version: '6'
|
150
147
|
type: :runtime
|
151
148
|
prerelease: false
|
152
149
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -154,9 +151,6 @@ dependencies:
|
|
154
151
|
- - ">="
|
155
152
|
- !ruby/object:Gem::Version
|
156
153
|
version: '4.2'
|
157
|
-
- - "<"
|
158
|
-
- !ruby/object:Gem::Version
|
159
|
-
version: '6'
|
160
154
|
- !ruby/object:Gem::Dependency
|
161
155
|
name: murmurhash3
|
162
156
|
requirement: !ruby/object:Gem::Requirement
|