mudis 0.7.3 → 0.8.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 +148 -79
- data/lib/mudis/version.rb +1 -1
- data/lib/mudis.rb +110 -0
- data/lib/mudis_config.rb +11 -1
- data/lib/mudis_proxy.rb +2 -2
- data/spec/persistence_spec.rb +37 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ae292b0f546008c9be0a96ef33f4e6a2f8c3a79fdd5545da375eafd435d9f36b
|
|
4
|
+
data.tar.gz: eee866d44d874439535fab0778e41a17f6bb10b2043dfec8ee7493c083811a75
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d24edc845960b0c56b9bdf286d51863c919960d8270fabe9acb6729ad1faa173a9dd40b810e89e766360fe383787ebc89e03c8a84234b65d2202a44705e80cfb
|
|
7
|
+
data.tar.gz: f260429d8a394eefdc0a2d637c646b175ce22b48ed1a9fdf04e225d908022656d985ce67041d8762a0cadf95484b4b29e63114b4ae55227f0060c60be97347ad
|
data/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|

|
|
2
2
|
|
|
3
|
+
[](https://www.jetbrains.com/ruby/)
|
|
4
|
+
|
|
3
5
|
[](https://badge.fury.io/rb/mudis)
|
|
4
6
|
[](LICENSE)
|
|
5
7
|
|
|
@@ -36,12 +38,13 @@ Mudis also works naturally in Hanami because it’s a pure Ruby in-memory cache.
|
|
|
36
38
|
- [Rails Service Integration](#rails-service-integration)
|
|
37
39
|
- [Metrics](#metrics)
|
|
38
40
|
- [Advanced Configuration](#advanced-configuration)
|
|
39
|
-
- [
|
|
40
|
-
- [Graceful Shutdown](#graceful-shutdown)
|
|
41
|
-
- [Known Limitations](#known-limitations)
|
|
41
|
+
- [Soft Persistence (Snapshots)](#soft-persistence-snapshots)
|
|
42
42
|
- [Inter-Process Caching (IPC Mode)](#inter-process-caching-ipc-mode)
|
|
43
43
|
- [Overview](#overview)
|
|
44
44
|
- [Setup (Puma)](#setup-puma)
|
|
45
|
+
- [Benchmarks](#benchmarks)
|
|
46
|
+
- [Graceful Shutdown](#graceful-shutdown)
|
|
47
|
+
- [Known Limitations](#known-limitations)
|
|
45
48
|
- [Create a Mudis Web Cache Server](#create-a-mudis-web-cache-server)
|
|
46
49
|
- [Minimal Setup](#minimal-setup)
|
|
47
50
|
- [Project Philosophy](#project-philosophy)
|
|
@@ -528,100 +531,69 @@ When setting `serializer`, be mindful of the below
|
|
|
528
531
|
|
|
529
532
|
---
|
|
530
533
|
|
|
531
|
-
##
|
|
532
|
-
|
|
533
|
-
#### Serializer(s)
|
|
534
|
-
|
|
535
|
-
_100000 iterations_
|
|
536
|
-
|
|
537
|
-
| Serializer | Total Time (s) | Ops/sec |
|
|
538
|
-
|----------------|------------|----------------|
|
|
539
|
-
| oj | 0.1342 | 745320 |
|
|
540
|
-
| marshal | 0.3228 | 309824 |
|
|
541
|
-
| json | 0.9035 | 110682 |
|
|
542
|
-
| oj + zlib | 1.8050 | 55401 |
|
|
543
|
-
| marshal + zlib | 1.8057 | 55381 |
|
|
544
|
-
| json + zlib | 2.7949 | 35780 |
|
|
545
|
-
|
|
546
|
-
> If opting for OJ, you will need to install the dependency in your project and configure as needed.
|
|
547
|
-
|
|
548
|
-
#### Mudis vs Rails.cache
|
|
549
|
-
|
|
550
|
-
Mudis is marginally slower than `Rails.cache` by design; it trades raw speed for control, observability, and safety.
|
|
551
|
-
|
|
552
|
-
_10000 iterations of 1MB, Marshal (to match MemoryStore default), compression ON_
|
|
553
|
-
|
|
554
|
-
| Operation | `Rails.cache` | `Mudis` | Delta |
|
|
555
|
-
| --------- | ------------- | ----------- | --------- |
|
|
556
|
-
| Write | 2.139 ms/op | 2.417 ms/op | +0.278 ms |
|
|
557
|
-
| Read | 0.007 ms/op | 0.810 ms/op | +0.803 ms |
|
|
534
|
+
## Soft Persistence (Snapshots)
|
|
558
535
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
#### **Why this overhead exists**
|
|
562
|
-
|
|
563
|
-
Mudis includes features that MemoryStore doesn’t:
|
|
564
|
-
|
|
565
|
-
| Feature | Mudis | Rails.cache (MemoryStore) |
|
|
566
|
-
| ------------------ | ---------------------- | --------------------------- |
|
|
567
|
-
| Per-key TTL expiry | ✅ | ⚠️ on access |
|
|
568
|
-
| True LRU eviction | ✅ | ❌ |
|
|
569
|
-
| Hard memory limits | ✅ | ❌ |
|
|
570
|
-
| Value compression | ✅ | ❌ |
|
|
571
|
-
| Thread safety | ✅ Bucket-level mutexes | ✅ Global mutex |
|
|
572
|
-
| Observability | ✅ | ❌ |
|
|
573
|
-
| Namespacing | ✅ | ❌ Manual scoping |
|
|
574
|
-
| IPC Aware | ✅ (if enabled) | ❌ Requires manual configuration and additional gems |
|
|
536
|
+
Mudis can optionally soft-persist its in-memory cache to disk between process restarts.
|
|
537
|
+
When enabled, it will automatically:
|
|
575
538
|
|
|
576
|
-
|
|
539
|
+
- **Save** a snapshot of the current cache at shutdown
|
|
540
|
+
- **Reload** it on startup
|
|
577
541
|
|
|
578
|
-
|
|
579
|
-
- Configurable expiry
|
|
580
|
-
- Memory protection
|
|
581
|
-
- Namespace scoping
|
|
582
|
-
- Real-time metrics for hits, misses, evictions, memory usage
|
|
542
|
+
This feature is **off by default** and can be enabled via configuration.
|
|
583
543
|
|
|
584
|
-
|
|
544
|
+
Example configuration:
|
|
585
545
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
546
|
+
```ruby
|
|
547
|
+
Mudis.configure do |config|
|
|
548
|
+
config.persistence_enabled = true
|
|
549
|
+
config.persistence_path = "tmp/mudis_snapshot.dump"
|
|
550
|
+
config.persistence_format = :marshal # or :json
|
|
551
|
+
config.persistence_safe_write = true # atomic temp write + rename
|
|
552
|
+
end
|
|
590
553
|
|
|
591
|
-
|
|
554
|
+
## Optionally install the exit hook manually (usually automatic)
|
|
555
|
+
# Mudis.install_persistence_hook!
|
|
556
|
+
```
|
|
592
557
|
|
|
593
|
-
|
|
558
|
+
From your startup routine:
|
|
594
559
|
|
|
595
|
-
|
|
560
|
+
```ruby
|
|
561
|
+
# Restore snapshot on startup
|
|
562
|
+
Mudis.load_snapshot!
|
|
563
|
+
```
|
|
596
564
|
|
|
597
|
-
|
|
598
|
-
| --------- | ------------- | ----------- | ------------- |
|
|
599
|
-
| Write | 1.291 ms/op | 0.32 ms/op | **−0.971 ms** |
|
|
600
|
-
| Read | 0.011 ms/op | 0.048 ms/op | +0.037 ms |
|
|
565
|
+
*This feature works identically in Rails, Hanami, and standalone Ruby scripts, as long as `Mudis.configure` is called prior to `Mudis.load_snapshot!`.*
|
|
601
566
|
|
|
602
|
-
|
|
567
|
+
### Behavior
|
|
603
568
|
|
|
604
|
-
| Operation |
|
|
605
|
-
|
|
606
|
-
|
|
|
607
|
-
|
|
|
569
|
+
| Operation | Description |
|
|
570
|
+
|------------|-------------|
|
|
571
|
+
| `save_snapshot!` | Dumps all non-expired cache entries to disk. |
|
|
572
|
+
| `load_snapshot!` | Reads snapshot and restores entries via normal write path. |
|
|
573
|
+
| `install_persistence_hook!` | Registers an `at_exit` hook to automatically save on process exit. |
|
|
608
574
|
|
|
609
|
-
|
|
575
|
+
#### Notes
|
|
610
576
|
|
|
611
|
-
|
|
577
|
+
- Disabled by default for backward compatibility.
|
|
578
|
+
- Uses Ruby’s `Marshal` by default for fastest serialization; `:json` also supported.
|
|
579
|
+
- Respects TTLs: expired entries are never written to disk.
|
|
580
|
+
- Thread-safe: snapshots are taken under shard-level locks.
|
|
581
|
+
- Safe write: when `persistence_safe_write = true`, Mudis writes to a temp file and renames it atomically.
|
|
612
582
|
|
|
613
|
-
|
|
583
|
+
#### Example Flow
|
|
614
584
|
|
|
615
|
-
|
|
616
|
-
at_exit { Mudis.stop_expiry_thread }
|
|
617
|
-
```
|
|
585
|
+

|
|
618
586
|
|
|
619
|
-
|
|
587
|
+
1. On startup, `Mudis.load_snapshot!` repopulates the cache.
|
|
588
|
+
2. Your app uses the cache as normal (`write`, `read`, `fetch`, etc.).
|
|
589
|
+
3. When the process exits, `at_exit` automatically triggers `save_snapshot!`.
|
|
590
|
+
4. A file (e.g., `tmp/mudis_snapshot.dump`) holds your persisted cache.
|
|
620
591
|
|
|
621
|
-
|
|
592
|
+
#### Safety
|
|
622
593
|
|
|
623
|
-
|
|
624
|
-
-
|
|
594
|
+
If the snapshot file is **missing** or **corrupted**, Mudis will:
|
|
595
|
+
- Log a warning via `warn "[Mudis] Failed to load snapshot..."`, and
|
|
596
|
+
- Continue booting normally with an empty cache.
|
|
625
597
|
|
|
626
598
|
---
|
|
627
599
|
|
|
@@ -712,6 +684,103 @@ end
|
|
|
712
684
|
|
|
713
685
|
---
|
|
714
686
|
|
|
687
|
+
## Benchmarks
|
|
688
|
+
|
|
689
|
+
#### Serializer(s)
|
|
690
|
+
|
|
691
|
+
_100000 iterations_
|
|
692
|
+
|
|
693
|
+
| Serializer | Total Time (s) | Ops/sec |
|
|
694
|
+
|----------------|------------|----------------|
|
|
695
|
+
| oj | 0.1342 | 745320 |
|
|
696
|
+
| marshal | 0.3228 | 309824 |
|
|
697
|
+
| json | 0.9035 | 110682 |
|
|
698
|
+
| oj + zlib | 1.8050 | 55401 |
|
|
699
|
+
| marshal + zlib | 1.8057 | 55381 |
|
|
700
|
+
| json + zlib | 2.7949 | 35780 |
|
|
701
|
+
|
|
702
|
+
> If opting for OJ, you will need to install the dependency in your project and configure as needed.
|
|
703
|
+
|
|
704
|
+
#### Mudis vs Rails.cache
|
|
705
|
+
|
|
706
|
+
Mudis is marginally slower than `Rails.cache` by design; it trades raw speed for control, observability, and safety.
|
|
707
|
+
|
|
708
|
+
_10000 iterations of 1MB, Marshal (to match MemoryStore default), compression ON_
|
|
709
|
+
|
|
710
|
+
| Operation | `Rails.cache` | `Mudis` | Delta |
|
|
711
|
+
| --------- | ------------- | ----------- | --------- |
|
|
712
|
+
| Write | 2.139 ms/op | 2.417 ms/op | +0.278 ms |
|
|
713
|
+
| Read | 0.007 ms/op | 0.810 ms/op | +0.803 ms |
|
|
714
|
+
|
|
715
|
+
> For context: a typical database query or HTTP call takes 10–50ms. A difference of less than 1ms per operation is negligible for most apps.
|
|
716
|
+
|
|
717
|
+
#### **Why this overhead exists**
|
|
718
|
+
|
|
719
|
+
Mudis includes features that MemoryStore doesn’t:
|
|
720
|
+
|
|
721
|
+
| Feature | Mudis | Rails.cache (MemoryStore) |
|
|
722
|
+
| ------------------ | ---------------------- | --------------------------- |
|
|
723
|
+
| Per-key TTL expiry | ✅ | ⚠️ on access |
|
|
724
|
+
| True LRU eviction | ✅ | ❌ |
|
|
725
|
+
| Hard memory limits | ✅ | ❌ |
|
|
726
|
+
| Value compression | ✅ | ❌ |
|
|
727
|
+
| Thread safety | ✅ Bucket-level mutexes | ✅ Global mutex |
|
|
728
|
+
| Observability | ✅ | ❌ |
|
|
729
|
+
| Namespacing | ✅ | ❌ Manual scoping |
|
|
730
|
+
| IPC Aware | ✅ (if enabled) | ❌ Requires manual configuration and additional gems |
|
|
731
|
+
|
|
732
|
+
It will be down to the developer to decide if a fraction of a millisecond is worth
|
|
733
|
+
|
|
734
|
+
- Predictable eviction
|
|
735
|
+
- Configurable expiry
|
|
736
|
+
- Memory protection
|
|
737
|
+
- Namespace scoping
|
|
738
|
+
- Real-time metrics for hits, misses, evictions, memory usage
|
|
739
|
+
|
|
740
|
+
_10000 iterations of 1MB, Marshal (to match MemoryStore default), compression OFF (to match MemoryStore default)_
|
|
741
|
+
|
|
742
|
+
| Operation | `Rails.cache` | `Mudis` | Delta |
|
|
743
|
+
| --------- | ------------- | ----------- | ------------- |
|
|
744
|
+
| Write | 2.342 ms/op | 0.501 ms/op | **−1.841 ms** |
|
|
745
|
+
| Read | 0.007 ms/op | 0.011 ms/op | +0.004 ms |
|
|
746
|
+
|
|
747
|
+
With compression disabled, Mudis writes significanty faster and reads are virtually identical. Optimisation and configuration of Mudis will be determined by your individual needs.
|
|
748
|
+
|
|
749
|
+
#### Other Benchmarks
|
|
750
|
+
|
|
751
|
+
_10000 iterations of 512KB, JSON, compression OFF (to match MemoryStore default)_
|
|
752
|
+
|
|
753
|
+
| Operation | `Rails.cache` | `Mudis` | Delta |
|
|
754
|
+
| --------- | ------------- | ----------- | ------------- |
|
|
755
|
+
| Write | 1.291 ms/op | 0.32 ms/op | **−0.971 ms** |
|
|
756
|
+
| Read | 0.011 ms/op | 0.048 ms/op | +0.037 ms |
|
|
757
|
+
|
|
758
|
+
_10000 iterations of 512KB, JSON, compression ON_
|
|
759
|
+
|
|
760
|
+
| Operation | `Rails.cache` | `Mudis` | Delta |
|
|
761
|
+
| --------- | ------------- | ----------- | ------------- |
|
|
762
|
+
| Write | 1.11 ms/op | 1.16 ms/op | +0.05 ms |
|
|
763
|
+
| Read | 0.07 ms/op | 0.563 ms/op | +0.493 ms |
|
|
764
|
+
|
|
765
|
+
---
|
|
766
|
+
|
|
767
|
+
## Graceful Shutdown
|
|
768
|
+
|
|
769
|
+
Don’t forget to stop the expiry thread when your app exits:
|
|
770
|
+
|
|
771
|
+
```ruby
|
|
772
|
+
at_exit { Mudis.stop_expiry_thread }
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
---
|
|
776
|
+
|
|
777
|
+
## Known Limitations
|
|
778
|
+
|
|
779
|
+
- Data is **non-persistent**; only soft-persistence is optionally provided.
|
|
780
|
+
- Compression introduces CPU overhead.
|
|
781
|
+
|
|
782
|
+
---
|
|
783
|
+
|
|
715
784
|
## Create a Mudis Web Cache Server
|
|
716
785
|
|
|
717
786
|
### Minimal Setup
|
data/lib/mudis/version.rb
CHANGED
data/lib/mudis.rb
CHANGED
|
@@ -47,6 +47,11 @@ class Mudis # rubocop:disable Metrics/ClassLength
|
|
|
47
47
|
self.max_ttl = config.max_ttl
|
|
48
48
|
self.default_ttl = config.default_ttl
|
|
49
49
|
|
|
50
|
+
@persistence_enabled = config.persistence_enabled
|
|
51
|
+
@persistence_path = config.persistence_path
|
|
52
|
+
@persistence_format = config.persistence_format
|
|
53
|
+
@persistence_safe_write = config.persistence_safe_write
|
|
54
|
+
|
|
50
55
|
if config.buckets # rubocop:disable Style/GuardClause
|
|
51
56
|
@buckets = config.buckets
|
|
52
57
|
reset!
|
|
@@ -518,4 +523,109 @@ class Mudis # rubocop:disable Metrics/ClassLength
|
|
|
518
523
|
[ttl, @max_ttl].min
|
|
519
524
|
end
|
|
520
525
|
end
|
|
526
|
+
|
|
527
|
+
class << self
|
|
528
|
+
|
|
529
|
+
# Saves the current cache state to disk for persistence
|
|
530
|
+
def save_snapshot!
|
|
531
|
+
return unless @persistence_enabled
|
|
532
|
+
data = snapshot_dump
|
|
533
|
+
safe_write_snapshot(data)
|
|
534
|
+
rescue => e
|
|
535
|
+
warn "[Mudis] Failed to save snapshot: #{e.class}: #{e.message}"
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
# Loads the cache state from disk for persistence
|
|
539
|
+
def load_snapshot!
|
|
540
|
+
return unless @persistence_enabled
|
|
541
|
+
return unless File.exist?(@persistence_path)
|
|
542
|
+
data = read_snapshot
|
|
543
|
+
snapshot_restore(data)
|
|
544
|
+
rescue => e
|
|
545
|
+
warn "[Mudis] Failed to load snapshot: #{e.class}: #{e.message}"
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
# Installs an at_exit hook to save the snapshot on process exit
|
|
549
|
+
def install_persistence_hook!
|
|
550
|
+
return unless @persistence_enabled
|
|
551
|
+
return if defined?(@persistence_hook_installed) && @persistence_hook_installed
|
|
552
|
+
at_exit { save_snapshot! }
|
|
553
|
+
@persistence_hook_installed = true
|
|
554
|
+
end
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
class << self
|
|
558
|
+
private
|
|
559
|
+
# Collect a JSON/Marshal-safe array of { key, value, expires_in }
|
|
560
|
+
def snapshot_dump
|
|
561
|
+
entries = []
|
|
562
|
+
now = Time.now
|
|
563
|
+
@buckets.times do |idx|
|
|
564
|
+
mutex = @mutexes[idx]
|
|
565
|
+
store = @stores[idx]
|
|
566
|
+
mutex.synchronize do
|
|
567
|
+
store.each do |key, raw|
|
|
568
|
+
exp_at = raw[:expires_at]
|
|
569
|
+
next if exp_at && now > exp_at
|
|
570
|
+
value = decompress_and_deserialize(raw[:value])
|
|
571
|
+
expires_in = exp_at ? (exp_at - now).to_i : nil
|
|
572
|
+
entries << { key: key, value: value, expires_in: expires_in }
|
|
573
|
+
end
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
entries
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
# Restore via existing write-path so LRU/limits/compression/TTL are honored
|
|
580
|
+
def snapshot_restore(entries)
|
|
581
|
+
return unless entries && !entries.empty?
|
|
582
|
+
entries.each do |e|
|
|
583
|
+
begin
|
|
584
|
+
write(e[:key], e[:value], expires_in: e[:expires_in])
|
|
585
|
+
rescue => ex
|
|
586
|
+
warn "[Mudis] Failed to restore key #{e[:key].inspect}: #{ex.message}"
|
|
587
|
+
end
|
|
588
|
+
end
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
# Serializer for snapshot persistence
|
|
592
|
+
# Defaults to Marshal if not JSON
|
|
593
|
+
def serializer_for_snapshot
|
|
594
|
+
(@persistence_format || :marshal).to_sym == :json ? JSON : :marshal
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
# Safely writes snapshot data to disk
|
|
598
|
+
# Uses safe write if configured
|
|
599
|
+
def safe_write_snapshot(data)
|
|
600
|
+
path = @persistence_path
|
|
601
|
+
dir = File.dirname(path)
|
|
602
|
+
Dir.mkdir(dir) unless Dir.exist?(dir)
|
|
603
|
+
|
|
604
|
+
payload =
|
|
605
|
+
if (@persistence_format || :marshal).to_sym == :json
|
|
606
|
+
serializer_for_snapshot.dump(data)
|
|
607
|
+
else
|
|
608
|
+
Marshal.dump(data)
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
if @persistence_safe_write
|
|
612
|
+
tmp = "#{path}.tmp-#{$$}-#{Thread.current.object_id}"
|
|
613
|
+
File.open(tmp, "wb") { |f| f.write(payload) }
|
|
614
|
+
File.rename(tmp, path)
|
|
615
|
+
else
|
|
616
|
+
File.open(path, "wb") { |f| f.write(payload) }
|
|
617
|
+
end
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
# Reads snapshot data from disk
|
|
621
|
+
# Uses safe read if configured
|
|
622
|
+
def read_snapshot
|
|
623
|
+
if (@persistence_format || :marshal).to_sym == :json
|
|
624
|
+
serializer_for_snapshot.load(File.binread(@persistence_path))
|
|
625
|
+
else
|
|
626
|
+
Marshal.load(File.binread(@persistence_path))
|
|
627
|
+
end
|
|
628
|
+
end
|
|
629
|
+
end
|
|
630
|
+
|
|
521
631
|
end
|
data/lib/mudis_config.rb
CHANGED
|
@@ -10,7 +10,12 @@ class MudisConfig
|
|
|
10
10
|
:max_bytes,
|
|
11
11
|
:buckets,
|
|
12
12
|
:max_ttl,
|
|
13
|
-
:default_ttl
|
|
13
|
+
:default_ttl,
|
|
14
|
+
# Persistence settings
|
|
15
|
+
:persistence_enabled,
|
|
16
|
+
:persistence_path,
|
|
17
|
+
:persistence_format,
|
|
18
|
+
:persistence_safe_write
|
|
14
19
|
|
|
15
20
|
def initialize
|
|
16
21
|
@serializer = JSON # Default serialization strategy
|
|
@@ -21,5 +26,10 @@ class MudisConfig
|
|
|
21
26
|
@buckets = nil # use nil to signal fallback to ENV or default
|
|
22
27
|
@max_ttl = nil # Max TTL for cache entries (optional)
|
|
23
28
|
@default_ttl = nil # Default TTL for cache entries (optional)
|
|
29
|
+
# Persistence settings
|
|
30
|
+
@persistence_enabled = false # Whether persistence is enabled
|
|
31
|
+
@persistence_path = 'mudis_data' # Default path for persistence files
|
|
32
|
+
@persistence_format = :json # Default persistence file format
|
|
33
|
+
@persistence_safe_write = true # Whether to use safe write for persistence
|
|
24
34
|
end
|
|
25
35
|
end
|
data/lib/mudis_proxy.rb
CHANGED
|
@@ -19,7 +19,7 @@ unless defined?(MudisClient)
|
|
|
19
19
|
return
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
unless defined?($mudis) && $mudis
|
|
22
|
+
unless defined?($mudis) && $mudis # rubocop:disable Style/GlobalVars
|
|
23
23
|
warn "[MudisProxy] $mudis not set: proxy not activated"
|
|
24
24
|
return
|
|
25
25
|
end
|
|
@@ -34,4 +34,4 @@ class << Mudis
|
|
|
34
34
|
def reset! = $mudis.reset! # rubocop:disable Style/GlobalVars
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
warn "[MudisProxy] Proxy activated: forwarding calls to $mudis"
|
|
37
|
+
warn "[MudisProxy] Proxy activated: forwarding calls to $mudis"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
RSpec.describe "Mudis soft persistence" do
|
|
6
|
+
let(:path) { "tmp/test_mudis_snapshot.dump" }
|
|
7
|
+
|
|
8
|
+
before do
|
|
9
|
+
FileUtils.rm_f(path)
|
|
10
|
+
Mudis.reset!
|
|
11
|
+
Mudis.configure do |c|
|
|
12
|
+
c.persistence_enabled = true
|
|
13
|
+
c.persistence_path = path
|
|
14
|
+
c.persistence_format = :marshal
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "saves on exit and loads on next boot" do
|
|
19
|
+
Mudis.write("k1", "v1", expires_in: 60)
|
|
20
|
+
# simulate shutdown
|
|
21
|
+
Mudis.save_snapshot!
|
|
22
|
+
Mudis.reset!
|
|
23
|
+
|
|
24
|
+
# simulate fresh boot (apply config + load)
|
|
25
|
+
Mudis.apply_config! # if not public, re-configure to trigger load
|
|
26
|
+
Mudis.load_snapshot!
|
|
27
|
+
|
|
28
|
+
expect(Mudis.read("k1")).to eq("v1")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "does nothing when file missing" do
|
|
32
|
+
Mudis.reset!
|
|
33
|
+
Mudis.configure { |c| c.persistence_enabled = true; c.persistence_path = path }
|
|
34
|
+
Mudis.load_snapshot! # no file
|
|
35
|
+
expect(Mudis.read("any")).to be_nil
|
|
36
|
+
end
|
|
37
|
+
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mudis
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kiebor81
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2025-10-
|
|
10
|
+
date: 2025-10-25 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: climate_control
|
|
@@ -67,6 +67,7 @@ files:
|
|
|
67
67
|
- spec/mudis_server_spec.rb
|
|
68
68
|
- spec/mudis_spec.rb
|
|
69
69
|
- spec/namespace_spec.rb
|
|
70
|
+
- spec/persistence_spec.rb
|
|
70
71
|
- spec/reset_spec.rb
|
|
71
72
|
homepage: https://github.com/kiebor81/mudis
|
|
72
73
|
licenses:
|
|
@@ -99,4 +100,5 @@ test_files:
|
|
|
99
100
|
- spec/mudis_server_spec.rb
|
|
100
101
|
- spec/mudis_spec.rb
|
|
101
102
|
- spec/namespace_spec.rb
|
|
103
|
+
- spec/persistence_spec.rb
|
|
102
104
|
- spec/reset_spec.rb
|