mudis-ql 0.1.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 +7 -0
- data/README.md +596 -0
- data/lib/mudis-ql/metrics_scope.rb +175 -0
- data/lib/mudis-ql/scope.rb +184 -0
- data/lib/mudis-ql/store.rb +79 -0
- data/lib/mudis-ql/version.rb +5 -0
- data/lib/mudis-ql.rb +49 -0
- data/spec/mudis-ql/error_handling_spec.rb +330 -0
- data/spec/mudis-ql/integration_spec.rb +337 -0
- data/spec/mudis-ql/metrics_scope_spec.rb +332 -0
- data/spec/mudis-ql/performance_spec.rb +295 -0
- data/spec/mudis-ql/scope_spec.rb +169 -0
- data/spec/mudis-ql/store_spec.rb +77 -0
- data/spec/mudis-ql_spec.rb +52 -0
- metadata +118 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe MudisQL::MetricsScope do
|
|
4
|
+
before do
|
|
5
|
+
Mudis.serializer = JSON
|
|
6
|
+
Mudis.reset!
|
|
7
|
+
Mudis.reset_metrics!
|
|
8
|
+
|
|
9
|
+
# Create some cache activity to generate metrics
|
|
10
|
+
10.times do |i|
|
|
11
|
+
Mudis.write("key#{i}", { value: i }, namespace: "test")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Create some reads for hits
|
|
15
|
+
5.times { |i| Mudis.read("key#{i}", namespace: "test") }
|
|
16
|
+
|
|
17
|
+
# Try to read non-existent keys for misses
|
|
18
|
+
3.times { |i| Mudis.read("missing#{i}", namespace: "test") }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
let(:metrics_scope) { described_class.new }
|
|
22
|
+
|
|
23
|
+
describe "#summary" do
|
|
24
|
+
it "returns top-level metrics without arrays" do
|
|
25
|
+
summary = metrics_scope.summary
|
|
26
|
+
|
|
27
|
+
expect(summary).to have_key(:hits)
|
|
28
|
+
expect(summary).to have_key(:misses)
|
|
29
|
+
expect(summary).to have_key(:evictions)
|
|
30
|
+
expect(summary).not_to have_key(:least_touched)
|
|
31
|
+
expect(summary).not_to have_key(:buckets)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "shows correct hit and miss counts" do
|
|
35
|
+
summary = metrics_scope.summary
|
|
36
|
+
|
|
37
|
+
expect(summary[:hits]).to eq(5)
|
|
38
|
+
expect(summary[:misses]).to eq(3)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "#least_touched" do
|
|
43
|
+
it "returns a Scope object" do
|
|
44
|
+
result = metrics_scope.least_touched
|
|
45
|
+
|
|
46
|
+
expect(result).to be_a(MudisQL::Scope)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "can be queried with where" do
|
|
50
|
+
result = metrics_scope.least_touched
|
|
51
|
+
.where(access_count: 0)
|
|
52
|
+
.all
|
|
53
|
+
|
|
54
|
+
expect(result).to be_an(Array)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "can be ordered by access count" do
|
|
58
|
+
result = metrics_scope.least_touched
|
|
59
|
+
.order(:access_count)
|
|
60
|
+
.limit(5)
|
|
61
|
+
.all
|
|
62
|
+
|
|
63
|
+
expect(result.size).to be <= 5
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "can pluck just keys" do
|
|
67
|
+
keys = metrics_scope.least_touched
|
|
68
|
+
.where(access_count: 0)
|
|
69
|
+
.pluck(:key)
|
|
70
|
+
|
|
71
|
+
expect(keys).to be_an(Array)
|
|
72
|
+
expect(keys).to all(be_a(String))
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
describe "#buckets" do
|
|
77
|
+
it "returns a Scope object" do
|
|
78
|
+
result = metrics_scope.buckets
|
|
79
|
+
|
|
80
|
+
expect(result).to be_a(MudisQL::Scope)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "can query buckets with conditions" do
|
|
84
|
+
result = metrics_scope.buckets
|
|
85
|
+
.where(keys: ->(k) { k && k > 0 })
|
|
86
|
+
.all
|
|
87
|
+
|
|
88
|
+
expect(result).to be_an(Array)
|
|
89
|
+
result.each do |bucket|
|
|
90
|
+
expect(bucket[:keys]).to be > 0
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "can order buckets by memory" do
|
|
95
|
+
result = metrics_scope.buckets
|
|
96
|
+
.order(:memory_bytes, :desc)
|
|
97
|
+
.all
|
|
98
|
+
|
|
99
|
+
if result.size > 1
|
|
100
|
+
expect(result.first[:memory_bytes]).to be >= result.last[:memory_bytes]
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "can find buckets by index" do
|
|
105
|
+
result = metrics_scope.buckets
|
|
106
|
+
.where(index: 0)
|
|
107
|
+
.first
|
|
108
|
+
|
|
109
|
+
expect(result).to be_a(Hash) if result
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
describe "#total_keys" do
|
|
114
|
+
it "returns the sum of keys across all buckets" do
|
|
115
|
+
total = metrics_scope.total_keys
|
|
116
|
+
|
|
117
|
+
expect(total).to eq(10)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
describe "#total_memory" do
|
|
122
|
+
it "returns total memory usage" do
|
|
123
|
+
memory = metrics_scope.total_memory
|
|
124
|
+
|
|
125
|
+
expect(memory).to be_a(Integer)
|
|
126
|
+
expect(memory).to be > 0
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
describe "#hit_rate" do
|
|
131
|
+
it "calculates hit rate percentage" do
|
|
132
|
+
rate = metrics_scope.hit_rate
|
|
133
|
+
|
|
134
|
+
expect(rate).to be_a(Float)
|
|
135
|
+
expect(rate).to be_between(0, 100)
|
|
136
|
+
# 5 hits, 3 misses = 5/8 = 62.5%
|
|
137
|
+
expect(rate).to eq(62.5)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "returns 0 when no operations" do
|
|
141
|
+
Mudis.reset_metrics!
|
|
142
|
+
rate = metrics_scope.refresh.hit_rate
|
|
143
|
+
|
|
144
|
+
expect(rate).to eq(0.0)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
describe "#efficiency" do
|
|
149
|
+
it "returns efficiency metrics" do
|
|
150
|
+
eff = metrics_scope.efficiency
|
|
151
|
+
|
|
152
|
+
expect(eff).to have_key(:hit_rate)
|
|
153
|
+
expect(eff).to have_key(:miss_rate)
|
|
154
|
+
expect(eff).to have_key(:eviction_rate)
|
|
155
|
+
expect(eff).to have_key(:rejection_rate)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it "has hit_rate and miss_rate that sum to 100" do
|
|
159
|
+
eff = metrics_scope.efficiency
|
|
160
|
+
|
|
161
|
+
expect(eff[:hit_rate] + eff[:miss_rate]).to eq(100.0)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
describe "#high_memory_buckets" do
|
|
166
|
+
it "finds buckets exceeding memory threshold" do
|
|
167
|
+
buckets = metrics_scope.high_memory_buckets(1000)
|
|
168
|
+
|
|
169
|
+
expect(buckets).to be_an(Array)
|
|
170
|
+
buckets.each do |bucket|
|
|
171
|
+
expect(bucket[:memory_bytes]).to be > 1000
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it "returns empty array when threshold too high" do
|
|
176
|
+
buckets = metrics_scope.high_memory_buckets(999_999_999)
|
|
177
|
+
|
|
178
|
+
expect(buckets).to be_empty
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
describe "#high_key_buckets" do
|
|
183
|
+
it "finds buckets with many keys" do
|
|
184
|
+
buckets = metrics_scope.high_key_buckets(0)
|
|
185
|
+
|
|
186
|
+
expect(buckets).to be_an(Array)
|
|
187
|
+
buckets.each do |bucket|
|
|
188
|
+
expect(bucket[:keys]).to be > 0
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
it "filters by threshold" do
|
|
193
|
+
buckets = metrics_scope.high_key_buckets(5)
|
|
194
|
+
|
|
195
|
+
buckets.each do |bucket|
|
|
196
|
+
expect(bucket[:keys]).to be > 5
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
describe "#bucket_distribution" do
|
|
202
|
+
it "returns distribution statistics" do
|
|
203
|
+
dist = metrics_scope.bucket_distribution
|
|
204
|
+
|
|
205
|
+
expect(dist).to have_key(:total_buckets)
|
|
206
|
+
expect(dist).to have_key(:avg_keys_per_bucket)
|
|
207
|
+
expect(dist).to have_key(:max_keys_per_bucket)
|
|
208
|
+
expect(dist).to have_key(:min_keys_per_bucket)
|
|
209
|
+
expect(dist).to have_key(:avg_memory_per_bucket)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
it "calculates correct totals" do
|
|
213
|
+
dist = metrics_scope.bucket_distribution
|
|
214
|
+
|
|
215
|
+
expect(dist[:total_buckets]).to be > 0
|
|
216
|
+
expect(dist[:max_keys_per_bucket]).to be >= dist[:min_keys_per_bucket]
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
describe "#never_accessed_keys" do
|
|
221
|
+
it "returns keys with zero access count" do
|
|
222
|
+
keys = metrics_scope.never_accessed_keys
|
|
223
|
+
|
|
224
|
+
expect(keys).to be_an(Array)
|
|
225
|
+
expect(keys.size).to be > 0
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
it "returns only string keys" do
|
|
229
|
+
keys = metrics_scope.never_accessed_keys
|
|
230
|
+
|
|
231
|
+
expect(keys).to all(be_a(String))
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
describe "#refresh" do
|
|
236
|
+
it "updates metrics data" do
|
|
237
|
+
old_hits = metrics_scope.summary[:hits]
|
|
238
|
+
|
|
239
|
+
# Generate more activity
|
|
240
|
+
Mudis.read("key0", namespace: "test")
|
|
241
|
+
|
|
242
|
+
new_hits = metrics_scope.refresh.summary[:hits]
|
|
243
|
+
|
|
244
|
+
expect(new_hits).to be > old_hits
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
it "returns self for chaining" do
|
|
248
|
+
result = metrics_scope.refresh
|
|
249
|
+
|
|
250
|
+
expect(result).to eq(metrics_scope)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
describe "integration scenarios" do
|
|
255
|
+
it "identifies hotspots - most accessed keys" do
|
|
256
|
+
# Access some keys multiple times
|
|
257
|
+
10.times { Mudis.read("key0", namespace: "test") }
|
|
258
|
+
5.times { Mudis.read("key1", namespace: "test") }
|
|
259
|
+
|
|
260
|
+
most_accessed = MudisQL.metrics.refresh.least_touched
|
|
261
|
+
.order(:access_count, :desc)
|
|
262
|
+
.limit(5)
|
|
263
|
+
.all
|
|
264
|
+
|
|
265
|
+
expect(most_accessed).to be_an(Array)
|
|
266
|
+
if most_accessed.size > 1 && most_accessed.first[:access_count] && most_accessed.last[:access_count]
|
|
267
|
+
expect(most_accessed.first[:access_count]).to be >= most_accessed.last[:access_count]
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
it "finds unbalanced buckets" do
|
|
272
|
+
dist = MudisQL.metrics.bucket_distribution
|
|
273
|
+
avg_keys = dist[:avg_keys_per_bucket]
|
|
274
|
+
|
|
275
|
+
unbalanced = MudisQL.metrics.buckets
|
|
276
|
+
.where(keys: ->(k) { k && k > avg_keys * 1.5 })
|
|
277
|
+
.all
|
|
278
|
+
|
|
279
|
+
expect(unbalanced).to be_an(Array)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
it "monitors cache health" do
|
|
283
|
+
health = {
|
|
284
|
+
hit_rate: MudisQL.metrics.hit_rate,
|
|
285
|
+
total_keys: MudisQL.metrics.total_keys,
|
|
286
|
+
memory: MudisQL.metrics.total_memory,
|
|
287
|
+
efficiency: MudisQL.metrics.efficiency
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
expect(health[:hit_rate]).to be_a(Float)
|
|
291
|
+
expect(health[:total_keys]).to be_a(Integer)
|
|
292
|
+
expect(health[:memory]).to be_a(Integer)
|
|
293
|
+
expect(health[:efficiency]).to be_a(Hash)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
it "analyzes memory distribution" do
|
|
297
|
+
buckets_by_memory = MudisQL.metrics.buckets
|
|
298
|
+
.order(:memory_bytes, :desc)
|
|
299
|
+
.pluck(:index, :memory_bytes)
|
|
300
|
+
|
|
301
|
+
expect(buckets_by_memory).to be_an(Array)
|
|
302
|
+
expect(buckets_by_memory).not_to be_empty
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
describe "edge cases" do
|
|
307
|
+
it "handles empty cache" do
|
|
308
|
+
Mudis.reset!
|
|
309
|
+
Mudis.reset_metrics!
|
|
310
|
+
|
|
311
|
+
metrics = MudisQL.metrics
|
|
312
|
+
|
|
313
|
+
expect(metrics.total_keys).to eq(0)
|
|
314
|
+
expect(metrics.hit_rate).to eq(0.0)
|
|
315
|
+
expect(metrics.never_accessed_keys).to be_an(Array)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
it "handles metrics without least_touched data" do
|
|
319
|
+
allow(Mudis).to receive(:metrics).and_return({
|
|
320
|
+
hits: 10,
|
|
321
|
+
misses: 5,
|
|
322
|
+
buckets: []
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
metrics = MudisQL.metrics
|
|
326
|
+
|
|
327
|
+
expect { metrics.least_touched.all }.not_to raise_error
|
|
328
|
+
expect { metrics.never_accessed_keys }.not_to raise_error
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe "MudisQL Performance Tests" do
|
|
4
|
+
before do
|
|
5
|
+
Mudis.serializer = JSON
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
describe "query optimization" do
|
|
9
|
+
let(:namespace) { "perf_test" }
|
|
10
|
+
|
|
11
|
+
before do
|
|
12
|
+
# Create 1000 records
|
|
13
|
+
1000.times do |i|
|
|
14
|
+
Mudis.write(
|
|
15
|
+
"record_#{i}",
|
|
16
|
+
{
|
|
17
|
+
id: i,
|
|
18
|
+
category: ["A", "B", "C", "D", "E"][i % 5],
|
|
19
|
+
score: rand(1..100),
|
|
20
|
+
active: i.even?,
|
|
21
|
+
name: "Record #{i}"
|
|
22
|
+
},
|
|
23
|
+
namespace: namespace
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "executes complex queries efficiently" do
|
|
29
|
+
start_time = Time.now
|
|
30
|
+
|
|
31
|
+
results = MudisQL.from(namespace)
|
|
32
|
+
.where(category: "A")
|
|
33
|
+
.where(score: ->(s) { s > 50 })
|
|
34
|
+
.where(active: true)
|
|
35
|
+
.order(:score, :desc)
|
|
36
|
+
.limit(20)
|
|
37
|
+
.all
|
|
38
|
+
|
|
39
|
+
duration = Time.now - start_time
|
|
40
|
+
|
|
41
|
+
expect(results.size).to be <= 20
|
|
42
|
+
expect(duration).to be < 1.0 # Should complete in under 1 second
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "handles multiple sequential queries efficiently" do
|
|
46
|
+
start_time = Time.now
|
|
47
|
+
|
|
48
|
+
10.times do
|
|
49
|
+
MudisQL.from(namespace)
|
|
50
|
+
.where(active: true)
|
|
51
|
+
.order(:score)
|
|
52
|
+
.limit(10)
|
|
53
|
+
.all
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
duration = Time.now - start_time
|
|
57
|
+
|
|
58
|
+
expect(duration).to be < 2.0 # 10 queries in under 2 seconds
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "counts large result sets efficiently" do
|
|
62
|
+
start_time = Time.now
|
|
63
|
+
|
|
64
|
+
count = MudisQL.from(namespace)
|
|
65
|
+
.where(active: true)
|
|
66
|
+
.count
|
|
67
|
+
|
|
68
|
+
duration = Time.now - start_time
|
|
69
|
+
|
|
70
|
+
expect(count).to eq(500)
|
|
71
|
+
expect(duration).to be < 0.5
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "handles pluck on large datasets efficiently" do
|
|
75
|
+
start_time = Time.now
|
|
76
|
+
|
|
77
|
+
ids = MudisQL.from(namespace)
|
|
78
|
+
.where(category: "B")
|
|
79
|
+
.pluck(:id)
|
|
80
|
+
|
|
81
|
+
duration = Time.now - start_time
|
|
82
|
+
|
|
83
|
+
expect(ids.size).to eq(200)
|
|
84
|
+
expect(duration).to be < 0.5
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe "memory efficiency" do
|
|
89
|
+
let(:namespace) { "memory_test" }
|
|
90
|
+
|
|
91
|
+
it "handles queries without loading all data unnecessarily" do
|
|
92
|
+
100.times do |i|
|
|
93
|
+
Mudis.write("m#{i}", { value: i }, namespace: namespace)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# first should be more efficient than all.first
|
|
97
|
+
result = MudisQL.from(namespace)
|
|
98
|
+
.where(value: ->(v) { v > 50 })
|
|
99
|
+
.order(:value)
|
|
100
|
+
.first
|
|
101
|
+
|
|
102
|
+
expect(result["value"]).to eq(51)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "handles pagination without memory bloat" do
|
|
106
|
+
500.times do |i|
|
|
107
|
+
Mudis.write("p#{i}", { seq: i }, namespace: namespace)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Simulate pagination through large dataset
|
|
111
|
+
5.times do |page|
|
|
112
|
+
results = MudisQL.from(namespace)
|
|
113
|
+
.order(:seq)
|
|
114
|
+
.limit(10)
|
|
115
|
+
.offset(page * 10)
|
|
116
|
+
.all
|
|
117
|
+
|
|
118
|
+
expect(results.size).to eq(10)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
describe "cache interaction patterns" do
|
|
124
|
+
let(:namespace) { "cache_pattern" }
|
|
125
|
+
|
|
126
|
+
it "efficiently handles repeated queries with cache hits" do
|
|
127
|
+
20.times { |i| Mudis.write("item#{i}", { value: i }, namespace: namespace) }
|
|
128
|
+
|
|
129
|
+
# Warm up
|
|
130
|
+
MudisQL.from(namespace).all
|
|
131
|
+
|
|
132
|
+
start_time = Time.now
|
|
133
|
+
|
|
134
|
+
# Run same query 100 times
|
|
135
|
+
100.times do
|
|
136
|
+
MudisQL.from(namespace)
|
|
137
|
+
.where(value: ->(v) { v < 10 })
|
|
138
|
+
.all
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
duration = Time.now - start_time
|
|
142
|
+
|
|
143
|
+
# Should benefit from cache, complete quickly
|
|
144
|
+
expect(duration).to be < 2.0
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it "handles rapid creation and querying" do
|
|
148
|
+
start_time = Time.now
|
|
149
|
+
|
|
150
|
+
100.times do |i|
|
|
151
|
+
Mudis.write("rapid#{i}", { num: i }, namespace: namespace)
|
|
152
|
+
|
|
153
|
+
# Query immediately after write
|
|
154
|
+
result = MudisQL.from(namespace)
|
|
155
|
+
.where(num: i)
|
|
156
|
+
.first
|
|
157
|
+
|
|
158
|
+
expect(result).not_to be_nil
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
duration = Time.now - start_time
|
|
162
|
+
|
|
163
|
+
expect(duration).to be < 3.0
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
describe "query complexity scaling" do
|
|
168
|
+
let(:namespace) { "scaling" }
|
|
169
|
+
|
|
170
|
+
before do
|
|
171
|
+
200.times do |i|
|
|
172
|
+
Mudis.write(
|
|
173
|
+
"s#{i}",
|
|
174
|
+
{
|
|
175
|
+
a: rand(100),
|
|
176
|
+
b: rand(100),
|
|
177
|
+
c: rand(100),
|
|
178
|
+
d: ["x", "y", "z"].sample,
|
|
179
|
+
e: i.even?
|
|
180
|
+
},
|
|
181
|
+
namespace: namespace
|
|
182
|
+
)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it "handles queries with multiple conditions" do
|
|
187
|
+
start_time = Time.now
|
|
188
|
+
|
|
189
|
+
results = MudisQL.from(namespace)
|
|
190
|
+
.where(a: ->(v) { v > 25 })
|
|
191
|
+
.where(b: ->(v) { v < 75 })
|
|
192
|
+
.where(c: 40..60)
|
|
193
|
+
.where(d: "x")
|
|
194
|
+
.where(e: true)
|
|
195
|
+
.all
|
|
196
|
+
|
|
197
|
+
duration = Time.now - start_time
|
|
198
|
+
|
|
199
|
+
expect(duration).to be < 0.5
|
|
200
|
+
expect(results).to be_an(Array)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it "handles complex ordering scenarios" do
|
|
204
|
+
start_time = Time.now
|
|
205
|
+
|
|
206
|
+
results = MudisQL.from(namespace)
|
|
207
|
+
.where(a: ->(v) { v > 50 })
|
|
208
|
+
.order(:b, :desc)
|
|
209
|
+
.limit(50)
|
|
210
|
+
.all
|
|
211
|
+
|
|
212
|
+
duration = Time.now - start_time
|
|
213
|
+
|
|
214
|
+
expect(duration).to be < 0.5
|
|
215
|
+
expect(results.size).to be <= 50
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
describe "real-world simulation" do
|
|
220
|
+
it "simulates an e-commerce search with filters" do
|
|
221
|
+
# Setup product catalog
|
|
222
|
+
500.times do |i|
|
|
223
|
+
Mudis.write(
|
|
224
|
+
"product_#{i}",
|
|
225
|
+
{
|
|
226
|
+
name: "Product #{i}",
|
|
227
|
+
price: rand(10..1000),
|
|
228
|
+
category: ["Electronics", "Clothing", "Home", "Sports"][i % 4],
|
|
229
|
+
rating: rand(1.0..5.0).round(1),
|
|
230
|
+
in_stock: [true, false].sample,
|
|
231
|
+
brand: ["BrandA", "BrandB", "BrandC"][i % 3]
|
|
232
|
+
},
|
|
233
|
+
namespace: "products"
|
|
234
|
+
)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Simulate user search
|
|
238
|
+
start_time = Time.now
|
|
239
|
+
|
|
240
|
+
results = MudisQL.from("products")
|
|
241
|
+
.where(category: "Electronics")
|
|
242
|
+
.where(price: 100..500)
|
|
243
|
+
.where(rating: ->(r) { r >= 4.0 })
|
|
244
|
+
.where(in_stock: true)
|
|
245
|
+
.order(:price, :asc)
|
|
246
|
+
.limit(25)
|
|
247
|
+
.all
|
|
248
|
+
|
|
249
|
+
duration = Time.now - start_time
|
|
250
|
+
|
|
251
|
+
expect(results.size).to be <= 25
|
|
252
|
+
expect(duration).to be < 0.8
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
it "simulates user analytics dashboard" do
|
|
256
|
+
# Setup user data
|
|
257
|
+
300.times do |i|
|
|
258
|
+
Mudis.write(
|
|
259
|
+
"user_#{i}",
|
|
260
|
+
{
|
|
261
|
+
id: i,
|
|
262
|
+
signup_date: "2025-#{rand(1..12).to_s.rjust(2, '0')}-01",
|
|
263
|
+
lifetime_value: rand(0..5000),
|
|
264
|
+
orders: rand(0..50),
|
|
265
|
+
status: ["active", "inactive", "suspended"][i % 3]
|
|
266
|
+
},
|
|
267
|
+
namespace: "users"
|
|
268
|
+
)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
start_time = Time.now
|
|
272
|
+
|
|
273
|
+
# Dashboard queries
|
|
274
|
+
active_users = MudisQL.from("users").where(status: "active").count
|
|
275
|
+
high_value = MudisQL.from("users")
|
|
276
|
+
.where(lifetime_value: ->(v) { v > 1000 })
|
|
277
|
+
.where(status: "active")
|
|
278
|
+
.count
|
|
279
|
+
|
|
280
|
+
top_customers = MudisQL.from("users")
|
|
281
|
+
.where(status: "active")
|
|
282
|
+
.order(:lifetime_value, :desc)
|
|
283
|
+
.limit(10)
|
|
284
|
+
.pluck(:id, :lifetime_value)
|
|
285
|
+
|
|
286
|
+
duration = Time.now - start_time
|
|
287
|
+
|
|
288
|
+
expect(active_users).to be > 0
|
|
289
|
+
expect(high_value).to be >= 0
|
|
290
|
+
expect(top_customers.size).to eq(10)
|
|
291
|
+
expect(duration).to be < 1.0
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
|