Pages

Showing posts with label Cache. Show all posts
Showing posts with label Cache. Show all posts

Tuesday, April 19, 2011

QMass, An API for Cluster Wide Operations

I have started a new clustering api for Java. You can access the project page at http://code.google.com/p/qmass/. Very first version is ready to be downloaded.
It's first application is an hibernate cache provider implementation. You can start using it by simply defining org.mca.qmass.cache.hibernate.provider.QMassHibernateCacheProvider as the hibernate cache provider.
By default it will try to cluster applications running on localhost in the port range 6661-6670. To configure QMass you need to set the qmass.cluster property through your persistence.xml. An example persistence.xml might look like this :
<persistence>
<persistence-unit name="app">
...
<properties>
<property name="hibernate.cache.use_second_level_cache"
value="true"/>
<property name="hibernate.cache.provider_class"
value="org.mca.qmass.cache.hibernate.provider.QMassHibernateCacheProvider"/>
<property name="qmass.cluster"
value="localhost,6661,6670/"/>
<property name="qmass.name"
value="myproject"/>
...
</properties>
</persistence-unit>
</persistence>
Cluster definition 10.10.10.112,6661,6670/10.10.10.113,6661,6670/ will define a cluster spanning two different machines with in the port range. qmass.name is the name of your cluster. By setting it you might have two or more clusters running together with out messing with each other.

Tuesday, March 29, 2011

Comparing different cache products

My app. relies heavily on cache to function. Although I use cache on the view, I use it mostly as Hibernate 2nd level cache. Here are some of the products I used and what I think about them.
Ehcache
I think this was used to be called Easy Hibernate Cache and Hibernate people promoted it's use. Now it's owned by Terracotta. I used this quite for a while, it's light, fast and mostly easy to use. However it didn't worked that good as a distributed cache environment.
It has multicast support for peer discovery which works pretty well for most of the time. Except it didn't worked on our prod environment where we have 10 nodes across the network. Some of the nodes just didn't connect to each other which is unacceptable.
It also has manual peer discovery, which needs different ehcache.xml's which would point to other nodes. I think you could tell hibernate, which xml to use at persistence.xml file.
Still I had hard time tracking which node joined to the cluster and which failed to.
Memcached
Main reason I tried memcached was, its use at facebook, wikipedia and such. Memcached is designed a bit different from the ehcache. This is written in 'C', had to be build and run as a daemon process. Cache clients can connect to it through telnet and perform. First problem I ran into was building it. Although It build easily on a Linux environment It required some editing to headers on a HP-UX in order to compile. Second and greater problem is, it doesn't have support for Hibernate. I used the library here http://code.google.com/p/hibernate-memcached/ which is not being actively developed anymore. It also has some nasty bugs...
Hazelcast
Hazelcast is what I use now. At first look it's design looks similar to ehcache but this one is made distribution in mind. It's distributed by default. I have enabled back-up reads, and near-cache settings for better performance.
It comes whit a decent monitoring app. which you can monitor your cluster and see the active nodes.
It's much easier to configure especially compared whit ehcache.

Monday, October 25, 2010

JSF Page Fragment Caching

Problem : Our menu took ages to render.
Our application menu had links to various functions of the apps., 400+ nodes, which needed to be authorized based on EL. JSF has to execute certain EL for each node and decide if its to be rendered and than actually render the menu.
Solution : Cache it.
Simplest solution is to cache your menu. Our menu changes when the users roles changes which doesn't happen all that often so JSF doesn't have to do all that computing and since lots of user share same set of roles they can share the cached menus.
How ?
As far as I am aware Seam has a cache component. I didn't used that and I thought I could do simple component to cache with out much hassle. Here is how the tag looks like;
<mca:cache region="menu" key="#{viewUtility.getMenuKey()}">
... menu goes here ...
</mca:cache>
We need a cache region and cache key. Region will be used to specify what you are caching. Key will identify the value. In this case I used the hash code of the user's roles so that same cached will be used for different users as long as they have the same roles.
Much of the work is done on renderer here;
/**
* User: malpay
* Date: 12.Ağu.2010
* Time: 10:53:06
*/
public class CacheRenderer extends Renderer {

private final static Log log = LogFactory.getLog(CacheRenderer.class);

private CacheManager cacheManager;

public CacheManager getCacheManager() {
if (cacheManager == null) {
cacheManager = (CacheManager) FacesContextUtils.getWebApplicationContext(
FacesContext.getCurrentInstance()).getBean("ehCacheManager");
}
return cacheManager;
}


private void replaceResponseWriter(FacesContext context) {
ResponseWriter rw = context.getResponseWriter();
CacheWriter cw = new CacheWriter(rw);
context.setResponseWriter(cw);
}

private boolean cacheNotUptoDate(CacheComponent cc, Cache cache) {
return cache.get(cc.getKey()) == null;
}

@Override
public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
CacheComponent cc = (CacheComponent) component;
Cache cache = getRegion(cc.getRegion());
if (cacheNotUptoDate(cc, cache)) {
replaceResponseWriter(context);
}
}

@Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
CacheComponent cc = (CacheComponent) component;
if (responseWriterReplaced(context)) {
CacheWriter cw = (CacheWriter) context.getResponseWriter();
String value = updateCache(cc, cw);
context.setResponseWriter(cw.getResponseWriter());
context.getResponseWriter().write(value);
}
}

@Override
public void encodeChildren(FacesContext context, UIComponent component) throws IOException {
CacheComponent cc = (CacheComponent) component;
Cache cache = getRegion(cc.getRegion());
if (cacheNotUptoDate(cc, cache)) {
for (UIComponent child : component.getChildren()) {
child.encodeAll(context);
}
} else {
char[] chars = cache.get(cc.getKey()).getValue().toString().toCharArray();
log.info("rendering cache region : " + cc.getRegion() + "" + cc.getKey());
context.getResponseWriter().write(chars, 0, chars.length);
}
}

private String updateCache(CacheComponent cc, CacheWriter cw) {
Cache cache = getRegion(cc.getRegion());
String value = cw.getValue();
cache.put(new Element(cc.getKey(), value));
return value;
}

private boolean responseWriterReplaced(FacesContext context) {
return context.getResponseWriter() instanceof CacheWriter;
}


private Cache getRegion(String region) {
if (getCacheManager().getCache(region) == null) {
getCacheManager().addCache(region);
}
return getCacheManager().getCache(region);
}

@Override
public boolean getRendersChildren() {
return true;
}
}
What it does is check the cache if its up to date, if not render the children as usually but save the rendered portion of the page and update the cache with it. If the cache is up to date don't render the children, simply wrote the cache on the response.
This works well on my scenario but be aware It may require some adjustments for more general use.