mudis 0.4.1 → 0.4.3
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/README.md +3 -2
- data/lib/mudis/version.rb +1 -1
- data/lib/mudis.rb +32 -4
- data/lib/mudis_config.rb +3 -1
- data/sig/mudis.rbs +24 -13
- data/sig/mudis_config.rbs +8 -0
- data/spec/guardrails_spec.rb +58 -0
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a4a27b63d070e4a8e356d6ffed09eda290c7f633ffb3bf7b9008499170b4a55
|
4
|
+
data.tar.gz: 1ccaf9415f6e595272884c926615861975f376d5a52c7109a845dcb4b4480e8d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2fbcea123de9db1aefcb31e8aea21069bbbdae11300b3a6dd2b73b65c801c6b9dbdc5f60d5d84e001e26059a2e6a2e1e1ef01b61e23261f143cbf0ae3c129e40
|
7
|
+
data.tar.gz: 3344818c66b36d4fbb9c5e5e287a3067ee7a8c6779b2803084b8b7d4001337e073ccd45406665f2e50bc27ef58ac0e4bc9a17168f85fb302e2b0331cbc11ba2c
|
data/README.md
CHANGED
@@ -21,7 +21,7 @@ There are plenty out there, in various states of maintenance and in many shapes
|
|
21
21
|
|
22
22
|
#### Feature / Function Comparison
|
23
23
|
|
24
|
-
| **Feature** | **Mudis
|
24
|
+
| **Feature** | **Mudis** | **MemoryStore** (`Rails.cache`) | **FastCache** | **Zache** | **EasyCache** | **MiniCache** |
|
25
25
|
| -------------------------------------- | ---------------- | ------------------------------- | -------------- | ------------- | ------------- | -------------- |
|
26
26
|
| **LRU eviction strategy** | ✅ Per-bucket | ✅ Global | ✅ Global | ❌ | ❌ | ✅ Simplistic |
|
27
27
|
| **TTL expiry support** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
@@ -309,8 +309,9 @@ end
|
|
309
309
|
| `start_expiry_thread` | Background TTL cleanup loop (every N sec) | Disabled by default|
|
310
310
|
| `hard_memory_limit` | Enforce hard memory limits on key size and reject if exceeded | `false`|
|
311
311
|
| `max_bytes` | Maximum allowed cache size | `1GB`|
|
312
|
+
| `buckets` | Number of memory buckets (shards) | `32` |
|
312
313
|
|
313
|
-
|
314
|
+
Buckets can also be set using a `MUDIS_BUCKETS` environment variable.
|
314
315
|
|
315
316
|
When setting `serializer`, be mindful of the below
|
316
317
|
|
data/lib/mudis/version.rb
CHANGED
data/lib/mudis.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "json"
|
4
4
|
require "thread" # rubocop:disable Lint/RedundantRequireStatement
|
@@ -19,8 +19,8 @@ class Mudis # rubocop:disable Metrics/ClassLength
|
|
19
19
|
@stop_expiry = false # Signal for stopping expiry thread
|
20
20
|
|
21
21
|
class << self
|
22
|
-
attr_accessor :serializer, :compress, :
|
23
|
-
attr_reader :max_bytes
|
22
|
+
attr_accessor :serializer, :compress, :hard_memory_limit
|
23
|
+
attr_reader :max_bytes, :max_value_bytes
|
24
24
|
|
25
25
|
# Configures Mudis with a block, allowing customization of settings
|
26
26
|
def configure
|
@@ -35,6 +35,8 @@ class Mudis # rubocop:disable Metrics/ClassLength
|
|
35
35
|
|
36
36
|
# Applies the current configuration to Mudis
|
37
37
|
def apply_config!
|
38
|
+
validate_config!
|
39
|
+
|
38
40
|
self.serializer = config.serializer
|
39
41
|
self.compress = config.compress
|
40
42
|
self.max_value_bytes = config.max_value_bytes
|
@@ -42,6 +44,18 @@ class Mudis # rubocop:disable Metrics/ClassLength
|
|
42
44
|
self.max_bytes = config.max_bytes
|
43
45
|
end
|
44
46
|
|
47
|
+
# Validates the current configuration, raising errors for invalid settings
|
48
|
+
def validate_config! # rubocop:disable Metrics/AbcSize
|
49
|
+
if config.max_value_bytes && config.max_value_bytes > config.max_bytes
|
50
|
+
raise ArgumentError,
|
51
|
+
"max_value_bytes cannot exceed max_bytes"
|
52
|
+
end
|
53
|
+
|
54
|
+
raise ArgumentError, "max_value_bytes must be > 0" if config.max_value_bytes && config.max_value_bytes <= 0
|
55
|
+
|
56
|
+
raise ArgumentError, "buckets must be > 0" if config.buckets && config.buckets <= 0
|
57
|
+
end
|
58
|
+
|
45
59
|
# Returns a snapshot of metrics (thread-safe)
|
46
60
|
def metrics # rubocop:disable Metrics/MethodLength
|
47
61
|
@metrics_mutex.synchronize do
|
@@ -89,9 +103,18 @@ class Mudis # rubocop:disable Metrics/ClassLength
|
|
89
103
|
|
90
104
|
# Sets the maximum size for a single value in bytes
|
91
105
|
def max_bytes=(value)
|
106
|
+
raise ArgumentError, "max_bytes must be > 0" if value.to_i <= 0
|
107
|
+
|
92
108
|
@max_bytes = value
|
93
109
|
@threshold_bytes = (@max_bytes * 0.9).to_i
|
94
110
|
end
|
111
|
+
|
112
|
+
# Sets the maximum size for a single value in bytes, raising an error if invalid
|
113
|
+
def max_value_bytes=(value)
|
114
|
+
raise ArgumentError, "max_value_bytes must be > 0" if value && value.to_i <= 0
|
115
|
+
|
116
|
+
@max_value_bytes = value
|
117
|
+
end
|
95
118
|
end
|
96
119
|
|
97
120
|
# Node structure for the LRU doubly-linked list
|
@@ -107,7 +130,12 @@ class Mudis # rubocop:disable Metrics/ClassLength
|
|
107
130
|
|
108
131
|
# Number of cache buckets (shards). Default: 32
|
109
132
|
def self.buckets
|
110
|
-
@buckets
|
133
|
+
return @buckets if @buckets
|
134
|
+
|
135
|
+
val = config.buckets || ENV["MUDIS_BUCKETS"]&.to_i || 32
|
136
|
+
raise ArgumentError, "bucket count must be > 0" if val <= 0
|
137
|
+
|
138
|
+
@buckets = val
|
111
139
|
end
|
112
140
|
|
113
141
|
# --- Internal Structures ---
|
data/lib/mudis_config.rb
CHANGED
@@ -7,7 +7,8 @@ class MudisConfig
|
|
7
7
|
:compress,
|
8
8
|
:max_value_bytes,
|
9
9
|
:hard_memory_limit,
|
10
|
-
:max_bytes
|
10
|
+
:max_bytes,
|
11
|
+
:buckets
|
11
12
|
|
12
13
|
def initialize
|
13
14
|
@serializer = JSON # Default serialization strategy
|
@@ -15,5 +16,6 @@ class MudisConfig
|
|
15
16
|
@max_value_bytes = nil # Max size per value (optional)
|
16
17
|
@hard_memory_limit = false # Enforce max_bytes as hard cap
|
17
18
|
@max_bytes = 1_073_741_824 # 1 GB default max cache size
|
19
|
+
@buckets = nil # use nil to signal fallback to ENV or default
|
18
20
|
end
|
19
21
|
end
|
data/sig/mudis.rbs
CHANGED
@@ -1,11 +1,17 @@
|
|
1
|
-
# sig/mudis.rbs
|
2
|
-
|
3
1
|
class Mudis
|
4
2
|
# Configuration
|
5
3
|
class << self
|
6
4
|
attr_accessor serializer : Object
|
7
5
|
attr_accessor compress : bool
|
8
|
-
attr_accessor
|
6
|
+
attr_accessor hard_memory_limit : bool
|
7
|
+
attr_reader max_bytes : Integer
|
8
|
+
attr_reader max_value_bytes : Integer?
|
9
|
+
|
10
|
+
def configure: () { (config: MudisConfig) -> void } -> void
|
11
|
+
def config: () -> MudisConfig
|
12
|
+
def apply_config!: () -> void
|
13
|
+
|
14
|
+
def buckets: () -> Integer
|
9
15
|
end
|
10
16
|
|
11
17
|
# Lifecycle
|
@@ -13,27 +19,32 @@ class Mudis
|
|
13
19
|
def self.stop_expiry_thread: () -> void
|
14
20
|
|
15
21
|
# Core operations
|
16
|
-
def self.write: (String, untyped, ?expires_in: Integer) -> void
|
17
|
-
def self.read: (String) -> untyped?
|
18
|
-
def self.update: (String) { (untyped) -> untyped } -> void
|
19
|
-
def self.delete: (String) -> void
|
20
|
-
def self.exists?: (String) -> bool
|
22
|
+
def self.write: (String, untyped, ?expires_in: Integer, ?namespace: String) -> void
|
23
|
+
def self.read: (String, ?namespace: String) -> untyped?
|
24
|
+
def self.update: (String, ?namespace: String) { (untyped) -> untyped } -> void
|
25
|
+
def self.delete: (String, ?namespace: String) -> void
|
26
|
+
def self.exists?: (String, ?namespace: String) -> bool
|
21
27
|
|
22
28
|
# DSL & Helpers
|
23
29
|
def self.fetch: (
|
24
30
|
String,
|
25
31
|
?expires_in: Integer,
|
26
|
-
?force: bool
|
32
|
+
?force: bool,
|
33
|
+
?namespace: String
|
27
34
|
) { () -> untyped } -> untyped
|
28
35
|
|
29
|
-
def self.clear: (String) -> void
|
30
|
-
def self.replace: (String, untyped, ?expires_in: Integer) -> void
|
31
|
-
def self.inspect: (String) -> Hash[Symbol, untyped]?
|
36
|
+
def self.clear: (String, ?namespace: String) -> void
|
37
|
+
def self.replace: (String, untyped, ?expires_in: Integer, ?namespace: String) -> void
|
38
|
+
def self.inspect: (String, ?namespace: String) -> Hash[Symbol, untyped]?
|
32
39
|
|
33
40
|
# Introspection & management
|
34
|
-
def self.metrics: () -> Hash[Symbol,
|
41
|
+
def self.metrics: () -> Hash[Symbol, untyped]
|
35
42
|
def self.cleanup_expired!: () -> void
|
36
43
|
def self.all_keys: () -> Array[String]
|
37
44
|
def self.current_memory_bytes: () -> Integer
|
38
45
|
def self.max_memory_bytes: () -> Integer
|
46
|
+
|
47
|
+
# State reset
|
48
|
+
def self.reset!: () -> void
|
49
|
+
def self.reset_metrics!: () -> void
|
39
50
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
require "climate_control"
|
5
|
+
|
6
|
+
RSpec.describe "Mudis Configuration Guardrails" do # rubocop:disable Metrics/BlockLength
|
7
|
+
after { Mudis.reset! }
|
8
|
+
|
9
|
+
describe "bucket configuration" do
|
10
|
+
it "defaults to 32 buckets if ENV is nil" do
|
11
|
+
Mudis.instance_variable_set(:@buckets, nil) # force recomputation
|
12
|
+
ClimateControl.modify(MUDIS_BUCKETS: nil) do
|
13
|
+
expect(Mudis.send(:buckets)).to eq(32)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it "raises if MUDIS_BUCKETS is 0 or less" do
|
18
|
+
expect do
|
19
|
+
Mudis.instance_variable_set(:@buckets, nil) # force recomputation
|
20
|
+
ClimateControl.modify(MUDIS_BUCKETS: "0") { Mudis.send(:buckets) }
|
21
|
+
end.to raise_error(ArgumentError, /bucket count must be > 0/)
|
22
|
+
|
23
|
+
expect do
|
24
|
+
Mudis.instance_variable_set(:@buckets, nil) # force recomputation
|
25
|
+
ClimateControl.modify(MUDIS_BUCKETS: "-5") { Mudis.send(:buckets) }
|
26
|
+
end.to raise_error(ArgumentError, /bucket count must be > 0/)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "memory configuration" do
|
31
|
+
it "raises if max_bytes is set to 0 or less" do
|
32
|
+
expect do
|
33
|
+
Mudis.max_bytes = 0
|
34
|
+
end.to raise_error(ArgumentError, /max_bytes must be > 0/)
|
35
|
+
|
36
|
+
expect do
|
37
|
+
Mudis.max_bytes = -1
|
38
|
+
end.to raise_error(ArgumentError, /max_bytes must be > 0/)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "raises if max_value_bytes is 0 or less via config" do
|
42
|
+
expect do
|
43
|
+
Mudis.configure do |c|
|
44
|
+
c.max_value_bytes = 0
|
45
|
+
end
|
46
|
+
end.to raise_error(ArgumentError, /max_value_bytes must be > 0/)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "raises if max_value_bytes exceeds max_bytes" do
|
50
|
+
expect do
|
51
|
+
Mudis.configure do |c|
|
52
|
+
c.max_bytes = 1_000_000
|
53
|
+
c.max_value_bytes = 2_000_000
|
54
|
+
end
|
55
|
+
end.to raise_error(ArgumentError, /max_value_bytes cannot exceed max_bytes/)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mudis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- kiebor81
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-07-
|
11
|
+
date: 2025-07-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: climate_control
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.1.0
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.1.0
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: rspec
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -31,12 +45,15 @@ executables: []
|
|
31
45
|
extensions: []
|
32
46
|
extra_rdoc_files:
|
33
47
|
- sig/mudis.rbs
|
48
|
+
- sig/mudis_config.rbs
|
34
49
|
files:
|
35
50
|
- README.md
|
36
51
|
- lib/mudis.rb
|
37
52
|
- lib/mudis/version.rb
|
38
53
|
- lib/mudis_config.rb
|
39
54
|
- sig/mudis.rbs
|
55
|
+
- sig/mudis_config.rbs
|
56
|
+
- spec/guardrails_spec.rb
|
40
57
|
- spec/mudis_spec.rb
|
41
58
|
homepage: https://github.com/kiebor81/mudis
|
42
59
|
licenses:
|
@@ -62,4 +79,5 @@ signing_key:
|
|
62
79
|
specification_version: 4
|
63
80
|
summary: A fast in-memory Ruby LRU cache with compression and expiry.
|
64
81
|
test_files:
|
82
|
+
- spec/guardrails_spec.rb
|
65
83
|
- spec/mudis_spec.rb
|