mudis 0.3.1 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bdaf73fb205686d551ea90a70ffe82e93287c9fcb2e9209e25e626f200c84768
4
- data.tar.gz: 778faad72b00b0f2b477df2ec53e107aba064d2cfe4e0a8b53a8d4623521d1ce
3
+ metadata.gz: 2dc10c0b461aac92e8c559f3e3a97b7654d6c3480c8ba8755fbe10f083945183
4
+ data.tar.gz: 90292495545e270316d273f525d306c77ca1351e47ac99207d2739352b472f6e
5
5
  SHA512:
6
- metadata.gz: e860b5e07f84e20481463eb4bf36984e094b2f95c70d1be4c072318b7e089858b7355ca8e44476495354676ce403b4e4e5cf5e796376c1d155bb47bc60e9ea21
7
- data.tar.gz: 902113917b4b1a376d850a6a9d2d54994bebdf52c4834680524a43d9407f71343899a07c2b206080d9de9e522d3538b3288e74f065152e3458ee4617fc8eb293
6
+ metadata.gz: 72fcae767bb8d79492b66b76854c65bf30b776caa1a4dd7c1342da6972fe68ca65a54ec56c796a799503681576cf40827d8d85916ea674a9d48c25debc314409
7
+ data.tar.gz: 2b2886cbdfebb83d63c5093812d898383d33f6b9a6eea60bf3a119c3b72b345d516d7416281cfd18146a2665e4c151bc301dbc7999eca82aea4e8aea495b0759
data/README.md CHANGED
@@ -46,6 +46,10 @@ There are plenty out there, in various states of maintenance and in many shapes
46
46
 
47
47
  ## Design
48
48
 
49
+ #### Internal Structure and Behaviour
50
+
51
+ ![mudis_flow](design/mudis_obj.png "Mudis Internals")
52
+
49
53
  #### Write - Read - Eviction
50
54
 
51
55
  ![mudis_flow](design/mudis_flow.png "Write - Read - Eviction")
@@ -89,20 +93,34 @@ In your Rails app, create an initializer:
89
93
 
90
94
  ```ruby
91
95
  # config/initializers/mudis.rb
96
+ Mudis.configure do |c|
97
+ c.serializer = JSON # or Marshal | Oj
98
+ c.compress = true # Compress values using Zlib
99
+ c.max_value_bytes = 2_000_000 # Reject values > 2MB
100
+ c.hard_memory_limit = true # enforce hard memory limits
101
+ c.max_bytes = 1_073_741_824 # set maximum cache size
102
+ end
92
103
 
93
- Mudis.serializer = JSON # or Marshal | Oj
94
- Mudis.compress = true # Compress values using Zlib
95
- Mudis.max_value_bytes = 2_000_000 # Reject values > 2MB
96
104
  Mudis.start_expiry_thread(interval: 60) # Cleanup every 60s
97
- Mudis.hard_memory_limit = true # enforce hard memory limits
98
- Mudis.max_bytes = 1_073_741_824 # set maximum cache size
99
105
 
100
106
  at_exit do
101
107
  Mudis.stop_expiry_thread
102
108
  end
103
109
  ```
104
110
 
105
- > If your `lib/` folder isn't eager loaded, explicitly `require 'mudis'` in this file.
111
+ Or with direct setters:
112
+
113
+ ```ruby
114
+ Mudis.serializer = JSON # or Marshal | Oj
115
+ Mudis.compress = true # Compress values using Zlib
116
+ Mudis.max_value_bytes = 2_000_000 # Reject values > 2MB
117
+ Mudis.hard_memory_limit = true # enforce hard memory limits
118
+ Mudis.max_bytes = 1_073_741_824 # set maximum cache size
119
+
120
+ Mudis.start_expiry_thread(interval: 60) # Cleanup every 60s
121
+
122
+ ## set at exit hook
123
+ ```
106
124
 
107
125
  ---
108
126
 
@@ -127,6 +145,37 @@ Mudis.update('user:123') { |data| data.merge(age: 30) }
127
145
  Mudis.delete('user:123')
