Class: Philiprehberger::CacheKit::Store

Inherits:
Object
  • Object
show all
Includes:
Batch, Callbacks, Eviction, Serializable, TagStats
Defined in:
lib/philiprehberger/cache_kit/store.rb

Overview

Thread-safe in-memory LRU cache with TTL and tag-based invalidation.

Instance Method Summary collapse

Methods included from Serializable

#restore, #snapshot

Methods included from Batch

#get_many

Methods included from Callbacks

#on_evict

Constructor Details

#initialize(max_size: 1000) ⇒ Store

Returns a new instance of Store.

Parameters:

  • max_size (Integer) (defaults to: 1000)

    maximum number of entries (LRU eviction when exceeded)



14
15
16
17
18
19
20
21
22
23
24
# File 'lib/philiprehberger/cache_kit/store.rb', line 14

def initialize(max_size: 1000)
  @max_size = max_size
  @data = {}
  @order = []
  @mutex = Mutex.new
  @hits = 0
  @misses = 0
  @evictions = 0
  init_callbacks
  init_tag_stats
end

Instance Method Details

#[](key) ⇒ Object



67
# File 'lib/philiprehberger/cache_kit/store.rb', line 67

def [](key) = get(key)

#[]=(key, value) ⇒ Object



69
70
71
# File 'lib/philiprehberger/cache_kit/store.rb', line 69

def []=(key, value)
  set(key, value)
end

#clearObject



51
52
53
# File 'lib/philiprehberger/cache_kit/store.rb', line 51

def clear
  @mutex.synchronize { @data.clear && @order.clear }
end

#compactInteger

Prune expired entries and return the count of evicted items

Returns:

  • (Integer)

    number of evicted entries



97
98
99
100
101
102
103
# File 'lib/philiprehberger/cache_kit/store.rb', line 97

def compact
  @mutex.synchronize do
    expired_keys = @data.select { |_, entry| entry.expired? }.keys
    expired_keys.each { |key| remove_entry(key) }
    expired_keys.length
  end
end

#decrement(key, by: 1, ttl: nil) ⇒ Numeric

Atomically decrement a numeric entry. See #increment.

Parameters:

  • key (String)

    the cache key

  • by (Numeric) (defaults to: 1)

    amount to subtract (default 1)

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

    optional TTL override

Returns:

  • (Numeric)

    the new value

Raises:

  • (Error)

    when the existing value is not numeric



206
207
208
# File 'lib/philiprehberger/cache_kit/store.rb', line 206

def decrement(key, by: 1, ttl: nil)
  @mutex.synchronize { apply_counter_delta(key, -by, ttl: ttl) }
end

#delete(key) ⇒ Boolean

Returns true if the key existed.

Returns:

  • (Boolean)

    true if the key existed



42
43
44
# File 'lib/philiprehberger/cache_kit/store.rb', line 42

def delete(key)
  @mutex.synchronize { delete_entry(key) }
end

#delete_many(*keys) ⇒ Integer

