Cassandra’s built-in key and row caches can provide very efficient data caching. Some Cassandra production deployments have leveraged Cassandra’s caching features to the point where dedicated caching tools such as memcached could be completely replaced. Such deployments not only remove a redundant layer from the stack, but they also achieve the fundamental efficiency of strengthening caching functionality in the lower tier where the data is already being stored. Among other advantages, this means that caching never needs to be restarted in a completely “cold” state.
To fully optimize Cassandra’s caching hit rate, several aspects of the system should be considered, from hardware basics to specific caching thresholds. With proper tuning, hit rates of 85% or better are possible with Cassandra, and each hit on a key cache can save one disk seek per SSTable. Row caching, when feasible, can save the system from performing any disk seeks at all when fetching a cached row. Whenever growth in the read load begins to impact your hit rates, you can add capacity to quickly restore optimal levels of caching.
The rest of this page discusses the following important considerations:
Key caching is enabled by default, and high levels of key caching are recommended for most scenarios. Cases for row caching are more specialized, though when it works well within existing memory resources, row cache provides the most dramatic gains in efficiency.
The key cache holds the location of keys in memory on a per-column family basis. For column family level read optimizations, turning this value up can have an immediate impact (as soon as the cache warms) when there are large numbers of frequently accessed rows or the size of the columns in the rows makes it impractical to cache the row itself. Key caching is enabled by default, at a level of 200,000 keys.
Key cache performance can be monitored by using nodetool cfstats and examining the reported ‘Key cache hit rate’. JMX/jconsole may also be used similarly.
Unlike the key cache, the row cache holds the entire contents of the row in memory. It is best used when you have a small subset of data to keep hot and you frequently need most or all of the columns returned. For these use cases, row cache can have substantial performance benefits.
By the same token, you should avoid enabling row cache for column families with large rows or high write:read ratios. In such situations, row cache can very quickly consume a large amount of available memory. Note also that, when a row cache is operating efficiently, it keeps Java garbage compaction processes very active.
Row cache performance can be monitored by using nodetool cfstats and examining the reported ‘Row cache hit rate’. JMX/jconsole may also be used similarly.
In a scenario where both row and key caches are configured, the row cache will return results whenever possible. In the case of a row cache miss, the key cache may still provide a hit, assuming that it holds a larger number of keys than the row cache. In this example with both caches already populated, two read operations on a column family are depicted:
One read operation hits the row cache, returning the requested row without a disk seek. The other read operation requests a row that is not present in the row cache but is present in the key cache. After accessing the row in the SSTable, the system returns the data and populates the row cache with this read operation.
If your requirements permit it, a data model that logically separates heavily-read data into discrete column families can help optimize caching. Column families with relatively small, “narrow” rows lend themselves to highly efficient row caching. By the same token, it can make sense to separately store lower-demand data, or data with extremely long rows, in a column family with minimal caching, if any.
Row caching in such contexts brings the most benefit when access patterns follow a normal (Gaussian) distribution. When the keys most frequently requested follow such patterns, cache hit rates tend to increase. If you have particularly “hot” rows in your data model, row caching can bring significant performance improvements
Deploying a large number of Cassandra nodes under a relatively light load per node will maximize the fundamental benefit from key and row caches.
A less obvious but very important consideration is the OS page cache. Modern operating systems maintain page caches for frequently accessed data and are very efficient at keeping this data in memory. Even after a row is released in the Java Virtual Machine’s memory, it can be kept “warm” in the OS page cache – especially if the data is requested repeatedly, or no other requested data replaces it.
If your requirements allow you to lower JVM heap size and memtable sizes to leave memory for OS page caching, then do so. Ultimately, through gradual adjustments, you should achieve the desired balance between these three demands on available memory: heap, memtables, and caching.
Careful, incremental testing is essential to maximizing benefit from Cassandra’s caching features. Adjustments that increase your cache hit rate are likely to decrease the system resources available for your write load and other operations. After making changes to cache configuration, it is best to monitor Cassandra as a whole for unintended impact on the system.
The jconsole GUI can be a helpful tool for monitoring caching metrics exposed through JMX. For each node and each column family, you can view your cache hit rate, cache size, and number of hits by expanding org.apache.cassandra.db in the MBeans tab. For example:
To tune for higher hit rates with a key cache, use the Cassandra CLI to raise the number of keys cached for a given column family. For example:
[default@Keyspace1] update column family Standard1 with keys_cached=205000; ef9692f1-5b18-11e0-ab58-e9fa9c1a789f Waiting for schema agreement... ... schemas agree across the cluster [default@Keyspace1]
Monitor the new cache setting not only for hit rate, but also to make sure that it does not crowd memtables or heap size out of available memory. If you cannot maintain the desired hit rate of 85% or better, add nodes to the system and re-test until you can meet your caching requirements.
You can tune row caching by applying the same approach to raising rows_cached for a column family, but keep in mind: caching large rows can very quickly consume memory. A careful, incremental approach to increasing row cache settings is critical for the health of your Cassandra system. Generally, if row cache hit rates remain below 30%, it may make more sense to disable row caching (it is disabled by default).
Either nodetool cfstats or JMX/jconsole can be used to get the necessary information for estimating actual cache sizes. In addition to the cache sizes reported by JMX (which are counts of row and keys), you should factor in the average key and row sizes plus some amount of overhead from the Java objects.
To approximate the key cache size, multiply the reported ‘Key cache size’ for each column family by the average size of each key + 40 bytes, and sum the results over all column families.
To calculate the approximate row cache size, multiply the reported ‘Row cache size’, which is the number of rows in the cache, by the average size of each row, and sum them.