Class: Litecache

Inherits:
Object
  • Object
show all
Includes:
Litemetric::Measurable, Litesupport::Liteconnection
Defined in:
lib/litestack/litecache.rb

Overview

Litecache is a caching library for Ruby applications that is built on top of SQLite. It is designed to be simple to use, very fast, and feature-rich, providing developers with a reliable and efficient way to cache data.

One of the main features of Litecache is automatic key expiry, which allows developers to set an expiration time for each cached item. This ensures that cached data is automatically removed from the cache after a certain amount of time has passed, reducing the risk of stale data being served to users.

In addition, Litecache supports LRU (Least Recently Used) removal, which means that if the cache reaches its capacity limit, the least recently used items will be removed first to make room for new items. This ensures that the most frequently accessed data is always available in the cache.

Litecache also supports integer value increment/decrement, which allows developers to increment or decrement the value of a cached item in a thread-safe manner. This is useful for implementing counters or other types of numerical data that need to be updated frequently.

Overall, Litecache is a powerful and flexible caching library that provides automatic key expiry, LRU removal, and integer value increment/decrement capabilities. Its fast performance and simple API make it an excellent choice for Ruby applications that need a reliable and efficient way to cache data.

Constant Summary collapse

DEFAULT_OPTIONS =

the default options for the cache can be overriden by passing new options in a hash to Litecache.new

path: "./cache.db"
expiry: 60 * 60 * 24 * 30 -> one month default expiry if none is provided
size: 128 * 1024 * 1024 -> 128MB
mmap_size: 128 * 1024 * 1024 -> 128MB to be held in memory
min_size: 32 * 1024 -> 32MB
return_full_record: false -> only return the payload
sleep_interval: 1 -> 1 second of sleep between cleanup runs
{
  path: Litesupport.root.join("cache.sqlite3"),
  config_path: "./litecache.yml",
  sync: 0,
  expiry: 60 * 60 * 24 * 30, # one month
  size: 128 * 1024 * 1024, # 128MB
  mmap_size: 128 * 1024 * 1024, # 128MB
  min_size: 8 * 1024 * 1024, # 16MB
  return_full_record: false, # only return the payload
  sleep_interval: 1, # 1 second
  metrics: false
}

Instance Method Summary collapse

Methods included from Litemetric::Measurable

#capture, #capture_snapshot, #collect_metrics, #create_snapshotter, #measure, #metrics_identifier

Methods included from Litesupport::Liteconnection

#journal_mode, #options, #path, #size, #synchronous

Methods included from Litesupport::Forkable

#_fork

Constructor Details

#initialize(options = {}) ⇒ Litecache

creates a new instance of Litecache can optionally receive an options hash which will be merged with the DEFAULT_OPTIONS (the new hash overrides any matching keys in the default one).

Example:

litecache = Litecache.new

litecache.set("a", "somevalue")
litecache.get("a") # =>  "somevalue"

litecache.set("b", "othervalue", 1) # expire aftre 1 second
litecache.get("b") # => "othervalue"
sleep 2
litecache.get("b") # => nil

litecache.clear # nothing remains in the cache
litecache.close # optional, you can safely kill the process


64
65
66
67
68
69
# File 'lib/litestack/litecache.rb', line 64

def initialize(options = {})
  options[:size] = DEFAULT_OPTIONS[:min_size] if options[:size] && options[:size] < DEFAULT_OPTIONS[:min_size]
  @last_visited = {}
  init(options)
  collect_metrics if @options[:metrics]
end

Instance Method Details

#clearObject

delete all key, value pairs in the cache



163
164
165
# File 'lib/litestack/litecache.rb', line 163

def clear
  run_sql("delete FROM data")
end

#closeObject

close the connection to the cache file



168
169
170
171
# File 'lib/litestack/litecache.rb', line 168

def close
  @running = false
  super
end

#countObject

return the number of key, value pairs in the cache



153
154
155
# File 'lib/litestack/litecache.rb', line 153

def count
  run_stmt(:counter)[0][0]
end

#decrement(key, amount, expires_in = nil) ⇒ Object

decrement an integer value by amount, optionally add an expiry value (in seconds)



135
136
137
# File 'lib/litestack/litecache.rb', line 135