128
146
  ```
129
147
 
148
+ ### Developer Utilities
149
+
150
+ Mudis provides utility methods to help with test environments, console debugging, and dev tool resets.
151
+
152
+ #### `Mudis.reset!`
153
+ Clears the internal cache state. Including all keys, memory tracking, and metrics. Also stops the expiry thread.
154
+
155
+ ```ruby
156
+ Mudis.write("foo", "bar")
157
+ Mudis.reset!
158
+ Mudis.read("foo") # => nil
159
+ ```
160
+
161
+ - Wipe all buckets (@stores, @lru_nodes, @current_bytes)
162
+ - Reset all metrics (:hits, :misses, :evictions, :rejected)
163
+ - Stop any running background expiry thread
164
+
165
+ #### `Mudis.reset_metrics!`
166
+
167
+ Clears only the metric counters and preserves all cached values.
168
+
169
+ ```ruby
170
+ Mudis.write("key", "value")
171
+ Mudis.read("key") # => "value"
172
+ Mudis.metrics # => { hits: 1, misses: 0, ... }
173
+
174
+ Mudis.reset_metrics!
175
+ Mudis.metrics # => { hits: 0, misses: 0, ... }
176
+ Mudis.read("key") # => "value" (still cached)
177
+ ```
178
+
130
179
  ---
131
180
 
132
181
  ## Rails Service Integration
@@ -222,25 +271,29 @@ Track cache effectiveness and performance:
222
271
 
223
272
  ```ruby
224
273
  Mudis.metrics
225
- # => { hits: 15, misses: 5, evictions: 3, rejections: 0 }
274
+ # => {
275
+ # hits: 15,
276
+ # misses: 5,
277
+ # evictions: 3,
278
+ # rejected: 0,
279
+ # total_memory: 45678,
280
+ # buckets: [
281
+ # { index: 0, keys: 12, memory_bytes: 12345, lru_size: 12 },
282
+ # ...
283
+ # ]
284
+ # }
285
+
226
286
  ```
227
287
 
228
- Optionally, return these metrics from a controller for remote analysis and monitoring if using rails.
288
+ Optionally, return these metrics from a controller for remote analysis and monitoring if using Rails.
229
289
 
230
290
  ```ruby
231
291
  class MudisController < ApplicationController
232
-
233
292
  def metrics
234
- render json: {
235
- mudis_metrics: Mudis.metrics,
236
- memory_used_bytes: Mudis.current_memory_bytes,
237
- memory_max_bytes: Mudis.max_memory_bytes,
238
- keys: Mudis.all_keys.size
239
- }
293
+ render json: { mudis: Mudis.metrics }
240
294
  end
241
295
 
242
296
  end
