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);
}
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(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(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.
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.put(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 in not bound to any particular User!
Configure memcached
When you want to enable a real memcached implementation, enable memcached and define the daemon address in your application.conf:
memcached=enabled
memcached.host=127.0.0.1:11211
You can connect to a distributed cache by specifying multiple daemon addresses:
memcached=enabled
memcached.1.host=127.0.0.1:11211
memcached.2.host=127.0.0.1:11212