Use a cache
To create high-performance systems, sometimes you need to cache data. Play has a cache library and will use Memcached when used in a distributed environment.
If you don’t configure Memcached, Play will use a standalone cache that stores data in the JVM heap. Caching data in the JVM application breaks the ‘share nothing’ assumption made by Play: you can’t run your application on several servers, and expect the application to behave consistently. Each application instance will have a different copy of the data.
It is important to understand that the cache contract is clear: when you put data in a cache, you can’t expect that data to remain there forever. In fact you shouldn’t. A cache is fast, but values expire, and the cache generally exists only in memory (without persistent backup).
So the best way to use the cache is to repopulate it when it doesn’t have what you expect:
public static void allProducts() {
List<Product> products = Cache.get("products", List.class);
if(products == null) {
products = Product.findAll();
Cache.set("products", products, "30mn");
}
render(products);
}
Modules might configure the cache for you
For example, the GAE module configures the cache to use Google cache when deployed to the GAE servers.
The cacheFor annotation
The cacheFor
annotation provides an easy way to cache the ouput (HTML, JSON, etc) of an action.
import play.cache.*;
public class Application extends Controller {
@CacheFor("5s")
public static void indexCachedInSeconds() {
Date date = new Date();
renderText("Current time is: " + date);
}
@CacheFor("1min")
public static void indexCachedInMinutes() {
Date date = new Date();
renderText("Current time is: " + date);
}
}
The annotation takes an optional duration
parameter to specify the expiration of the cached content.
The expiration can be specified in days, hours, minutes or seconds using respectively the following shorthands d
, h
, min
or s
.
The default expiration is 1h
.
Using expiration 0s
When specifying expiration == “0s” (zero seconds) the actual expiration-time may vary between different cache implementations
h2. The cache API
The cache API is provided by the play.cache.Cache
class. This class contains the set of methods to set, replace, and get data from the cache. Refer to the Memcached documentation to understand the exact behavior of each method.
Some examples:
public static void showProduct(String id) {
Product product = Cache.get("product_" + id, Product.class);
if(product == null) {
product = Product.findById(id);
Cache.set("product_" + id, product, "30mn");
}
render(product);
}
public static void addProduct(String name, int price) {
Product product = new Product(name, price);
product.save();
showProduct(product.id);
}
public static void editProduct(String id, String name, int price) {
Product product = Product.findById(id);
product.name = name;
product.price = price;
Cache.set("product_" + id, product, "30mn");
showProduct(id);
}
public static void deleteProduct(String id) {
Product product = Product.findById(id);
product.delete();
Cache.delete("product_" + id);
allProducts();
}
Some methods start with the safe
prefix – e.g. safeDelete
, safeSet
. The standard methods are non-blocking. That means that when you issue the call:
Cache.delete("product_" + id);
The delete
method will return immediately and will not wait until the cached object is actually deleted. So if an error occurs – e.g. an IO error – the object may still be present.
When you need to make sure that the object is deleted before continuing, you can use the safeDelete
method:
Cache.safeDelete("product_" + id);
This method is blocking and returns a boolean value indicating whether the object has been deleted or not. So the full pattern that ensures an item is deleted from the cache is:
if(!Cache.safeDelete("product_" + id)) {
throw new Exception("Oops, the product has not been removed from the cache");
}
...
Note that those being blocking calls, safe
methods will slow down your application. So use them only when needed.
Also note that when specifying expiration == "0s"
(zero seconds) the actual expiration-time may vary between different cache implementations.
Don’t use the Session as a cache!
If you come from a framework that uses an in-memory Session implementation, you may be frustrated to see that Play allows only a small set of String data to be saved in the HTTP Session. But this is much better because a session is not the place to cache your application data!
So if you have been accustomed to doing things similar to:
httpServletRequest.getSession().put("userProducts", products);
...
// and then in subsequent requests
products = (List<Product>)httpServletRequest.getSession().get("userProducts");
In Play you achieve the same effect a little differently. We think it’s a better approach:
Cache.set(session.getId(), products);
...
// and then in subsequent requests
List<Product> products = Cache.get(session.getId(), List.class)
Here we have used a unique UUID to keep unique information in the Cache for each user. Remember that, unlike a session object, the cache is not bound to any particular User!
Configure memcached
When you want to enable a real Memcached implementation, enable Memcached with the memcached configuration and define the daemon address in the memcached.host configuration.
Continuing the discussion
Learn about Sending emails.