Class: Philiprehberger::CacheKit::Store
- Inherits:
-
Object
- Object
- Philiprehberger::CacheKit::Store
- 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
- #[](key) ⇒ Object
- #[]=(key, value) ⇒ Object
- #clear ⇒ Object
-
#compact ⇒ Integer
Prune expired entries and return the count of evicted items.
-
#decrement(key, by: 1, ttl: nil) ⇒ Numeric
Atomically decrement a numeric entry.
-
#delete(key) ⇒ Boolean
True if the key existed.
-
#delete_many(*keys) ⇒ Integer
Bulk-delete multiple keys in a single lock acquisition.
-
#expire_at(key) ⇒ Time?
Absolute expiration time of the entry.
-
#fetch(key, ttl: nil, tags: [], &block) ⇒ Object
Get or compute a value (thread-safe).
-
#get(key) ⇒ Object
Get a value by key.
-
#increment(key, by: 1, ttl: nil) ⇒ Numeric
Atomically increment a numeric entry.
-
#initialize(max_size: 1000) ⇒ Store
constructor
A new instance of Store.
-
#invalidate_tag(tag) ⇒ Object
Invalidate all entries with a given tag.
- #key?(key) ⇒ Boolean
- #keys ⇒ Object
-
#keys_by_tag(tag) ⇒ Array<String>
Return the keys associated with a given tag.
- #prune ⇒ Object
-
#refresh(key, ttl: nil) ⇒ Boolean
Reset the TTL of an existing entry without changing its value.
-
#set(key, value, ttl: nil, tags: []) ⇒ Object
Store a value with optional TTL and tags.
-
#set_many(hash, ttl: nil, tags: []) ⇒ void
Bulk set multiple entries in a single lock acquisition.
- #size ⇒ Object
-
#stats(tag: nil) ⇒ Hash
Cache statistics, optionally filtered by tag.
-
#ttl(key) ⇒ Float?
Remaining seconds until the entry expires.
Methods included from Serializable
Methods included from Batch
Methods included from Callbacks
Constructor Details
#initialize(max_size: 1000) ⇒ Store
Returns a new instance of Store.
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 |
#clear ⇒ Object
51 52 53 |
# File 'lib/philiprehberger/cache_kit/store.rb', line 51 def clear @mutex.synchronize { @data.clear && @order.clear } end |
#compact ⇒ Integer
Prune expired entries and return the count of evicted items
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.
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.
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).
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.
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: , &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.
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
59 60 61 |
# File 'lib/philiprehberger/cache_kit/store.rb', line 59 def key?(key) @mutex.synchronize { key_present?(key) } end |
#keys ⇒ Object
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.
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..include?(tag_s) end end end |
#prune ⇒ Object
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
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.) @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: ) } end |
#set_many(hash, ttl: nil, tags: []) ⇒ void
This method returns an undefined value.
Bulk set multiple entries in a single lock acquisition
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: ) } end end |
#size ⇒ Object
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.
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.
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 |