mudis 0.8.0 → 0.9.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/README.md +958 -932
- data/lib/mudis/expiry.rb +53 -0
- data/lib/mudis/lru.rb +66 -0
- data/lib/mudis/metrics.rb +42 -0
- data/lib/mudis/namespace.rb +47 -0
- data/lib/mudis/persistence.rb +113 -0
- data/lib/mudis/version.rb +3 -3
- data/lib/mudis.rb +370 -631
- data/lib/mudis_client.rb +107 -65
- data/lib/mudis_config.rb +35 -35
- data/lib/mudis_ipc_config.rb +13 -0
- data/lib/mudis_proxy.rb +39 -37
- data/lib/mudis_server.rb +100 -81
- data/sig/mudis.rbs +62 -56
- data/sig/mudis_client.rbs +24 -22
- data/sig/mudis_config.rbs +10 -10
- data/sig/mudis_expiry.rbs +13 -0
- data/sig/mudis_ipc_config.rbs +10 -0
- data/sig/mudis_lru.rbs +21 -0
- data/sig/mudis_metrics.rbs +11 -0
- data/sig/mudis_namespace.rbs +13 -0
- data/sig/mudis_persistence.rbs +19 -0
- data/sig/mudis_server.rbs +16 -6
- data/spec/api_compatibility_spec.rb +155 -0
- data/spec/eviction_spec.rb +29 -29
- data/spec/guardrails_spec.rb +138 -138
- data/spec/memory_guard_spec.rb +33 -33
- data/spec/metrics_spec.rb +34 -34
- data/spec/modules/expiry_spec.rb +170 -0
- data/spec/modules/lru_spec.rb +149 -0
- data/spec/modules/metrics_spec.rb +105 -0
- data/spec/modules/namespace_spec.rb +157 -0
- data/spec/modules/persistence_spec.rb +125 -0
- data/spec/mudis_client_spec.rb +147 -137
- data/spec/mudis_server_spec.rb +100 -90
- data/spec/mudis_spec.rb +183 -183
- data/spec/namespace_spec.rb +69 -69
- data/spec/persistence_spec.rb +38 -37
- data/spec/reset_spec.rb +31 -31
- metadata +51 -3
data/sig/mudis_client.rbs
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
class MudisClient
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def initialize: () -> void
|
|
5
|
-
|
|
6
|
-
def
|
|
7
|
-
|
|
8
|
-
def
|
|
9
|
-
|
|
10
|
-
def
|
|
11
|
-
|
|
12
|
-
def
|
|
13
|
-
|
|
14
|
-
def
|
|
15
|
-
|
|
16
|
-
def
|
|
17
|
-
|
|
18
|
-
def
|
|
19
|
-
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
def
|
|
1
|
+
class MudisClient
|
|
2
|
+
include MudisIPCConfig
|
|
3
|
+
|
|
4
|
+
def initialize: () -> void
|
|
5
|
+
|
|
6
|
+
def open_connection: () -> (TCPSocket | UNIXSocket)
|
|
7
|
+
|
|
8
|
+
def request: (payload: { cmd: String, key?: String, value?: untyped, ttl?: Integer?, namespace?: String? }) -> untyped
|
|
9
|
+
|
|
10
|
+
def read: (key: String, namespace?: String?) -> untyped
|
|
11
|
+
|
|
12
|
+
def write: (key: String, value: untyped, expires_in?: Integer?, namespace?: String?) -> untyped
|
|
13
|
+
|
|
14
|
+
def delete: (key: String, namespace?: String?) -> untyped
|
|
15
|
+
|
|
16
|
+
def exists?: (key: String, namespace?: String?) -> bool
|
|
17
|
+
|
|
18
|
+
def fetch: (key: String, expires_in?: Integer?, namespace?: String?, &block: { () -> untyped }) -> untyped
|
|
19
|
+
|
|
20
|
+
def metrics: () -> { reads: Integer, writes: Integer, deletes: Integer, exists: Integer }
|
|
21
|
+
|
|
22
|
+
def reset_metrics!: () -> void
|
|
23
|
+
|
|
24
|
+
def reset!: () -> void
|
|
23
25
|
end
|
data/sig/mudis_config.rbs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
class MudisConfig
|
|
2
|
-
attr_accessor serializer: Object
|
|
3
|
-
attr_accessor compress: bool
|
|
4
|
-
attr_accessor max_value_bytes: Integer?
|
|
5
|
-
attr_accessor hard_memory_limit: bool
|
|
6
|
-
attr_accessor max_bytes: Integer
|
|
7
|
-
attr_accessor max_ttl: Integer?
|
|
8
|
-
attr_accessor default_ttl: Integer?
|
|
9
|
-
attr_accessor buckets: Integer?
|
|
10
|
-
end
|
|
1
|
+
class MudisConfig
|
|
2
|
+
attr_accessor serializer: Object
|
|
3
|
+
attr_accessor compress: bool
|
|
4
|
+
attr_accessor max_value_bytes: Integer?
|
|
5
|
+
attr_accessor hard_memory_limit: bool
|
|
6
|
+
attr_accessor max_bytes: Integer
|
|
7
|
+
attr_accessor max_ttl: Integer?
|
|
8
|
+
attr_accessor default_ttl: Integer?
|
|
9
|
+
attr_accessor buckets: Integer?
|
|
10
|
+
end
|
data/sig/mudis_lru.rbs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class Mudis
|
|
2
|
+
class LRUNode
|
|
3
|
+
attr_accessor key: String
|
|
4
|
+
attr_accessor prev: LRUNode?
|
|
5
|
+
attr_accessor next: LRUNode?
|
|
6
|
+
|
|
7
|
+
def initialize: (String) -> void
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module LRU
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def evict_key: (Integer, String) -> void
|
|
14
|
+
|
|
15
|
+
def insert_lru: (Integer, LRUNode) -> void
|
|
16
|
+
|
|
17
|
+
def promote_lru: (Integer, String) -> void
|
|
18
|
+
|
|
19
|
+
def remove_node: (Integer, LRUNode) -> void
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class Mudis
|
|
2
|
+
module Namespace
|
|
3
|
+
def keys: (namespace: String) -> Array[String]
|
|
4
|
+
|
|
5
|
+
def clear_namespace: (namespace: String) -> void
|
|
6
|
+
|
|
7
|
+
def with_namespace: (namespace: String) { () -> untyped } -> untyped
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def namespaced_key: (String, ?namespace: String?) -> String
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class Mudis
|
|
2
|
+
module Persistence
|
|
3
|
+
def save_snapshot!: () -> void
|
|
4
|
+
|
|
5
|
+
def load_snapshot!: () -> void
|
|
6
|
+
|
|
7
|
+
def install_persistence_hook!: () -> void
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def snapshot_dump: () -> Hash[String, untyped]
|
|
12
|
+
|
|
13
|
+
def snapshot_restore: (Hash[String, untyped]) -> void
|
|
14
|
+
|
|
15
|
+
def safe_write_snapshot: (Hash[String, untyped]) -> void
|
|
16
|
+
|
|
17
|
+
def read_snapshot: () -> Hash[String, untyped]
|
|
18
|
+
end
|
|
19
|
+
end
|
data/sig/mudis_server.rbs
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
class MudisServer
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def self.start!: () -> void
|
|
5
|
-
|
|
6
|
-
def self.
|
|
1
|
+
class MudisServer
|
|
2
|
+
include MudisIPCConfig
|
|
3
|
+
|
|
4
|
+
def self.start!: () -> void
|
|
5
|
+
|
|
6
|
+
def self.start_tcp_server!: () -> void
|
|
7
|
+
|
|
8
|
+
def self.start_unix_server!: () -> void
|
|
9
|
+
|
|
10
|
+
def self.accept_connections: (server: (TCPServer | UNIXServer)) -> void
|
|
11
|
+
|
|
12
|
+
def self.handle_client: (socket: (TCPSocket | UNIXSocket)) -> void
|
|
13
|
+
|
|
14
|
+
def self.process_request: (req: Hash[String, untyped]) -> Hash[Symbol, untyped]
|
|
15
|
+
|
|
16
|
+
def self.write_response: (socket: (TCPSocket | UNIXSocket), payload: Hash[Symbol, untyped]) -> void
|
|
7
17
|
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe "Mudis Public API" do
|
|
6
|
+
describe "ensures no breaking changes" do
|
|
7
|
+
it "exposes all core cache operations" do
|
|
8
|
+
expect(Mudis).to respond_to(:read)
|
|
9
|
+
expect(Mudis).to respond_to(:write)
|
|
10
|
+
expect(Mudis).to respond_to(:delete)
|
|
11
|
+
expect(Mudis).to respond_to(:exists?)
|
|
12
|
+
expect(Mudis).to respond_to(:update)
|
|
13
|
+
expect(Mudis).to respond_to(:fetch)
|
|
14
|
+
expect(Mudis).to respond_to(:clear)
|
|
15
|
+
expect(Mudis).to respond_to(:replace)
|
|
16
|
+
expect(Mudis).to respond_to(:inspect)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "exposes all configuration methods" do
|
|
20
|
+
expect(Mudis).to respond_to(:configure)
|
|
21
|
+
expect(Mudis).to respond_to(:config)
|
|
22
|
+
expect(Mudis).to respond_to(:serializer)
|
|
23
|
+
expect(Mudis).to respond_to(:serializer=)
|
|
24
|
+
expect(Mudis).to respond_to(:compress)
|
|
25
|
+
expect(Mudis).to respond_to(:compress=)
|
|
26
|
+
expect(Mudis).to respond_to(:max_bytes)
|
|
27
|
+
expect(Mudis).to respond_to(:max_bytes=)
|
|
28
|
+
expect(Mudis).to respond_to(:max_value_bytes)
|
|
29
|
+
expect(Mudis).to respond_to(:max_value_bytes=)
|
|
30
|
+
expect(Mudis).to respond_to(:hard_memory_limit)
|
|
31
|
+
expect(Mudis).to respond_to(:hard_memory_limit=)
|
|
32
|
+
expect(Mudis).to respond_to(:max_ttl)
|
|
33
|
+
expect(Mudis).to respond_to(:max_ttl=)
|
|
34
|
+
expect(Mudis).to respond_to(:default_ttl)
|
|
35
|
+
expect(Mudis).to respond_to(:default_ttl=)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "exposes all metrics methods" do
|
|
39
|
+
expect(Mudis).to respond_to(:metrics)
|
|
40
|
+
expect(Mudis).to respond_to(:reset_metrics!)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "exposes all expiry methods" do
|
|
44
|
+
expect(Mudis).to respond_to(:start_expiry_thread)
|
|
45
|
+
expect(Mudis).to respond_to(:stop_expiry_thread)
|
|
46
|
+
expect(Mudis).to respond_to(:cleanup_expired!)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "exposes all namespace methods" do
|
|
50
|
+
expect(Mudis).to respond_to(:keys)
|
|
51
|
+
expect(Mudis).to respond_to(:clear_namespace)
|
|
52
|
+
expect(Mudis).to respond_to(:with_namespace)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "exposes all persistence methods" do
|
|
56
|
+
expect(Mudis).to respond_to(:save_snapshot!)
|
|
57
|
+
expect(Mudis).to respond_to(:load_snapshot!)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "exposes all utility methods" do
|
|
61
|
+
expect(Mudis).to respond_to(:reset!)
|
|
62
|
+
expect(Mudis).to respond_to(:all_keys)
|
|
63
|
+
expect(Mudis).to respond_to(:current_memory_bytes)
|
|
64
|
+
expect(Mudis).to respond_to(:max_memory_bytes)
|
|
65
|
+
expect(Mudis).to respond_to(:least_touched)
|
|
66
|
+
expect(Mudis).to respond_to(:buckets)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "maintains backward compatibility with method signatures" do
|
|
70
|
+
# Core operations accept namespace parameter
|
|
71
|
+
expect(Mudis.method(:read).parameters).to include([:key, :namespace])
|
|
72
|
+
expect(Mudis.method(:write).parameters).to include([:key, :namespace])
|
|
73
|
+
expect(Mudis.method(:delete).parameters).to include([:key, :namespace])
|
|
74
|
+
expect(Mudis.method(:exists?).parameters).to include([:key, :namespace])
|
|
75
|
+
|
|
76
|
+
# Fetch accepts expires_in, force, and namespace
|
|
77
|
+
expect(Mudis.method(:fetch).parameters).to include([:key, :expires_in])
|
|
78
|
+
expect(Mudis.method(:fetch).parameters).to include([:key, :force])
|
|
79
|
+
expect(Mudis.method(:fetch).parameters).to include([:key, :namespace])
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "verifies LRUNode class is still accessible" do
|
|
83
|
+
expect(defined?(Mudis::LRUNode)).to be_truthy
|
|
84
|
+
node = Mudis::LRUNode.new("test_key")
|
|
85
|
+
expect(node).to respond_to(:key)
|
|
86
|
+
expect(node).to respond_to(:prev)
|
|
87
|
+
expect(node).to respond_to(:next)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "verifies all public methods work correctly" do
|
|
91
|
+
Mudis.reset!
|
|
92
|
+
|
|
93
|
+
# Write and read
|
|
94
|
+
Mudis.write("test", "value")
|
|
95
|
+
expect(Mudis.read("test")).to eq("value")
|
|
96
|
+
|
|
97
|
+
# Exists
|
|
98
|
+
expect(Mudis.exists?("test")).to be true
|
|
99
|
+
|
|
100
|
+
# Update
|
|
101
|
+
Mudis.update("test") { |v| v.upcase }
|
|
102
|
+
expect(Mudis.read("test")).to eq("VALUE")
|
|
103
|
+
|
|
104
|
+
# Fetch
|
|
105
|
+
result = Mudis.fetch("new_key") { "new_value" }
|
|
106
|
+
expect(result).to eq("new_value")
|
|
107
|
+
|
|
108
|
+
# Replace
|
|
109
|
+
Mudis.replace("test", "replaced")
|
|
110
|
+
expect(Mudis.read("test")).to eq("replaced")
|
|
111
|
+
|
|
112
|
+
# Inspect
|
|
113
|
+
meta = Mudis.inspect("test")
|
|
114
|
+
expect(meta).to include(:key, :bucket, :created_at, :size_bytes)
|
|
115
|
+
|
|
116
|
+
# Namespace operations
|
|
117
|
+
Mudis.write("k1", "v1", namespace: "ns1")
|
|
118
|
+
expect(Mudis.keys(namespace: "ns1")).to include("k1")
|
|
119
|
+
|
|
120
|
+
Mudis.with_namespace("ns2") do
|
|
121
|
+
Mudis.write("k2", "v2")
|
|
122
|
+
end
|
|
123
|
+
expect(Mudis.keys(namespace: "ns2")).to include("k2")
|
|
124
|
+
|
|
125
|
+
# Clear namespace
|
|
126
|
+
Mudis.clear_namespace(namespace: "ns1")
|
|
127
|
+
expect(Mudis.keys(namespace: "ns1")).to be_empty
|
|
128
|
+
|
|
129
|
+
# Metrics
|
|
130
|
+
metrics = Mudis.metrics
|
|
131
|
+
expect(metrics).to include(:hits, :misses, :evictions, :total_memory, :buckets)
|
|
132
|
+
|
|
133
|
+
# Least touched
|
|
134
|
+
touched = Mudis.least_touched(5)
|
|
135
|
+
expect(touched).to be_an(Array)
|
|
136
|
+
|
|
137
|
+
# All keys
|
|
138
|
+
keys = Mudis.all_keys
|
|
139
|
+
expect(keys).to be_an(Array)
|
|
140
|
+
|
|
141
|
+
# Memory tracking
|
|
142
|
+
expect(Mudis.current_memory_bytes).to be > 0
|
|
143
|
+
expect(Mudis.max_memory_bytes).to be > 0
|
|
144
|
+
|
|
145
|
+
# Delete
|
|
146
|
+
Mudis.delete("test")
|
|
147
|
+
expect(Mudis.exists?("test")).to be false
|
|
148
|
+
|
|
149
|
+
# Clear (alias for delete)
|
|
150
|
+
Mudis.write("to_clear", "value")
|
|
151
|
+
Mudis.clear("to_clear")
|
|
152
|
+
expect(Mudis.exists?("to_clear")).to be false
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
data/spec/eviction_spec.rb
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "spec_helper"
|
|
4
|
-
|
|
5
|
-
RSpec.describe "Mudis LRU Eviction" do
|
|
6
|
-
before do
|
|
7
|
-
Mudis.reset!
|
|
8
|
-
Mudis.stop_expiry_thread
|
|
9
|
-
|
|
10
|
-
Mudis.instance_variable_set(:@buckets, 1)
|
|
11
|
-
Mudis.instance_variable_set(:@stores, [{}])
|
|
12
|
-
Mudis.instance_variable_set(:@mutexes, [Mutex.new])
|
|
13
|
-
Mudis.instance_variable_set(:@lru_heads, [nil])
|
|
14
|
-
Mudis.instance_variable_set(:@lru_tails, [nil])
|
|
15
|
-
Mudis.instance_variable_set(:@lru_nodes, [{}])
|
|
16
|
-
Mudis.instance_variable_set(:@current_bytes, [0])
|
|
17
|
-
Mudis.hard_memory_limit = false
|
|
18
|
-
Mudis.instance_variable_set(:@threshold_bytes, 60)
|
|
19
|
-
Mudis.max_value_bytes = 100
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
it "evicts old entries when size limit is reached" do
|
|
23
|
-
Mudis.write("a", "a" * 50)
|
|
24
|
-
Mudis.write("b", "b" * 50)
|
|
25
|
-
|
|
26
|
-
expect(Mudis.read("a")).to be_nil
|
|
27
|
-
expect(Mudis.read("b")).not_to be_nil
|
|
28
|
-
end
|
|
29
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spec_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe "Mudis LRU Eviction" do
|
|
6
|
+
before do
|
|
7
|
+
Mudis.reset!
|
|
8
|
+
Mudis.stop_expiry_thread
|
|
9
|
+
|
|
10
|
+
Mudis.instance_variable_set(:@buckets, 1)
|
|
11
|
+
Mudis.instance_variable_set(:@stores, [{}])
|
|
12
|
+
Mudis.instance_variable_set(:@mutexes, [Mutex.new])
|
|
13
|
+
Mudis.instance_variable_set(:@lru_heads, [nil])
|
|
14
|
+
Mudis.instance_variable_set(:@lru_tails, [nil])
|
|
15
|
+
Mudis.instance_variable_set(:@lru_nodes, [{}])
|
|
16
|
+
Mudis.instance_variable_set(:@current_bytes, [0])
|
|
17
|
+
Mudis.hard_memory_limit = false
|
|
18
|
+
Mudis.instance_variable_set(:@threshold_bytes, 60)
|
|
19
|
+
Mudis.max_value_bytes = 100
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "evicts old entries when size limit is reached" do
|
|
23
|
+
Mudis.write("a", "a" * 50)
|
|
24
|
+
Mudis.write("b", "b" * 50)
|
|
25
|
+
|
|
26
|
+
expect(Mudis.read("a")).to be_nil
|
|
27
|
+
expect(Mudis.read("b")).not_to be_nil
|
|
28
|
+
end
|
|
29
|
+
end
|