243
-
244
297
  ```
245
298
 
246
299
  ---
@@ -303,7 +356,18 @@ at_exit { Mudis.stop_expiry_thread }
303
356
 
304
357
  ## Roadmap
305
358
 
306
- - [ ] Stats per bucket
359
+ #### API Enhancements
360
+
361
+ - [ ] bulk_read(keys, namespace:): Batch retrieval of multiple keys with a single method call
362
+
363
+ #### Safety & Policy Controls
364
+
365
+ - [ ] max_ttl: Enforce a global upper bound on expires_in to prevent excessively long-lived keys
366
+ - [ ] default_ttl: Provide a fallback TTL when one is not specified
367
+
368
+ #### Debugging
369
+
370
+ - [ ] clear_namespace(namespace): Remove all keys in a namespace in one call
307
371
 
308
372
  ---
309
373
 
@@ -311,6 +375,8 @@ at_exit { Mudis.stop_expiry_thread }
311
375
 
312
376
  MIT License © kiebor81
313
377
 
378
+ ---
379
+
314
380
  ## Contributing
315
381
 
316
382
  PRs are welcome! To get started:
data/lib/mudis/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- MUDIS_VERSION = "0.3.1"
3
+ MUDIS_VERSION = "0.4.1"
data/lib/mudis.rb CHANGED
@@ -4,6 +4,8 @@ require "json"
4
4
  require "thread" # rubocop:disable Lint/RedundantRequireStatement
5
5
  require "zlib"
6
6
 
7
+ require_relative "mudis_config"
8
+
7
9
  # Mudis is a thread-safe, in-memory, sharded, LRU cache with optional compression and expiry.
8
10
  # It is designed for high concurrency and performance within a Ruby application.
9
11
  class Mudis # rubocop:disable Metrics/ClassLength
@@ -20,11 +22,72 @@ class Mudis # rubocop:disable Metrics/ClassLength
20
22
  attr_accessor :serializer, :compress, :max_value_bytes, :hard_memory_limit
21
23
  attr_reader :max_bytes
22
24
 
25
+ # Configures Mudis with a block, allowing customization of settings
26
+ def configure
27
+ yield(config)
28
+ apply_config!
29
+ end
30
+
31
+ # Returns the current configuration object
32
+ def config
33
+ @config ||= MudisConfig.new
34
+ end
35
+
36
+ # Applies the current configuration to Mudis
37
+ def apply_config!
38
+ self.serializer = config.serializer
39
+ self.compress = config.compress
40
+ self.max_value_bytes = config.max_value_bytes
41
+ self.hard_memory_limit = config.hard_memory_limit
42
+ self.max_bytes = config.max_bytes
43
+ end
44
+
23
45
  # Returns a snapshot of metrics (thread-safe)
24
- def metrics
25
- @metrics_mutex.synchronize { @metrics.dup }
46
+ def metrics # rubocop:disable Metrics/MethodLength
47
+ @metrics_mutex.synchronize do
48
+ {
49
+ hits: @metrics[:hits],
50
+ misses: @metrics[:misses],
51
+ evictions: @metrics[:evictions],
52
+ rejected: @metrics[:rejected],
53
+ total_memory: current_memory_bytes,
54
+ buckets: buckets.times.map do |idx|
55
+ {
56
+ index: idx,
57
+ keys: @stores[idx].size,
58
+ memory_bytes: @current_bytes[idx],
59
+ lru_size: @lru_nodes[idx].size
60
+ }
61
+ end
62
+ }
63
+ end
64
+ end
65
+
66
+ # Resets metric counters (thread-safe)
67
+ def reset_metrics!
68
+ @metrics_mutex.synchronize do
69
+ @metrics = { hits: 0, misses: 0, evictions: 0, rejected: 0 }
70
+ end
71
+ end
72
+
73
+ # Fully resets all internal state (except config)
74
+ def reset!
75
+ stop_expiry_thread
76
+
77
+ @buckets = nil
78
+ b = buckets
79
+
80
+ @stores = Array.new(b) { {} }
81
+ @mutexes = Array.new(b) { Mutex.new }
82
+ @lru_heads = Array.new(b) { nil }
83
+ @lru_tails = Array.new(b) { nil }
84
+ @lru_nodes = Array.new(b) { {} }
85
+ @current_bytes = Array.new(b, 0)
86
+
87
+ reset_metrics!
26
88
  end
27
89
 
90
+ # Sets the maximum size for a single value in bytes
28
91
  def max_bytes=(value)
29
92
  @max_bytes = value
30
93
  @threshold_bytes = (@max_bytes * 0.9).to_i
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # MudisConfig holds all configuration values for Mudis,
4
+ # and provides defaults that can be overridden via Mudis.configure.
5
+ class MudisConfig
6
+ attr_accessor :serializer,
7
+ :compress,
8
+ :max_value_bytes,
9
+ :hard_memory_limit,
10
+ :max_bytes
11
+
12
+ def initialize
13
+ @serializer = JSON # Default serialization strategy
14
+ @compress = false # Whether to compress values with Zlib
15
+ @max_value_bytes = nil # Max size per value (optional)
16
+ @hard_memory_limit = false # Enforce max_bytes as hard cap
17
+ @max_bytes = 1_073_741_824 # 1 GB default max cache size
18
+ end
19
+ end
data/spec/mudis_spec.rb CHANGED
@@ -227,4 +227,71 @@ RSpec.describe Mudis do # rubocop:disable Metrics/BlockLength
227
227
  expect(Mudis.current_memory_bytes).to be > 0
228
228
  end
229
229
  end
230
+
231
+ describe ".metrics" do
232
+ it "includes per-bucket stats" do
233
+ Mudis.write("a", "x" * 50)
234
+ metrics = Mudis.metrics
235
+
236
+ expect(metrics).to include(:buckets)
237
+ expect(metrics[:buckets]).to be_an(Array)
238
+ expect(metrics[:buckets].first).to include(:index, :keys, :memory_bytes, :lru_size)
239
+ end
240
+ end
241
+
242
+ describe ".configure" do
243
+ it "applies configuration settings correctly" do
244
+ Mudis.configure do |c|
245
+ c.serializer = JSON
246
+ c.compress = true
247
+ c.max_value_bytes = 12_345
248
+ c.hard_memory_limit = true
249
+ c.max_bytes = 987_654
250
+ end
251
+
252
+ expect(Mudis.compress).to eq(true)
253
+ expect(Mudis.max_value_bytes).to eq(12_345)
254
+ expect(Mudis.hard_memory_limit).to eq(true)
255
+ expect(Mudis.max_bytes).to eq(987_654)
256
+ end
257
+ end
258
+
259
+ describe ".reset!" do
260
+ it "clears all stores, memory, and metrics" do
261
+ Mudis.write("reset_key", "value")
262
+ expect(Mudis.read("reset_key")).to eq("value")
263
+ expect(Mudis.current_memory_bytes).to be > 0
264
+ expect(Mudis.metrics[:hits]).to be >= 0
265
+
266
+ Mudis.reset!
267
+
268
+ metrics = Mudis.metrics
269
+ expect(metrics[:hits]).to eq(0)
270
+ expect(metrics[:misses]).to eq(0)
271
+ expect(metrics[:evictions]).to eq(0)
272
+ expect(metrics[:rejected]).to eq(0)
273
+ expect(Mudis.current_memory_bytes).to eq(0)
274
+ expect(Mudis.all_keys).to be_empty
275
+
276
+ # Optionally confirm reset_key is now gone
277
+ expect(Mudis.read("reset_key")).to be_nil
278
+ end
279
+ end
280
+
281
+ describe ".reset_metrics!" do
282
+ it "resets only the metrics without clearing cache" do
283
+ Mudis.write("metrics_key", "value")
284
+ Mudis.read("metrics_key") # generates :hits
285
+ Mudis.read("missing_key") # generates :misses
286
+
287
+ expect(Mudis.metrics[:hits]).to eq(1)
288
+ expect(Mudis.metrics[:misses]).to eq(1)
289
+
290
+ Mudis.reset_metrics!
291
+
292
+ expect(Mudis.metrics[:hits]).to eq(0)
293
+ expect(Mudis.metrics[:misses]).to eq(0)
294
+ expect(Mudis.read("metrics_key")).to eq("value") # still exists
295
+ end
296
+ end
230
297
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mudis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - kiebor81
@@ -14,16 +14,16 @@ dependencies:
14
14
  name: rspec
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '3.12'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '3.12'
27
27
  description: Thread-safe, bucketed, in-process cache for Ruby apps. Drop-in replacement
28
28
  for Kredis in some scenarios.
29
29
  email:
@@ -35,6 +35,7 @@ files:
35
35
  - README.md
36
36
  - lib/mudis.rb
37
37
  - lib/mudis/version.rb
38
+ - lib/mudis_config.rb
38
39
  - sig/mudis.rbs
39
40
  - spec/mudis_spec.rb
40
41
  homepage: https://github.com/kiebor81/mudis
@@ -49,7 +50,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
49
50
  requirements:
50
51
  - - ">="
51
52
  - !ruby/object:Gem::Version
52
- version: '0'
53
+ version: '3.0'
53
54
  required_rubygems_version: !ruby/object:Gem::Requirement
54
55
  requirements:
55
56
  - - ">="