JBoss.orgCommunity Documentation

Chapter 7. TorqueBox Caching

7.1. Overview
7.2. Clustering Modes
7.3. TorqueBox::Infinispan::Cache Options and Usage
7.4. ActiveSupport::Cache::TorqueBoxStore Options and Usage

As a part of the JBoss AS, TorqueBox utilizes the Infinispan data grid. Infinispan offers a noSQL key/value store that can be replicated or distributed across a cluster, or run on a single machine locally. The cache is exposed as Ruby modules and classes through TorqueBox. There are two ways that applications can take advantage of this data store.

  • TorqueBox::Infinispan::Cache Direct Ruby access to the Infinispan cache
  • ActiveSupport::Cache::TorqueBoxStore Sinatra and Rails session, fragment and other framework caching

Each of these components allows applications to configure the clustering mode and other options of the underlying Infinispan cache, and fully supports JTA and XA distributed transactions in the container.

Infinispan offers a number of clustering modes that determine what happens when an entry is written to the cache.

Local

This is the default mode when TorqueBox runs non-clustered, roughly equivalent to the Rails MemoryStore implementation, though it has some advantages over a simple memory store, e.g. write-through/write-behind persistence, JTA/XA support, MVCC-based concurency, and JMX manageability.

Invalidation

No data is actually shared among the nodes in this mode. Instead, notifications are sent to all nodes when data changes, causing them to evict their stale copies of the updated entry. This mode works best when the infinispan data store is backed by a single, canonical data source such as a relational database. It also works very well for Rails' fragment and action caching, and is the default mode for the cache underlying ActiveSupport::Cache::TorqueBoxStore.

Replicated

In this mode, entries added to any cache instance will be copied to all other cache instances in the cluster, and can then be retrieved locally from any instance. This mode is probably impractical for clusters of any significant size. Infinispan recommends 10 as a reasonable upper bound on the number of replicated nodes.

Distributed

This is the default mode for a la carte caches when TorqueBox runs clustered. This mode enables Infinispan clusters to achieve "linear scalability". Cache entries are copied to a fixed number of cluster nodes (2, by default) regardless of the cluster size. Distribution uses a consistent hashing algorithm to determine which nodes will store a given entry.

The TorqueBox::Infinispan::Cache supports a number of options. All components that use the cache as their underlying storage, e.g. ActiveSupport::Cache::TorqueBoxStore as well as the Cache class itself accept a hash of options. The common options for all cache components are:

Table 7.1. Cache options

OptionDefaultDescription
:mode:distributedAny of the following will result in replicated mode:
  • :r

  • :repl

  • :replicated

  • :replication

Any of the following will result in distributed mode:
  • :d

  • :dist

  • :distributed

  • :distribution

Any of the following will result in invalidation mode:
  • :i

  • :inv

  • :invalidated

  • :invalidation

Any other value for :mode will result in distributed when clustered and local otherwise.
:synctrueThe coordination between nodes in a cluster can happen either synchronously (slower writes) or asynchronously (faster writes).
:name{the application's name}The :name option enables you to create multiple cache stores in your app, each with different options. It's also a way you can configure multiple apps to share the same cache store.
:persistfalseThe :persist option enables file-based persistence of the cache entries. Any value for :persist which is a path to a writable directory will be used for cache storage.
:transaction_mode:transactionalBy default, all local caches are transactional. If you don't need transactions, set this to :non_transactional.
:locking_mode:optimisticStarting with Infinispan 5.1 the supported transaction models are :optimistic and :pessimistic. The :optimistic option defers lock acquisition to transaction prepare time, reducing lock acquisition duration and increasing throughput. With the pessimistic model, cluster wide-locks are acquired on each write and released after the transaction completes.
:encoding:marshal_smart The default value provides the widest support for the type of objects you can cache, i.e. any type of Ruby or Java object. Two other possible values for :encoding include :edn and :json. And while limited in the types of data that can be cached with them (essentially just the standard Ruby data types and collections) they can be used to share data among non-Ruby applications with access to the same Infinispan-backed caches.

TorqueBox::Infinispan::Cache Usage

The Cache object may be used to store and retrieve values from Infinispan. You can store just about anything: arbitrary Ruby data types, Java class instances, strings, numbers. The gamut. To use the Cache just make a new one providing it with initialization options.

        require 'torquebox-cache'
        cache = TorqueBox::Infinispan::Cache.new( :name => 'treasure', :persist=>'/data/treasure' )

Adding, removing and updating items in the cache is as you might expect.

# Put some stuff in the cache
cache.put( 'akey', "a string value" )
cache.put( "time", Time.now )
cache.put( user.id, user )

# Get it back again
time = cache.get( "time" )
user = cache.get( params[:id] )

# Remove something
cache.remove( 'akey' )
      

You also have typical hash-like methods which allow you to manipulate and query the cache for key information.

# Get all of the keys
keyset = cache.keys

# See if the cache contains a key
cache.contains_key? user.id

# Get everything! Caution, this could be very expensive
thewholeshebang = cache.all

# Clear it out. This happens asynchronously, so returns quickly
cache.clear

# Only put this in the cache if there isn't already something there with the same key
cache.put_if_absent( key, session[:user] )

# And you can replace a value in the cache conditionally 
# - if it hasn't changed since the last time you accessed it
t1 = Time.now
t2 = Time.now + 10
cache.put( "time", t1 )
# replaces t1 with t2
cache.replace( "time", t1, t2 )
# does NOT replace since the value is now t2
cache.replace( "time", t1, Time.now + 20 )
      

Increment, Decrement and Transactions

TorqueBox::Infinispan::Cache also provides some convenience methods for atomically incrementing or decrementing a value in the cache. Additionally, the Cache provides transactional blocks with Cache#transaction do ..., and all Cache operations automatically participate in XA transactions if they are called from within a TorqueBox.transaction block.

cache.increment('mykey') # 1
cache.increment('mykey') # 2
cache.decrement('mykey') # 1

# Automatically participates in XA transactions
Torquebox.transaction do
  cache.increment('mykey')
  # ...
end

# And can scope transactions itself
cache.transaction do
  cache.decrement('mykey')
  end
      

To read more about transactions see Chapter 15, TorqueBox Distributed Transactions.

As noted in Chapter 6, TorqueBox Web Applications the TorqueBox store can be used for all of the implicit caching within Rails and session storage within Sinatra. NB:The TorqueBoxStore uses :invalidation as it's default mode. If you will be using the Rails cache to explicitly store and retreive values across a cluster you should change the default clustering mode to :replicated or :distributed mode.

config.cache_store = :torquebox_store, {:mode => :distributed}

In addition to the common options for TorqueBox::Infinispan::Cache as noted above, ActiveSupport::Cache::TorqueBoxStore supports all the options of the existing Rails implementations, including the advanced features of MemCacheStore, along with a few more to control how data replication occurs amongst the nodes in a cluster.

Rails and Sinatra configuration details can be found in Chapter 6, TorqueBox Web Applications. Usage is essentially transparent to the application beyond this configuration.