Class: SafeMemoize::Stores::Multilevel

Inherits:
Base
  • Object
show all
Defined in:
lib/safe_memoize/stores/multilevel.rb

Overview

Multi-level (L1/L2/…) cache store that checks faster layers first and promotes values up on a miss, reducing latency and load on slower backends.

Reads walk the store list from first (fastest) to last (slowest). On a miss at level N the value is read from level N+1 and written back into all preceding levels ("read-through promotion"). Writes always go to every level so all layers stay consistent.

Examples:

In-process L1 + Redis L2

l1 = SafeMemoize::Stores::Memory.new
l2 = MyRedisStore.new

memoize :fetch, store: SafeMemoize::Stores::Multilevel.new(l1, l2)

Via the store: Array shorthand

memoize :fetch, store: [l1, l2], ttl: 300

With a short promote_expires_in for the L1 layer

memoize :fetch, store: SafeMemoize::Stores::Multilevel.new(l1, l2, promote_expires_in: 60)

Constant Summary

Constants inherited from Base

Base::MISS

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#exist?

Constructor Details

#initialize(*stores, promote_expires_in: nil) ⇒ Multilevel

Returns a new instance of Multilevel.

Parameters:

  • stores (Array<Stores::Base>)

    two or more store instances, ordered from fastest (L1) to slowest (last)

  • promote_expires_in (Numeric, nil) (defaults to: nil)

    TTL applied when promoting a value from a deeper layer into a shallower one; +nil+ means no expiry on the promoted entry (the L1 store's own eviction — e.g. LRU — handles memory bounds instead)

Raises:

  • (ArgumentError)

    if fewer than two stores are supplied, or any element is not a Base instance



36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/safe_memoize/stores/multilevel.rb', line 36

def initialize(*stores, promote_expires_in: nil)
  raise ArgumentError, "Multilevel requires at least 2 stores" if stores.size < 2

  stores.each_with_index do |s, i|
    unless s.is_a?(Base)
      raise ArgumentError,
        "Multilevel store[#{i}] must be a Stores::Base instance (got #{s.class})"
    end
  end

  @stores = stores.freeze
  @promote_expires_in = promote_expires_in ? Float(promote_expires_in) : nil
end

Instance Attribute Details

#storesArray<Stores::Base> (readonly)

Returns the ordered store layers (fastest first).

Returns:

  • (Array<Stores::Base>)

    the ordered store layers (fastest first)



26
27
28
# File 'lib/safe_memoize/stores/multilevel.rb', line 26

def stores
  @stores
end

Instance Method Details

#clearObject

Clear every level.



76
77
78
# File 'lib/safe_memoize/stores/multilevel.rb', line 76

def clear
  @stores.each(&:clear)
end

#delete(key) ⇒ Object

Delete from every level.



71
72
73
# File 'lib/safe_memoize/stores/multilevel.rb', line 71

def delete(key)
  @stores.each { |s| s.delete(key) }
end

#keysObject

Union of live keys across all levels.



81
82
83
# File 'lib/safe_memoize/stores/multilevel.rb', line 81

def keys
  @stores.flat_map(&:keys).uniq
end

#read(key) ⇒ Object

Walk levels from fastest to slowest; return the first hit, promoting the value into all shallower layers.



52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/safe_memoize/stores/multilevel.rb', line 52

def read(key)
  @stores.each_with_index do |store, i|
    result = store.read(key)
    next if result.equal?(MISS)

    # Promote into every shallower level
    @stores.first(i).each { |s| s.write(key, result, expires_in: @promote_expires_in) }
    return result
  end

  MISS
end

#write(key, value, expires_in: nil) ⇒ Object

Write to every level simultaneously.



66
67
68
# File 'lib/safe_memoize/stores/multilevel.rb', line 66

def write(key, value, expires_in: nil)
  @stores.each { |s| s.write(key, value, expires_in: expires_in) }
end