Bulk-delete multiple keys in a single lock acquisition. Does not fire eviction callbacks (matches #delete semantics).

Parameters:

  • keys (Array<String>)

    keys to delete

Returns:

  • (Integer)

    number of keys that were actually removed



157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/philiprehberger/cache_kit/store.rb', line 157

def delete_many(*keys)
  keys = keys.flatten
  @mutex.synchronize do
    removed = 0
    keys.each do |key|
      next unless @data.key?(key)

      remove_entry(key)
      removed += 1
    end
    removed
  end
end

#expire_at(key) ⇒ Time?

Absolute expiration time of the entry.

Returns nil when the key is missing, expired, or has no TTL.

Parameters:

  • key (String)

    the cache key

Returns:

  • (Time, nil)


143
144
145
146
147
148
149
150
# File 'lib/philiprehberger/cache_kit/store.rb', line 143

def expire_at(key)
  @mutex.synchronize do
    entry = @data[key]
    next nil if entry.nil? || entry.expired?

    entry.expire_at
  end
end

#fetch(key, ttl: nil, tags: [], &block) ⇒ Object

Get or compute a value (thread-safe).



37
38
39
# File 'lib/philiprehberger/cache_kit/store.rb', line 37

def fetch(key, ttl: nil, tags: [], &block)
  @mutex.synchronize { fetch_or_compute(key, ttl: ttl, tags: tags, &block) }
end

#get(key) ⇒ Object

Get a value by key. Returns nil if missing or expired.



27
28
29
# File 'lib/philiprehberger/cache_kit/store.rb', line 27

def get(key)
  @mutex.synchronize { fetch_entry(key) }
end

#increment(key, by: 1, ttl: nil) ⇒ Numeric

Atomically increment a numeric entry. Initializes missing or expired keys to 0 before applying the delta.

Parameters:

  • key (String)

    the cache key

  • by (Numeric) (defaults to: 1)

    amount to add (default 1)

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

    optional TTL override (nil preserves current TTL)

Returns:

  • (Numeric)

    the new value

Raises:

  • (Error)

    when the existing value is not numeric



195
196
197
# File 'lib/philiprehberger/cache_kit/store.rb', line 195

def increment(key, by: 1, ttl: nil)
  @mutex.synchronize { apply_counter_delta(key, by, ttl: ttl) }
end

#invalidate_tag(tag) ⇒ Object

Invalidate all entries with a given tag.



47
48
49
# File 'lib/philiprehberger/cache_kit/store.rb', line 47

def invalidate_tag(tag)
  @mutex.synchronize { invalidate_by_tag(tag) }
end

#key?(key) ⇒ Boolean

Returns:

  • (Boolean)


59
60
61
# File 'lib/philiprehberger/cache_kit/store.rb', line 59

def key?(key)
  @mutex.synchronize { key_present?(key) }
end

#keysObject



63
64
65
# File 'lib/philiprehberger/cache_kit/store.rb', line 63

def keys
  @mutex.synchronize { @data.reject { |_, e| e.expired? }.keys }
end

#keys_by_tag(tag) ⇒ Array<String>

Return the keys associated with a given tag. Excludes expired entries.

Parameters:

  • tag (String, Symbol)

Returns:

  • (Array<String>)


176
177
178
179
180
181
182
183
184
185
# File 'lib/philiprehberger/cache_kit/store.rb', line 176

def keys_by_tag(tag)
  tag_s = tag.to_s
  @mutex.synchronize do
    @data.each_with_object([]) do |(key, entry), acc|
      next if entry.expired?

      acc << key if entry.tags.include?(tag_s)
    end
  end
end

#pruneObject



78
79
80
# File 'lib/philiprehberger/cache_kit/store.rb', line 78

def prune
  @mutex.synchronize { prune_expired }
end

#refresh(key, ttl: nil) ⇒ Boolean

Reset the TTL of an existing entry without changing its value

Parameters:

  • key (String)

    the cache key

  • ttl (Integer, nil) (defaults to: nil)

    new TTL in seconds

Returns:

  • (Boolean)

    true if the key exists and was refreshed



110
111
112
113
114
115
116
117
118
119
120
# File 'lib/philiprehberger/cache_kit/store.rb', line 110

def refresh(key, ttl: nil)
  @mutex.synchronize do
    entry = @data[key]
    return false if entry.nil? || entry.expired?

    remove_entry(key)
    @data[key] = Entry.new(entry.value, ttl: ttl, tags: entry.tags)
    @order.push(key)
    true
  end
end

#set(key, value, ttl: nil, tags: []) ⇒ Object

Store a value with optional TTL and tags.



32
33
34
# File 'lib/philiprehberger/cache_kit/store.rb', line 32

def set(key, value, ttl: nil, tags: [])
  @mutex.synchronize { store_entry(key, value, ttl: ttl, tags: tags) }
end

#set_many(hash, ttl: nil, tags: []) ⇒ void

This method returns an undefined value.

Bulk set multiple entries in a single lock acquisition

Parameters:

  • hash (Hash)

    key => value pairs

  • ttl (Integer, nil) (defaults to: nil)

    time-to-live in seconds

  • tags (Array<String>) (defaults to: [])

    tags for all entries



88
89
90
91
92
# File 'lib/philiprehberger/cache_kit/store.rb', line 88

def set_many(hash, ttl: nil, tags: [])
  @mutex.synchronize do
    hash.each { |key, value| store_entry(key, value, ttl: ttl, tags: tags) }
  end
end

#sizeObject



55
56
57
# File 'lib/philiprehberger/cache_kit/store.rb', line 55

def size
  @mutex.synchronize { @data.size }
end

#stats(tag: nil) ⇒ Hash

Returns cache statistics, optionally filtered by tag.

Returns:

  • (Hash)

    cache statistics, optionally filtered by tag



74
75
76
# File 'lib/philiprehberger/cache_kit/store.rb', line 74

def stats(tag: nil)
  @mutex.synchronize { tag ? tag_stats_for(tag) : global_stats }
end

#ttl(key) ⇒ Float?

Remaining seconds until the entry expires.

Returns nil when the key is missing, expired, or has no TTL.

Parameters:

  • key (String)

    the cache key

Returns:

  • (Float, nil)


128
129
130
131
132
133
134
135
# File 'lib/philiprehberger/cache_kit/store.rb', line 128

def ttl(key)
  @mutex.synchronize do
    entry = @data[key]
    next nil if entry.nil? || entry.expired?

    entry.remaining_ttl
  end
end