AnyCache ·
AnyCache - a simplest cache wrapper that provides a minimalistic generic interface for all well-known cache storages and includes a minimal set of necessary operations:
fetch
, read
, write
, delete
, fetch_multi
, read_multi
, write_multi
, delete_matched
, expire
, persist
, exist?
, clear
, cleanup
, increment
, decrement
.
Supported clients:
Redis
(gem redis) (redis storage)Redis::Store
(gem redis-store) (redis storage)Dalli::Client
(gem dalli) (memcached storage)ActiveSupport::Cache::RedisCacheStore
(gem activesupport) (redis cache storage)ActiveSupport::Cache::DalliStore
(gem dalli) (dalli store)ActiveSupport::Cache::MemCacheStore
(gem activesupport) (memcache storage)ActiveSupport::Cache::FileStore
(gem activesupport) (file storage)ActiveSupport::Cache::MemoryStore
(gem activesupport) (in memory storage)[Coming Soon] File-based configuration;
[Coming Soon] Simple in-memory hash-based cache client
[Coming Soon] Simple key-value storage based on PostgreSQL
[Coming Soon] Support for
ActiveSupport::Cache::NullStore
[Coming Soon] Naive
NullStore
implementation;
Installation
gem 'any_cache'
bundle install
# --- or ---
gem install any_cache
require 'any_cache'
Usage / Table of Contents
- Creation
- Manual creation
- Config-based creation
- AnyCache with Redis
- AnyCache with Redis::Store
- AnyCache with Dalli::Client
- AnyCache with ActiveSupport::Cache::RedisCacheStore
- AnyCache with ActiveSupport::Cache::DalliStore
- AnyCache with ActiveSupport::Cache::MemCacheStore
- AnyCache with ActiveSupport::Cache::FileStore
- AnyCache with ActiveSupport::Cache::MemoryStore
- Many cache storages
- Custom cache clients
- Logging
- Operations
- Plugins
- Roadmap
Creation
To instantiate AnyCache instance you have to provide a client. Client - an independent driver that works with a corresponding cache storage (external dependency).
Supported clients:
Redis
Redis::Store
Dalli::Client
ActiveSupport::Cache::RedisCacheStore
ActiveSupport::Cache::DalliStore
ActiveSupport::Cache::MemCacheStore
ActiveSupport::Cache::FileStore
ActiveSupport::Cache::MemoryStore
AnyCache
can be instantiated by two ways:
Manual creation
Custom instantiation with explicit client objects:
# 1) create client object
client = Redis.new(...)
# -- or --
client = Redis::Store.new(...)
# -- or --
client = Dalli::Client.new(...)
# -- or --
client = ActiveSupport::Cache::RedisCacheStore.new(...)
# -- or --
client = ActiveSupport::Cache::DalliStore.new(...)
# -- or --
client = ActiveSupport::Cache::MemCacheStore.new(...)
# -- or --
client = ActiveSupport::Cache::FileStore.new(...)
# -- or --
client = ActiveSupport::Cache::MemoryStore.new(...)
# 2) build AnyCache instance
cache_store = AnyCache.build(client) # => <AnyCache:0x00007f990527f268 ...>
Config-based creation
You can configure AnyCache
globally or create subclasses and configure each of them. After that
storage instantiation works via .build
method without explicit attributes.
AnyCache.configure
is used for configuration;config.driver
is used for determine which client should be used;config.__driver_name__.options
stores client-related options;
Supported drivers:
:redis
- Redis;:redis_store
- Redis::Client;:dalli
- Dalli::Client;:as_redis_cache_store
- ActiveSupport::Cache::RedisCacheStore;:as_dalli_store
- ActiveSupport::Cache::DalliStore;:as_mem_cache_store
- ActiveSupport::Cache::MemCacheStore;:as_file_store
- ActiveSupport::Cache::FileStore;:as_memory_store
- ActiveSupport::Cache::MemoryStore;
AnyCache
with Redis
:
require 'redis'
require 'any_cache'
AnyCache.configure do |conf|
conf.driver = :redis
conf.redis.options = { ... } # Redis-related options
end
cache_store = AnyCache.build
AnyCache
with Redis::Store
:
require 'redis-store'
require 'any_cache'
AnyCache.configure do |conf|
conf.driver = :redis_store
conf.redis_store.options = { ... } # Redis::Store-related options
end
cache_store = AnyCache.build
AnyCache
with Dalli::Client
:
require 'dalli'
require 'any_cache'
AnyCache.configure do |conf|
conf.driver = :dalli
conf.dalli.servers = ... # string or array of strings
conf.dalli.options = { ... } # Dalli::Client-related options
end
cache_store = AnyCache.build
AnyCache
with ActiveSupport::Cache::RedisCacheStore
:
require 'redis'
require 'active_support'
require 'any_cache'
AnyCache.configure do |conf|
conf.driver = :as_redis_cache_store
conf.as_redis_cache_store.options = { ... } # ActiveSupport::Cache::RedisCacheStore-related options
end
cache_store = AnyCache.build
AnyCache
with ActiveSupport::Cache::DalliStore
:
require 'dalli'
require 'active_support'
require 'any_cache'
AnyCache.enable_patch!(:dalli_store) # NOTE: actual for Dalli <= 2.7.8
AnyCache.configure do |conf|
conf.driver = :as_dalli_store
conf.as_dalli_store.servers = ... # string or array of strings
conf.as_dalli_store.options = { ... } # ActiveSupport::Cache::DalliStore-related options
end
cache_store = AnyCache.build
AnyCache
with ActiveSupport::Cache::MemCacheStore
:
require 'active_support'
require 'any_cache'
AnyCache.configure do |conf|
conf.driver = :as_mem_cache_store
conf.as_mem_cache_store.servers = ... # string or array of strings
conf.as_mem_cache_store.options = { ... } # ActiveSupport::Cache::MemCacheStore-related options
end
cache_store = AnyCache.build
AnyCache
with ActiveSupport::Cache::FileStore
:
require 'active_support'
require 'any_cache'
AnyCache.configure do |conf|
conf.driver = :as_file_store
conf.as_file_store.cache_path = '/path/to/cache'
conf.as_file_store.options = { ... } # ActiveSupport::Cache::FileStore-related options
end
cache_store = AnyCache.build
AnyCache
with ActiveSupport::Cache::MemoryStore
:
require 'activesupport'
require 'any_cache'
AnyCache.configure do |conf|
conf.driver = :as_memory_store
conf.as_memory_store.options = { ... } # ActiveSupport::Cache::MemoryStore-related options
end
cache_store = AnyCache.build
Many cache storages
You can inherit AnyCache
class and create and configure as many cache storages as you want:
class RedisCache < AnyCache
configure do |conf|
conf.driver = :redis
end
end
class DalliCache < AnyCache
configure do |conf|
conf.driver = :dalli
end
end
redis_cache = RedisCache.build
dalli_cache = DalliCache.build
Custom cache clients
If you want to use your own cache client implementation, you should provide an object that responds to:
#fetch(key, [**options])
(doc)#fetch_multi(*keys, [**options])
(doc)#read(key, [**options])
(doc)#read_multi(*keys, [**options])
(doc)#write(key, value, [**options])
(doc)#write_multi(entries, [**options])
(doc)#delete(key, [**options])
(doc)#delete_matched(pattern, [**options])
(doc)#increment(key, amount, [**options])
(doc)#decrement(key, amount, [**options])
(doc)#expire(key, [**options])
(doc)#persist(key, [**options])
(doc)#exist?(key, [**options])
(doc)#clear([**options])
(doc)#cleanup([**options])
(doc)
class MyCacheClient
# ...
def read(key, **)
# ...
end
def write(key, value, **)
# ...
end
# ...
end
AnyCache.build(MyCacheClient.new)
Logging
AnyCache logs all its operations. By default, AnyCache
uses a simple STDOUT
logger with Logger::INFO
level.
Logging is performed with level configured in logger object.
Logger configuration:
# --- use your own logger ---
AnyCache.configure do |conf|
conf.logger = MyLoggerObject.new
end
# --- disable logging ---
AnyCache.configure do |conf|
conf.logger = nil
end
# --- (used by default) ---
AnyCache.configure do |conf|
conf.logger = AnyCache::Logging::Logger.new(STDOUT)
end
# --- (your cache client inherited from AnyCache) ---
YourCacheClient.configure do |conf|
# same configs as above
end
Log message format:
[AnyCache<CACHER_NAME>/Activity<OPERATION_NAME>]: performed <OPERATION_NAME> operation with
params: INSPECTED_ARGUMENTS and options: INSPECTED_OPTIONS
- progname
CACHER_NAME
- class name of your cache class;OPERATION_NAME
- performed operation (read
,write
,fetch
and etc);
- message
INSPECTED_ARGUMENTS
- a set of operation arguments;INSPECTED_OPTIONS
- a set of operation options;
any_cache.write("data", 123, expires_in: 60)
# I, [2018-09-07T10:04:56.649960 #15761] INFO -- [AnyCache<AnyCache>/Activity<write>]: performed <write> operation with attributes: ["data", 123] and options: {:expires_in=>60}.
any_cache.clear
# I, [2018-09-07T10:05:26.999847 #15761] INFO -- [AnyCache<AnyCache>/Activity<clear>]: performed <clear> operation with attributes: [] and options: {}.
Operations
AnyCache
provides a following operation set:
- fetch / fetch_multi
- read / read_multi
- write / write_multi
- delete / delete_matched
- increment / decrement
- expire
- persist
- clear
- exist?
Fetch
AnyCache#fetch(key, [force:], [expires_in:], [&fallback])
- works in
ActiveSupport::Cache::Store#fetch
-manner; - fetches data from the cache using the given key;
- if a
fallback
block has been passed and data with the given key does not exist - that block will be called with the given key and the return value will be written to the cache; - use
raw: true
if you want to fetch incrementable/decrementable entry;
- works in
# --- entry exists ---
cache_store.fetch("data") # => "some_data"
cache_store.fetch("data") { "new_data" } # => "some_data"
# --- entry does not exist ---
cache_store.fetch("data") # => nil
cache_store.fetch("data") { |key| "new_data" } # => "new_data"
cache_store.fetch("data") # => "new_data"
# --- new entry with expiration time ---
cache_store.fetch("data") # => nil
cache_store.fetch("data", expires_in: 8) { |key| "new_#{key}" } # => "new_data"
cache_store.fetch("data") # => "new_data"
# ...sleep 8 seconds...
cache_store.fetch("data") # => nil
# --- force update/rewrite ---
cache_store.fetch("data") # => "some_data"
cache_store.fetch("data", expires_in: 8, force: true) { |key| "new_#{key}" } # => "new_data"
cache_store.fetch("data") # => "new_data"
# ...sleep 8 seconds...
cache_store.fetch("data") # => nil
Fetch Multi
AnyCache#fetch_multi(*keys, [force:], [expires_in:], [&fallback])
- get a set of entries in hash form from the cache storage using given keys;
- works in
#fetch
manner but with a series of entries; - nonexistent entries will be fetched with
nil
values; - use
raw: true
if you want to fetch incrementable/decrementable entries;
# --- fetch entries ---
cache_store.fetch_multi("data", "second_data", "last_data")
# => returns:
{
"data" => "data", # existing entry
"second_data" => nil, # nonexistent entry
"last_data" => nil # nonexistent entry
}
# --- fetch etnries and define non-existent entries ---
cache_store.fetch_multi("data", "second_data", "last_data") { |key| "new_#{key}" }
# => returns:
{
"data" => "data", # entry with OLD value
"second_data" => "new_second_data", # entry with NEW DEFINED value
"last_data" => "new_last_data" # entry with NEW DEFINED value
}
# --- force rewrite all entries ---
cache_store.fetch_multi("data", "second_data", "last_data", force: true) { |key| "force_#{key}" }
# => returns
{
"data" => "force_data", # entry with REDEFINED value
"second_data" => "force_second_data", # entry with REDEFINED value
"last_data" => "force_last_data" # entry with REDEFINED value
}
Read
AnyCache#read(key)
- get an entry value from the cache storage- pass
raw: true
if you want to read incrementable/decrementable entries;
- pass
# --- entry exists ---
cache_store.read("data") # => "some_data"
# --- entry doesnt exist ---
cache_store.read("data") # => nil
# --- read incrementable/decrementable entry ---
cache_store.read("data", raw: true) # => "2" (for example)
Read Multi
AnyCache#read_multi(*keys)
- get entries from the cache storage in hash form;
- nonexistent entries will be fetched with
nil
values; - pass
raw: true
if you want to read incrementable/decrementable entries;
cache_store.read_multi("data", "another_data", "last_data", "super_data")
# => returns
{
"data" => "test", # existing entry
"another_data" => nil, # nonexistent entry
"last_data" => "some_data", # exisitng enry
"super_data" => nil # existing entry
}
# --- read incrementable/decrementable entries ---
cache_store.read_multi("data", "another_data", raw: true)
# => returns
{
"data" => "1",
"another_data" => "2",
}
Write
AnyCache#write(key, value, [expires_in:])
- write a new entry to the cache storage;- pass
raw: true
if you want to store incrementable/decrementable entries;
- pass
# --- permanent entry ---
cache_store.write("data", 123)
# --- temporal entry (expires in 60 seconds) ---
cache_store.write("data", 123, expires_in: 60)
# --- incrementable/decrementable entry ---
cache_store.write("data", 123, raw: true)
Write Multi
AnyCache#write_multi(**entries)
- write a set of permanent entries to the cache storage;- pass
raw: true
if you want to store incrementable/decrementable entries;
- pass
cache_store.write_multi("data" => "test", "another_data" => 123)
# --- incrementable/decrementable entries ---
cache_store.write_multi("data" => 1, "another_data" => 2, raw: true)
Delete
AnyCache#delete(key)
- remove entry from the cache storage;
cache_store.delete("data")
Delete Matched
AnyCache#delete_matched(pattern)
- removes all entries with keys matching the pattern;
- currently unsupported:
:dalli
,:as_mem_cache_store
,:as_dalli_store
;
# --- using a regepx ---
cache_store.delete_matched(/\A*test*\z/i)
# --- using a string ---
cache_store.delete_matched("data")
Increment
AnyCache#increment(key, amount = 1, [expires_in:])
- increment entry's value by the given amount and set the new expiration time if needed;- can increment only nonexistent entries OR entries that were written with
raw: true
option;
- can increment only nonexistent entries OR entries that were written with
# --- increment existing entry ---
cache_store.write("data", 1, raw: true) # you must provide :raw => true for incrementable entries
# --- increment by default value (1) ---
cache_store.increment("data") # => 2
# --- increment by custom value ---
cache_store.increment("data", 12) # => 14
# --- increment and expire after 31 seconds
cache_store.incrmeent("data", expires_in: 31) # => 15
# --- increment nonexistent entry (create new entry) ---
cache_store.increment("another_data", 5, expires_in: 5) # => 5
# --- read incrementable entry ---
cache_store.read("data", raw: true) # you must provide :raw => true for incrementable entries
Decrement
AnyCache#decrement(key, amount = 1, [expires_in:])
- decrement entry's value by the given amount and set the new expiration time if needed;- can decrement only nonexistent entries OR entries that were written with
raw: true
option;
- can decrement only nonexistent entries OR entries that were written with
# --- decrement existing entry ---
cache_store.write("data", 15, raw: true) # you must provide :raw => true for decrementable entries
# --- decrement by default value (1) ---
cache_store.decrement("data") # => 14
# --- decrement by custom value ---
cache_store.decrement("data", 10) # => 4
# --- decrement and expire after 5 seconds
cache_store.decrememnt("data", expirs_in: 5) # => 3
# --- decrement nonexistent entry (create new entry) ---
cache_store.decrememnt("another_data", 2, expires_in: 10) # => -2 (or 0 for Dalli::Client)
# --- read decrementable entry ---
cache_store.read("data", raw: true) # you must provide :raw => true for decrementable entries
Expire
AnyCache#expire(key, [expires_in:])
- expire entry immediately or set the new expiration time;
# --- expire immediately ---
cache_store.expire("data")
# --- set new expiration time (in seconds) --
cache_store.expire("data", expires_in: 36)
Persist
AnyCache#persist(key)
- change entry's expiration time to permanent;
# --- create temporal entry (30 seconds) ---
cache_store.write("data", { a: 1 }, expires_in: 30)
# --- remove entry expiration (make it permanent) ---
cache_store.persist("data")
Existence
AnyCache#exist?(key)
- determine if an entry exists;
# --- entry exists ---
cache_store.exist?("data") # => true
# --- entry does not exist ---
cache_store.exist?("another-data") # => false
Clear
AnyCache#clear()
- clear cache database;
# --- prepare cache data ---
cache_store.write("data", { a: 1, b: 2 })
cache_store.write("another_data", 123_456)
cache_store.read("data") # => { a: 1, b: 2 }
cache_store.read("another_data") # => 123_456
# --- clear cache ---
cache_store.clear
cache_store.read("data") # => nil
cache_store.read("another_data") # => nil
Cleanup
AnyCache#cleanup()
- remove expired entries from cache database (make sense only for:as_file_store
and:as_memory_store
cache clients);
# --- prepare cache data ---
cache_store.write("data", "123", expires_in: 5)
cache_store.write("another_data", "456", expires_in: 10)
# --- waiting for cache exiration (10 seconds) ---
cache_store.cleanup # remove expired entries from database (release disk space for example)
Plugins
AnyCache
provides a set of plugins and an interface for controllable plugin registering and loading.
# --- show names of registered plugins ---
AnyCache.plugins # => array of strings
# --- load specific plugin ---
AnyCache.plugin(:plugin_name) # or AnyCache.plugin('plugin_name')
Build
- see bin/rspec
bin/rspec --test-redis # run specs with Redis
bin/rspec --test-redis-store # run specs with Redis::Store
bin/rspec --test-dalli # run specs with Dalli::Client
bin/rspec --test-as-redis-cache-store # run specs with ActiveSupport::Cache::RedisCacheStore
bin/rspec --test-as-dalli-store # run specs with ActiveSupport::Cache::DalliStore
bin/rspec --test-as-mem-cache-store # run specs with ActiveSupport::Cache::MemCacheStore
bin/rspec --test-as-file-store # run specs with ActiveSupport::Cache::FileStore
bin/rspec --test-as-memory-store # run specs with ActiveSupport::Cache::MemoryStore
Roadmap
- centralized Logging API (core API);
- instrumentation layer (public API);
- more robust delegation API (core API);
- global and configurable default expiration time (public API);
#delete_matched
for memcached-based cache storages (core/public API);- rails integration (public API);
Contributing
- Fork it (https://github.com/0exp/any_cache/fork)
- Create your feature branch (
git checkout -b feature/my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin feature/my-new-feature
) - Create new Pull Request
License
Released under MIT License.
Authors
Created by Rustam Ibragimov