def decrement(key, amount, expires_in = nil)
  increment(key, -amount, expires_in)
end

#delete(key) ⇒ Object

delete a key, value pair from the cache



119
120
121
122
123
124
125
126
# File 'lib/litestack/litecache.rb', line 119

def delete(key)
  changes = 0
  @conn.acquire do |cache|
    cache.stmts[:deleter].execute!(key)
    changes = cache.changes
  end
  changes > 0
end

#get(key) ⇒ Object

get a value by its key if the key doesn’t exist or it is expired then null will be returned



107
108
109
110
111
112
113
114
115
116
# File 'lib/litestack/litecache.rb', line 107

def get(key)
  key = key.to_s
  if (record = @conn.acquire { |cache| cache.stmts[:getter].execute!(key)[0] })
    @last_visited[key] = true
    capture(:get, key, 1)
    return record[1]
  end
  capture(:get, key, 0)
  nil
end

#increment(key, amount, expires_in = nil) ⇒ Object

increment an integer value by amount, optionally add an expiry value (in seconds)



129
130
131
132
# File 'lib/litestack/litecache.rb', line 129

def increment(key, amount, expires_in = nil)
  expires_in ||= @expires_in
  @conn.acquire { |cache| cache.stmts[:incrementer].execute!(key.to_s, amount, expires_in) }
end

#max_sizeObject

return the maximum size of the cache



174
175
176
# File 'lib/litestack/litecache.rb', line 174

def max_size
  run_sql("SELECT s.page_size * c.max_page_count FROM pragma_page_size() as s, pragma_max_page_count() as c")[0][0].to_f / (1024 * 1024)
end

#prune(limit = nil) ⇒ Object

delete all entries in the cache up limit (ordered by LRU), if no limit is provided approximately 20% of the entries will be deleted



140
141
142
143
144
145
146
147
148
149
150
# File 'lib/litestack/litecache.rb', line 140

def prune(limit = nil)
  @conn.acquire do |cache|
    if limit&.is_a? Integer
      cache.stmts[:limited_pruner].execute!(limit)
    elsif limit&.is_a? Float
      cache.stmts[:extra_pruner].execute!(limit)
    else
      cache.stmts[:pruner].execute!
    end
  end
end

#set(key, value, expires_in = nil) ⇒ Object

add a key, value pair to the cache, with an optional expiry value (number of seconds)



72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/litestack/litecache.rb', line 72

def set(key, value, expires_in = nil)
  key = key.to_s
  expires_in = @options[:expires_in] if expires_in.nil? || expires_in.zero?
  @conn.acquire do |cache|
    cache.stmts[:setter].execute!(key, value, expires_in)
    capture(:set, key)
  rescue SQLite3::FullException
    cache.stmts[:extra_pruner].execute!(0.2)
    cache.execute("vacuum")
    retry
  end
  true
end

#set_unless_exists(key, value, expires_in = nil) ⇒ Object

add a key, value pair to the cache, but only if the key doesn’t exist, with an optional expiry value (number of seconds)



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/litestack/litecache.rb', line 87

def set_unless_exists(key, value, expires_in = nil)
  key = key.to_s
  expires_in = @options[:expires_in] if expires_in.nil? || expires_in.zero?
  changes = 0
  @conn.acquire do |cache|
    cache.transaction(:immediate) do
      cache.stmts[:inserter].execute!(key, value, expires_in)
      changes = cache.changes
    end
    capture(:set, key)
  rescue SQLite3::FullException
    cache.stmts[:extra_pruner].execute!(0.2)
    cache.execute("vacuum")
    retry
  end
  changes > 0
end

#snapshotObject



178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/litestack/litecache.rb', line 178

def snapshot
  {
    summary: {
      path: path,
      journal_mode: journal_mode,
      synchronous: synchronous,
      size: size,
      max_size: max_size,
      entries: count
    }
  }
end

#transaction(mode, acquire = true) ⇒ Object

low level access to SQLite transactions, use with caution



192
193
194
195
196
197
198
199
# File 'lib/litestack/litecache.rb', line 192

def transaction(mode, acquire = true)
  return cache.transaction(mode) { yield } unless acquire
  @conn.acquire do |cache|
    cache.transaction(mode) do
      yield
    end
  end
end