mudis 0.9.0 → 0.9.4
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 +114 -16
- data/lib/mudis/bound.rb +128 -0
- data/lib/mudis/metrics.rb +21 -2
- data/lib/mudis/persistence.rb +3 -1
- data/lib/mudis/version.rb +1 -1
- data/lib/mudis.rb +119 -26
- data/lib/mudis_client.rb +69 -31
- data/lib/mudis_config.rb +2 -0
- data/lib/mudis_ipc_config.rb +10 -0
- data/lib/mudis_server.rb +14 -9
- data/sig/mudis.rbs +19 -4
- data/sig/mudis_bound.rbs +25 -0
- data/sig/mudis_client.rbs +15 -5
- data/sig/mudis_config.rbs +5 -0
- data/sig/mudis_expiry.rbs +1 -1
- data/sig/mudis_ipc_config.rbs +8 -0
- data/sig/mudis_lru.rbs +1 -1
- data/sig/mudis_metrics.rbs +1 -1
- data/sig/mudis_persistence.rbs +4 -4
- data/sig/mudis_server.rbs +3 -3
- data/spec/api_compatibility_spec.rb +3 -0
- data/spec/bound_spec.rb +89 -0
- data/spec/guardrails_spec.rb +14 -0
- data/spec/memory_guard_spec.rb +9 -0
- data/spec/metrics_spec.rb +21 -0
- data/spec/modules/metrics_spec.rb +4 -1
- data/spec/modules/persistence_spec.rb +24 -26
- data/spec/mudis_client_spec.rb +84 -10
- data/spec/mudis_server_spec.rb +57 -16
- data/spec/mudis_spec.rb +37 -0
- data/spec/namespace_spec.rb +23 -0
- metadata +8 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 987b789b060c3e66ed1ce29fc8fc868f9955e986349a48da34f2d06f29bf21f4
|
|
4
|
+
data.tar.gz: cb846d56202f3934cb1f4e47ede9d12ef3cd8af050cedd6e5f901945b95c41ef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c14266d9bf5121b085f79029678ca4bf429451adf9335710d2046d152a9a17618051d141e2091fc6a260d5bcf9ec522680462037f9576d2d2504d09ddc8418e2
|
|
7
|
+
data.tar.gz: 217d6a415f9f7b9bea4240d6300337e4ea86e7aa10308a92dfb8c673ad8410f13cefb77c424bd00c7b2b138c698aefec030d47709fa78bd0b87dcb1e797874e7
|
data/README.md
CHANGED
|
@@ -29,8 +29,8 @@ Mudis also works naturally in Hanami because it’s a pure Ruby in-memory cache.
|
|
|
29
29
|
- [Installation](#installation)
|
|
30
30
|
- [Configuration (Ruby/Rails)](#configuration-rubyrails)
|
|
31
31
|
- [Configuration (Hanami)](#configuration-hanami)
|
|
32
|
-
- [Start and Stop
|
|
33
|
-
- [Starting
|
|
32
|
+
- [Start and Stop Expiry Thread](#start-and-stop-expiry-thread)
|
|
33
|
+
- [Starting Expiry Thread](#starting-expiry-thread)
|
|
34
34
|
- [Graceful Shutdown](#graceful-shutdown)
|
|
35
35
|
- [Basic Usage](#basic-usage)
|
|
36
36
|
- [Developer Utilities](#developer-utilities)
|
|
@@ -96,15 +96,57 @@ There are plenty out there, in various states of maintenance and in many shapes
|
|
|
96
96
|
|
|
97
97
|
#### Internal Structure and Behaviour
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
```mermaid
|
|
100
|
+
flowchart TD
|
|
101
|
+
A[Mudis] --> C[Config]
|
|
102
|
+
A --> B[Shards/Buckets]
|
|
103
|
+
B --> S[Store Hash]
|
|
104
|
+
B --> L[LRU List]
|
|
105
|
+
B --> M[Mutex]
|
|
106
|
+
A --> E[Expiry Thread]
|
|
107
|
+
A --> P[Persistence]
|
|
108
|
+
A --> R[Metrics]
|
|
109
|
+
A --> N[Namespace]
|
|
110
|
+
```
|
|
100
111
|
|
|
101
112
|
#### Write - Read - Eviction
|
|
102
113
|
|
|
103
|
-
|
|
114
|
+
```mermaid
|
|
115
|
+
flowchart TD
|
|
116
|
+
W[Write] --> S[Serialize]
|
|
117
|
+
S --> Z{Compress?}
|
|
118
|
+
Z -- Yes --> ZL[Zlib Deflate]
|
|
119
|
+
Z -- No --> SZ[Raw]
|
|
120
|
+
ZL --> G[Size/Memory Guardrails]
|
|
121
|
+
SZ --> G
|
|
122
|
+
G --> T[Set TTL]
|
|
123
|
+
T --> I[Insert in Bucket]
|
|
124
|
+
I --> L[LRU Insert]
|
|
125
|
+
L --> M[Update Bytes]
|
|
126
|
+
R[Read] --> LK[Lookup]
|
|
127
|
+
LK --> X{Expired?}
|
|
128
|
+
X -- Yes --> EV[Evict]
|
|
129
|
+
X -- No --> D[Deserialize/Inflate]
|
|
130
|
+
D --> PR[Promote LRU]
|
|
131
|
+
PR --> OUT[Return Value]
|
|
132
|
+
E[Evict] --> LRU[LRU Tail]
|
|
133
|
+
```
|
|
104
134
|
|
|
105
135
|
#### Cache Key Lifecycle
|
|
106
136
|
|
|
107
|
-
|
|
137
|
+
```mermaid
|
|
138
|
+
sequenceDiagram
|
|
139
|
+
participant Client
|
|
140
|
+
participant Mudis
|
|
141
|
+
participant LRU as LRU List
|
|
142
|
+
Client->>Mudis: write(key, value)
|
|
143
|
+
Mudis->>LRU: insert(key) at head
|
|
144
|
+
Client->>Mudis: read(key)
|
|
145
|
+
Mudis->>LRU: promote(key) to head
|
|
146
|
+
Note over Mudis: if over threshold, evict tail
|
|
147
|
+
Mudis->>LRU: evict(tail)
|
|
148
|
+
Mudis-->>Client: value or nil
|
|
149
|
+
```
|
|
108
150
|
|
|
109
151
|
---
|
|
110
152
|
|
|
@@ -115,9 +157,10 @@ There are plenty out there, in various states of maintenance and in many shapes
|
|
|
115
157
|
- **LRU Eviction**: Automatically evicts least recently used items as memory fills up.
|
|
116
158
|
- **Expiry Support**: Optional TTL per key with background cleanup thread.
|
|
117
159
|
- **Compression**: Optional Zlib compression for large values.
|
|
118
|
-
- **Metrics**: Tracks hits, misses, and evictions.
|
|
160
|
+
- **Metrics**: Tracks hits, misses, and evictions, with optional per-namespace counters.
|
|
119
161
|
- **IPC Mode**: Shared cross-process caching for multi-process aplications.
|
|
120
162
|
- **Soft-persistence**: Data snapshot and reload.
|
|
163
|
+
- **Caller Binding**: Optional scoped cache wrappers via `Mudis.bind`.
|
|
121
164
|
|
|
122
165
|
---
|
|
123
166
|
|
|
@@ -147,6 +190,7 @@ Mudis.configure do |c|
|
|
|
147
190
|
c.max_value_bytes = 2_000_000 # Reject values > 2MB
|
|
148
191
|
c.hard_memory_limit = true # enforce hard memory limits
|
|
149
192
|
c.max_bytes = 1_073_741_824 # set maximum cache size
|
|
193
|
+
c.eviction_threshold = 0.9 # evict when a bucket exceeds 90% of max_bytes
|
|
150
194
|
end
|
|
151
195
|
|
|
152
196
|
Mudis.start_expiry_thread(interval: 60) # Cleanup every 60s
|
|
@@ -164,6 +208,7 @@ Mudis.compress = true # Compress values using Zlib
|
|
|
164
208
|
Mudis.max_value_bytes = 2_000_000 # Reject values > 2MB
|
|
165
209
|
Mudis.hard_memory_limit = true # enforce hard memory limits
|
|
166
210
|
Mudis.max_bytes = 1_073_741_824 # set maximum cache size
|
|
211
|
+
Mudis.eviction_threshold = 0.9 # evict when a bucket exceeds 90% of max_bytes
|
|
167
212
|
|
|
168
213
|
Mudis.start_expiry_thread(interval: 60) # Cleanup every 60s
|
|
169
214
|
|
|
@@ -245,11 +290,11 @@ end
|
|
|
245
290
|
|
|
246
291
|
---
|
|
247
292
|
|
|
248
|
-
## Start and Stop
|
|
293
|
+
## Start and Stop Expiry Thread
|
|
249
294
|
|
|
250
295
|
The expiry thread is not triggered automatically when your application starts. You must add the start and stop in the respective process hooks.
|
|
251
296
|
|
|
252
|
-
### Starting
|
|
297
|
+
### Starting Expiry Thread
|
|
253
298
|
|
|
254
299
|
To enable background expiration and removal, you must start the expiry thread at start up after configuration.
|
|
255
300
|
|
|
@@ -289,11 +334,31 @@ Mudis.exists?('user:123') # => true
|
|
|
289
334
|
|
|
290
335
|
# Atomically update
|
|
291
336
|
Mudis.update('user:123') { |data| data.merge(age: 30) }
|
|
337
|
+
# Note: update refreshes TTL based on the original TTL duration.
|
|
292
338
|
|
|
293
339
|
# Delete a key
|
|
294
340
|
Mudis.delete('user:123')
|
|
295
341
|
```
|
|
296
342
|
|
|
343
|
+
### Caller-Scoped Cache (Bound)
|
|
344
|
+
|
|
345
|
+
For caller-bound caching (per-tenant or per-service scoping), use `Mudis.bind` to create a scoped wrapper:
|
|
346
|
+
|
|
347
|
+
```ruby
|
|
348
|
+
cache = Mudis.bind(
|
|
349
|
+
namespace: "caller:123",
|
|
350
|
+
default_ttl: 60,
|
|
351
|
+
max_ttl: 300,
|
|
352
|
+
max_value_bytes: 128_000
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
cache.write("profile", { name: "Alice" })
|
|
356
|
+
cache.read("profile") # => { "name" => "Alice" }
|
|
357
|
+
cache.fetch("expensive", singleflight: true) { expensive_query }
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
The wrapper delegates to Mudis but automatically applies the namespace and optional per-caller guardrails.
|
|
361
|
+
|
|
297
362
|
### Developer Utilities
|
|
298
363
|
|
|
299
364
|
Mudis provides utility methods to help with test environments, console debugging, and dev tool resets.
|
|
@@ -419,7 +484,7 @@ class MudisService
|
|
|
419
484
|
# @param force [Boolean] force recomputation
|
|
420
485
|
# @yield return value if key is missing
|
|
421
486
|
def fetch(expires_in: nil, force: false)
|
|
422
|
-
Mudis.fetch(cache_key, expires_in: expires_in, force: force, namespace: namespace) do
|
|
487
|
+
Mudis.fetch(cache_key, expires_in: expires_in, force: force, namespace: namespace, singleflight: true) do
|
|
423
488
|
yield
|
|
424
489
|
end
|
|
425
490
|
end
|
|
@@ -477,6 +542,13 @@ Mudis.metrics
|
|
|
477
542
|
|
|
478
543
|
```
|
|
479
544
|
|
|
545
|
+
You can also query metrics for a specific namespace:
|
|
546
|
+
|
|
547
|
+
```ruby
|
|
548
|
+
Mudis.metrics(namespace: "users")
|
|
549
|
+
# => { hits: 10, misses: 2, evictions: 1, rejected: 0, namespace: "users" }
|
|
550
|
+
```
|
|
551
|
+
|
|
480
552
|
Optionally, expose Mudis metrics from a controller or action for remote analysis and monitoring.
|
|
481
553
|
|
|
482
554
|
**Rails:**
|
|
@@ -546,6 +618,7 @@ end
|
|
|
546
618
|
| `Mudis.start_expiry_thread` | Background TTL cleanup loop (every N sec) | Disabled by default|
|
|
547
619
|
| `Mudis.hard_memory_limit` | Enforce hard memory limits on key size and reject if exceeded | `false`|
|
|
548
620
|
| `Mudis.max_bytes` | Maximum allowed cache size | `1GB`|
|
|
621
|
+
| `Mudis.eviction_threshold` | Evict when a bucket exceeds this ratio of max_bytes | `0.9` |
|
|
549
622
|
| `Mudis.max_ttl` | Set the maximum permitted TTL | `nil` (no limit) |
|
|
550
623
|
| `Mudis.default_ttl` | Set the default TTL for fallback when none is provided | `nil` |
|
|
551
624
|
|
|
@@ -611,7 +684,16 @@ Mudis.load_snapshot!
|
|
|
611
684
|
|
|
612
685
|
#### Example Flow
|
|
613
686
|
|
|
614
|
-
|
|
687
|
+
```mermaid
|
|
688
|
+
flowchart LR
|
|
689
|
+
A[App Boot] --> B[Configure Mudis]
|
|
690
|
+
B --> C[Load Snapshot]
|
|
691
|
+
C --> D[Cache Warm]
|
|
692
|
+
D --> E[Normal Operations]
|
|
693
|
+
E --> F[Exit Hook]
|
|
694
|
+
F --> G[Save Snapshot]
|
|
695
|
+
G --> H[Snapshot File]
|
|
696
|
+
```
|
|
615
697
|
|
|
616
698
|
1. On startup, `Mudis.load_snapshot!` repopulates the cache.
|
|
617
699
|
2. Your app uses the cache as normal (`write`, `read`, `fetch`, etc.).
|
|
@@ -644,10 +726,21 @@ This design allows multiple workers to share the same cache without duplicating
|
|
|
644
726
|
| **Shared Cache Across Processes** | All Puma workers share one Mudis instance via IPC. |
|
|
645
727
|
| **Zero External Dependencies** | No Redis, Memcached, or separate daemon required. |
|
|
646
728
|
| **Memory Efficient** | Cache data stored only once, not duplicated per worker. |
|
|
647
|
-
| **
|
|
729
|
+
| **Feature Coverage** | Core cache ops + introspection (keys, inspect, least_touched) and metrics are supported. |
|
|
648
730
|
| **Safe & Local** | Communication is limited to the host system’s UNIX socket, ensuring isolation and speed. |
|
|
649
731
|
|
|
650
|
-
|
|
732
|
+
```mermaid
|
|
733
|
+
flowchart LR
|
|
734
|
+
M[Master Process] --> S[MudisServer]
|
|
735
|
+
W1[Worker 1] --> C1[MudisClient]
|
|
736
|
+
W2[Worker 2] --> C2[MudisClient]
|
|
737
|
+
W3[Worker 3] --> C3[MudisClient]
|
|
738
|
+
C1 --> U[Unix Socket/TCP]
|
|
739
|
+
C2 --> U
|
|
740
|
+
C3 --> U
|
|
741
|
+
U --> S
|
|
742
|
+
S --> Cache[Mudis Cache]
|
|
743
|
+
```
|
|
651
744
|
|
|
652
745
|
### Setup (Puma)
|
|
653
746
|
|
|
@@ -701,13 +794,18 @@ if defined?($mudis) && $mudis
|
|
|
701
794
|
def self.delete(*a, **k) = $mudis.delete(*a, **k)
|
|
702
795
|
def self.fetch(*a, **k, &b) = $mudis.fetch(*a, **k, &b)
|
|
703
796
|
def self.metrics = $mudis.metrics
|
|
704
|
-
def self.reset_metrics! = $mudis.reset_metrics!
|
|
705
|
-
def self.reset! = $mudis.reset!
|
|
706
797
|
end
|
|
707
798
|
|
|
708
799
|
end
|
|
709
800
|
```
|
|
710
801
|
|
|
802
|
+
**IPC safety limitations:**
|
|
803
|
+
|
|
804
|
+
- Administrative operations are **not** exposed over IPC (e.g., `reset!`, `reset_metrics!`, `save_snapshot!`, `load_snapshot!`).
|
|
805
|
+
- IPC client requests use timeouts and retries:
|
|
806
|
+
- `MUDIS_IPC_TIMEOUT` (seconds, default: `1`)
|
|
807
|
+
- `MUDIS_IPC_RETRIES` (default: `1`)
|
|
808
|
+
|
|
711
809
|
**Use IPC mode when:**
|
|
712
810
|
|
|
713
811
|
- Running Rails or Rack apps under Puma cluster or multi-process background job workers.
|
|
@@ -799,7 +897,7 @@ _10000 iterations of 512KB, JSON, compression ON_
|
|
|
799
897
|
## Known Limitations
|
|
800
898
|
|
|
801
899
|
- Data is **non-persistent**; only soft-persistence is optionally provided.
|
|
802
|
-
- No SQL or equivallent query interface for cached data. Data is per Key retrieval only.
|
|
900
|
+
- No SQL or equivallent query interface for cached data. Data is per Key retrieval only (see [Mudis-QL](https://github.com/kiebor81/mudis-ql)).
|
|
803
901
|
- Compression introduces CPU overhead.
|
|
804
902
|
|
|
805
903
|
---
|
|
@@ -934,7 +1032,7 @@ Mudis is not intended to be a general-purpose, distributed caching platform. You
|
|
|
934
1032
|
|
|
935
1033
|
- [x] Review Mudis for improved readability and reduce complexity in top-level functions
|
|
936
1034
|
- [x] Enhanced guards
|
|
937
|
-
- [
|
|
1035
|
+
- [x] Refactor main classes for enhanced readability
|
|
938
1036
|
- [ ] Review for functionality gaps and enhance as needed
|
|
939
1037
|
|
|
940
1038
|
---
|
data/lib/mudis/bound.rb
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "zlib"
|
|
4
|
+
|
|
5
|
+
class Mudis
|
|
6
|
+
# Scoped wrapper for caller-bound access with optional per-caller policy.
|
|
7
|
+
class Bound
|
|
8
|
+
def initialize(namespace:, default_ttl: nil, max_ttl: nil, max_value_bytes: nil)
|
|
9
|
+
raise ArgumentError, "namespace is required" if namespace.nil? || namespace.to_s.empty?
|
|
10
|
+
|
|
11
|
+
@namespace = namespace
|
|
12
|
+
@default_ttl = default_ttl
|
|
13
|
+
@max_ttl = max_ttl
|
|
14
|
+
@max_value_bytes = max_value_bytes
|
|
15
|
+
@inflight_mutexes_lock = Mutex.new
|
|
16
|
+
@inflight_mutexes = {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_reader :namespace
|
|
20
|
+
|
|
21
|
+
def read(key)
|
|
22
|
+
Mudis.read(key, namespace: @namespace)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def write(key, value, expires_in: nil)
|
|
26
|
+
return if exceeds_max_value_bytes?(value)
|
|
27
|
+
|
|
28
|
+
Mudis.write(key, value, expires_in: effective_ttl(expires_in), namespace: @namespace)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def update(key)
|
|
32
|
+
Mudis.update(key, namespace: @namespace) do |current|
|
|
33
|
+
next_value = yield(current)
|
|
34
|
+
exceeds_max_value_bytes?(next_value) ? current : next_value
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def delete(key)
|
|
39
|
+
Mudis.delete(key, namespace: @namespace)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def exists?(key)
|
|
43
|
+
Mudis.exists?(key, namespace: @namespace)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def fetch(key, expires_in: nil, force: false, singleflight: false)
|
|
47
|
+
return fetch_without_lock(key, expires_in:, force:) { yield } unless singleflight
|
|
48
|
+
|
|
49
|
+
with_inflight_lock(key) do
|
|
50
|
+
fetch_without_lock(key, expires_in:, force:) { yield }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def clear(key)
|
|
55
|
+
delete(key)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def replace(key, value, expires_in: nil)
|
|
59
|
+
return if exceeds_max_value_bytes?(value)
|
|
60
|
+
|
|
61
|
+
Mudis.replace(key, value, expires_in: effective_ttl(expires_in), namespace: @namespace)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def inspect(key)
|
|
65
|
+
Mudis.inspect(key, namespace: @namespace)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def keys
|
|
69
|
+
Mudis.keys(namespace: @namespace)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def metrics
|
|
73
|
+
Mudis.metrics(namespace: @namespace)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def clear_namespace
|
|
77
|
+
Mudis.clear_namespace(namespace: @namespace)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def effective_ttl(expires_in)
|
|
83
|
+
ttl = expires_in || @default_ttl
|
|
84
|
+
return nil unless ttl
|
|
85
|
+
return ttl unless @max_ttl
|
|
86
|
+
|
|
87
|
+
[ttl, @max_ttl].min
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def exceeds_max_value_bytes?(value)
|
|
91
|
+
return false unless @max_value_bytes
|
|
92
|
+
|
|
93
|
+
raw = Mudis.serializer.dump(value)
|
|
94
|
+
raw = Zlib::Deflate.deflate(raw) if Mudis.compress
|
|
95
|
+
raw.bytesize > @max_value_bytes
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def fetch_without_lock(key, expires_in:, force:)
|
|
99
|
+
unless force
|
|
100
|
+
cached = read(key)
|
|
101
|
+
return cached if cached
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
value = yield
|
|
105
|
+
return nil if exceeds_max_value_bytes?(value)
|
|
106
|
+
|
|
107
|
+
write(key, value, expires_in: expires_in)
|
|
108
|
+
value
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def with_inflight_lock(lock_key)
|
|
112
|
+
entry = nil
|
|
113
|
+
@inflight_mutexes_lock.synchronize do
|
|
114
|
+
entry = (@inflight_mutexes[lock_key] ||= { mutex: Mutex.new, count: 0 })
|
|
115
|
+
entry[:count] += 1
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
entry[:mutex].synchronize { yield }
|
|
119
|
+
ensure
|
|
120
|
+
@inflight_mutexes_lock.synchronize do
|
|
121
|
+
next unless entry
|
|
122
|
+
|
|
123
|
+
entry[:count] -= 1
|
|
124
|
+
@inflight_mutexes.delete(lock_key) if entry[:count] <= 0
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
data/lib/mudis/metrics.rb
CHANGED
|
@@ -4,7 +4,9 @@ class Mudis
|
|
|
4
4
|
# Metrics module handles tracking of cache hits, misses, evictions and memory usage
|
|
5
5
|
module Metrics
|
|
6
6
|
# Returns a snapshot of metrics (thread-safe)
|
|
7
|
-
def metrics # rubocop:disable Metrics/MethodLength
|
|
7
|
+
def metrics(namespace: nil) # rubocop:disable Metrics/MethodLength
|
|
8
|
+
return namespace_metrics(namespace) if namespace
|
|
9
|
+
|
|
8
10
|
@metrics_mutex.synchronize do
|
|
9
11
|
{
|
|
10
12
|
hits: @metrics[:hits],
|
|
@@ -30,13 +32,30 @@ class Mudis
|
|
|
30
32
|
@metrics_mutex.synchronize do
|
|
31
33
|
@metrics = { hits: 0, misses: 0, evictions: 0, rejected: 0 }
|
|
32
34
|
end
|
|
35
|
+
|
|
36
|
+
@metrics_by_namespace_mutex.synchronize do
|
|
37
|
+
@metrics_by_namespace = {}
|
|
38
|
+
end
|
|
33
39
|
end
|
|
34
40
|
|
|
35
41
|
private
|
|
36
42
|
|
|
37
43
|
# Thread-safe metric increment
|
|
38
|
-
def metric(name)
|
|
44
|
+
def metric(name, namespace: nil)
|
|
39
45
|
@metrics_mutex.synchronize { @metrics[name] += 1 }
|
|
46
|
+
return unless namespace
|
|
47
|
+
|
|
48
|
+
@metrics_by_namespace_mutex.synchronize do
|
|
49
|
+
@metrics_by_namespace[namespace] ||= { hits: 0, misses: 0, evictions: 0, rejected: 0 }
|
|
50
|
+
@metrics_by_namespace[namespace][name] += 1
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def namespace_metrics(namespace)
|
|
55
|
+
@metrics_by_namespace_mutex.synchronize do
|
|
56
|
+
entry = @metrics_by_namespace[namespace] || { hits: 0, misses: 0, evictions: 0, rejected: 0 }
|
|
57
|
+
entry.merge(namespace: namespace)
|
|
58
|
+
end
|
|
40
59
|
end
|
|
41
60
|
end
|
|
42
61
|
end
|
data/lib/mudis/persistence.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
3
5
|
class Mudis
|
|
4
6
|
# Persistence module handles snapshot save/load operations for warm boot support
|
|
5
7
|
module Persistence
|
|
@@ -80,7 +82,7 @@ class Mudis
|
|
|
80
82
|
def safe_write_snapshot(data) # rubocop:disable Metrics/MethodLength
|
|
81
83
|
path = @persistence_path
|
|
82
84
|
dir = File.dirname(path)
|
|
83
|
-
|
|
85
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
84
86
|
|
|
85
87
|
payload =
|
|
86
88
|
if (@persistence_format || :marshal).to_sym == :json
|
data/lib/mudis/version.rb
CHANGED