CompositeCacheStore 🚀
Boost application speed and maximize user satisfaction with layered caching
Table of Contents
- Sponsors
- Why a composite cache?
- Eventual consistentency
- Dependencies
- Installation
- Setup
- Usage
- License
Sponsors
Proudly sponsored by
Why a composite cache?
Layered caching allows you to stack multiple caches with different scopes, lifetimes, and levels of reliability. A technique that yields several benefits.
- Improved performance
- Higher throughput
- Reduced load
- Enhanced capacity/scalability
Inner cache layer(s) provide the fastest reads as they're close to the application, typically in-memory within the same process. Outer layers are slower (still fast) but are shared by multiple processes and servers.
You can configure each layer with different expiration times, eviction policies, and storage mechanisms. You're in control of balancing the trade-offs between performance and data freshness.
Inner layers are supersonic while outer layers are speedy.
The difference between a cache hit on a local in-memory store versus a cache hit on a remote store is similar to making a grocery run in a Bugatti Chiron Super Sport 300+ compared to making the same trip on a bicyle, but all cache layers will be much faster than the underlying operations. For example, a complete cache miss (that triggers database queries and view rendering) would be equivalent to making this trip riding a sloth.
Eventual consistentency
Layered caching techniques exhibit some of the same traits as distributed systems because inner layers may hold onto stale data until their entries expire. Be sure to configure inner layers appropriately with shorter lifetimes.
This behavior is similar to the
race_condition_ttl
option in ActiveSupport::Cache::Store
which helps to avoid race conditions whenever multiple threads/processes try to write to the same cache entry simultaneously.
Be mindful of the potential gotchas.
- Data consistency - it's possible to end up with inconsistent or stale data
- Over-caching - caching too much can lead to increased memory usage and even slower performance
- Bugs/Testing - difficult bugs can be introduced with sophisticated caching techniques
Dependencies
Installation
bundle add "composite_cache_store"
Setup
Here's an example of how you might set up layered caching in a Rails application.
# config/initializers/composite_cache_store.rb
def Rails.composite_cache
@composite_cache ||= CompositeCacheStore.new(
layers: [
# Layer 1 cache (fastest)
# Most beneficial for high traffic volume
# Isolated to the process running an application instance
ActiveSupport::Cache::MemoryStore.new(
expires_in: 15.minutes,
size: 32.megabytes
),
# Layer 2 cache (faster)
# Most beneficial for moderate traffic volume
# Isolated to the machine running N-number of application instances,
# and shared by all application processes on the machine
ActiveSupport::Cache::RedisCacheStore.new(
url: "redis://localhost:6379/0",
expires_in: 2.hours
),
# Layer 3 cache (fast)
# Global cache shared by all application processes on all machines
ActiveSupport::Cache::RedisCacheStore.new(
url: "redis://remote.example.com:6379/0",
expires_in: 7.days
),
# additional layers are optional
]
)
end
Usage
A composite cache is ideal for mitigating hot spot latency in frequently invoked areas of the codebase.
# method that's invoked frequently by multiple processes/machines
def hotspot
Rails.composite_cache.fetch("example", expires_in: 12.hours) do
# reserve for high frequency access of slow operations
#
# examples:
# - api invocations
# - database queries
# - template renders
# - etc.
frequently_accessed_slow_operation
end
end
License
The gem is available as open source under the terms of the MIT License.