Declarative caching for AppEngine (part 2)

by radomir on June 2, 2010

In my previous post I’ve shown a simple solution for caching to App Engine’s Memcahe using annotated Java methods. Of course, Memcache should not be the only cache your application relies on, especially considering the latency of RPC call that comes with it. There are many cases when local (server instance) or even request scoped cache can be of use.

So, I iterated over my previous solution and released GJUtil 1.1 today with much more flexible caching structures. I will not go into details here… Please, check out the Caching Annotations for an overview and usage details.

{ 0 comments }

Declarative caching for AppEngine

by radomir on May 20, 2010

I really hate when my source files are bloated with lines and lines of code that do not implement any business logic at all. One of the sources of this was certainly code related to caching. Using Google’s Memcache service with the low-level API or via JCache facade is not complicated but it really was not elegant enough for my taste. I was hoping to find some annotations-based solution that will work for me, but no luck.

So, this appeared as a challenge and, inspired by the Springmodules caching implementation, I decided to research further and see how complicated could it be to implement. On the other hand, I just waited for an excuse to write my first annotation library. :-)

(Note: If I were using Spring, I would probably try to use Springmodules. However, I’ve decided earlier not to use Spring on my GAE project and took a more lightweight path with Stripes.)

Annotation class

I started defining an annotation class that would allow me to tag methods for which I wanted to cache return value:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MemCacheable {
  /** Defines expiration for some seconds in the future. */
  int expirationSeconds() default 0;
  /** Defines whether to cache null values. **/
  boolean cacheNull() default true;
}

The idea was that @MemCacheable on a method would mean that return value of the method should be cached (in Memcache) as long as possible, automatically generating a caching key from the method signature and its arguments. For example:

@MemCacheable
public Result myServiceMethod(Foo arg1, Bar arg2)

However, you could easily override this specifying how long do you want the keep the return value in cache:

@MemCacheable(expirationSeconds=600)
public Result myServiceMethod(Foo param1, Bar param2)

OK, creation of the annotation class was easy but it has no real value without a logic behind it.

Method interception

Next, I needed a solution for a method interception that will allow me to implement around advice for tagged methods. Until that moment, my project didn’t use any dependency injection framework so I decided to use Guice because:

  • it doesn’t force me to change to many things in the project (except few factory classes), and
  • it uses standard AOPAlliance interfaces for advices so, at least theoretically, interceptors could be reused with some other framework too.

Here’s the simplified version of the method interceptor:

public class CachingInterceptor implements MethodInterceptor {
  private MemcacheService memcache;

  public CachingInterceptor() {
    memcache = MemcacheServiceFactory.getMemcacheService();
  }

  public Object invoke(MethodInvocation invocation) throws Throwable {
    Method method = invocation.getMethod();
    MemCacheable memCacheable = method.getAnnotation(MemCacheable.class);
    if(memCacheable != null) {
      return handleMemCacheable(invocation, memCacheable);
    }
    return invocation.proceed();
  }

  private Object handleMemCacheable(MethodInvocation invocation, MemCacheable options) throws Throwable {
    Object key = generateKey(invocation.getThis(), invocation.getMethod(), options.group(), invocation.getArguments());
    Object result = memcache.get(key);
    if (result != null)
      return result;
    result = invocation.proceed();
    putToCache(key, result, options);
    return result;
  }

  protected boolean putToCache(Object key, Object value, MemCacheable options) {
    try {
      if (value == null && !options.cacheNull())
        return false;
      Expiration expires = null;
      if (options.expirationSeconds() > 0)
        expires = Expiration.byDeltaSeconds(options.expirationSeconds());
      MemcacheService.SetPolicy setPolicy = MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT;
      if (options.setAlways())
        setPolicy = MemcacheService.SetPolicy.SET_ALWAYS;
      memcache.put(key, value, expires, setPolicy);
      return true;
    } catch (Throwable t) {
      return false;
    }
  }

  protected Object generateKey(...) {
    // generate key using hash codes of the target method, method arguments, etc.
  }
}

For the key generation I used code from the Springmodules project mentioned above.

Gluing it with Guice

Adding Guice to the project was simple. First, I defined a module that ties caching interceptor with the annotation class:

public class CachingInterceptorsModule extends AbstractModule {
  protected void configure() {
    bindInterceptor(Matchers.any(),
      Matchers.annotatedWith(MemCacheable.class),
      new CachingInterceptor());
  }
}

That’s about it. To instantiate classes that use the caching annotations I created a simple class with the Guice injector:

public class AOP {
  private static final Injector injector =
    Guice.createInjector(new CachingInterceptorsModule());

  public static <T> T getInstance(Class<T> clazz) {
    return injector.getInstance(clazz);
  }
}

Setter annotation (bonus)

After the initial implementation was in place, my appetites were growing… What if I wanted to update the cached value before it expires? I could use something like this:

@MemCacheSetter(group="ResultsCache")
public void cacheResult(Foo param1, Bar param2, Result result)
{}

With the above annotation, I could generate a cache key using all method arguments except the last one, which is the object we’re storing to cache. So, for the above example, we generate a cache key using the group name specified in the annotation and two method arguments (param1 and param2). Simple isn’t it? (We don’t even need the method body as the interceptor will never invoke it.)

This requires small change to the @MemCacheable annotation. To benefit from the setter annotation, we’ll add support for the group argument:

@MemCacheable(group="ResultsCache")
public Result myServiceMethod(Foo  arg1, Bar arg2)

In effect, when the group name is specified, it’s used as a part of a cache key instead of the method signature. In cases when the group name is not provided, the method signature will be used instead.

I pushed the complete project sources and jar to the GJUtil project so you can find it there if you’re interested. The code is stable and already running in my BugDigger application. (If you’re a web developer, and I guess you are if you’re reading this, you may find it useful too.)

A downside of this library is its dependency on Guice for AOP things. I think it would be useful to remove dependency on any framework and inject caching interceptors with bytecode manipulation (e.g. using ASM) as a part of the compilation process. This would enable use of caching annotations with any class, not just those instantiated by dependency injector. If anyone is interested in sponsoring this effort (or maybe contributing some code), let me know.

The next week I’ll extend to the above code and show you how to use the same technique for caching in HTTP request or application context, avoiding RPC calls for the Memcache when local or request scope caching is sufficient.

Update: part 2

public class CachingInterceptor implements MethodInterceptor {private static final Logger log = Logger.getLogger(CachingInterceptor.class.getName());private MemcacheService memcache;public CachingInterceptor() {
memcache = MemcacheServiceFactory.getMemcacheService();
memcache.setNamespace(“GAE Caching Interceptor”);

// added handler because I had some strange errors (“entity too large” although it was not)
// with dev server but not seen since
memcache.setErrorHandler(new LogAndContinueErrorHandler(Level.FINE) {

@Override
public void handleServiceError(MemcacheServiceException thrown) {

log.log(Level.SEVERE, “Memcache service error!”, thrown);

super.handleServiceError(thrown);
}
});
}

public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();

MemCacheable memCacheable = method.getAnnotation(MemCacheable.class);
if(memCacheable != null) {
return handleMemCacheable(invocation, memCacheable);
}
MemCachedSetter setter = method.getAnnotation(MemCachedSetter.class);
if(setter != null) {
return handleMemCachedSetter(invocation, setter);
}
return invocation.proceed();
}

private Object handleMemCacheable(MethodInvocation invocation, MemCacheable options) throws Throwable {
Object key = generateKey(invocation.getThis(), invocation.getMethod(), options.group(), invocation.getArguments());
Object result = getFromCache(key);
if (result != null)
return result;
result = invocation.proceed();
putToCache(key, result, options);
return result;
}

private Object handleMemCachedSetter(MethodInvocation invocation, MemCachedSetter options) throws Throwable {
Object[] args = invocation.getArguments();
if(args.length < 2) {
throw new IllegalArgumentException(“MemCachedSetter annotation requires at least 2 arguments on method ” +
invocation.getMethod());
}
Object key = generateKey(invocation.getThis(), null, options.group(), args, 0, args.length – 1);
// the last argument is an object we’re storing to cache
Object value = args[args.length - 1];

if(putToCache(key, value, options)) {
if(log.isLoggable(Level.FINE))
log.fine(“Stored object to cache group ” + options.group());
}

// do we have a use case for invoking the original method?
return null;
}

protected Object generateKey(Object target, Method method, String key, Object[] methodArguments) {
return generateKey(target, method, key, methodArguments,
0, methodArguments != null ? methodArguments.length : 0);
}

protected Object generateKey(Object target, Method method, String group,
Object[] methodArguments, int beginIndex, int endIndex) {
HashCodeCalculator hashCodeCalculator = new HashCodeCalculator();

if(group != null && group.length() > 0)
hashCodeCalculator.append(group.hashCode());
else if(method != null)
hashCodeCalculator.append(System.identityHashCode(method));

if (methodArguments != null) {
for (int i = beginIndex; i < endIndex; i++) {
Object methodArgument = methodArguments[i];
int hash = 0;
// if (generateArgumentHashCode) {
// hash = Reflections.reflectionHashCode(methodArgument);
// } else {
hash = Objects.nullSafeHashCode(methodArgument);
// }
hashCodeCalculator.append(hash);
}
}

long checkSum = hashCodeCalculator.getCheckSum();
int hashCode = hashCodeCalculator.getHashCode();

Object cacheKey = new HashCodeCacheKey(checkSum, hashCode);
return cacheKey;
}

protected Object getFromCache(Object key) {
try {
Object value = memcache.get(key);
if(log.isLoggable(Level.FINE)) {
if(value != null)
log.fine(“Found in cache key ” + key);
else
log.fine(“Not found in cache key ” + key);
}
return value;
} catch (Throwable t) {
log.log(Level.WARNING, “Error retrieving object from cache.”, t);
return null;
}
}

protected boolean putToCache(Object key, Object value, MemCacheable options) {
try {
if (value == null && !options.cacheNull())
return false;

Expiration expires = null;
if (options.expirationSeconds() > 0)
expires = Expiration.byDeltaSeconds(options.expirationSeconds());

MemcacheService.SetPolicy setPolicy = MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT;
if (options.setAlways())
setPolicy = MemcacheService.SetPolicy.SET_ALWAYS;

memcache.put(key, value, expires, setPolicy);

if(log.isLoggable(Level.FINE)) {
log.fine(“Stored to cache key ” + key);
}
return true;
} catch (Throwable t) {
log.log(Level.WARNING, “Error storing object to cache.”, t);
return false;
}
}

protected boolean putToCache(Object key, Object value, MemCachedSetter options) {
try {
if (value == null && !options.cacheNull())
return false;

Expiration expires = null;
if (options.expirationSeconds() > 0)
expires = Expiration.byDeltaSeconds(options.expirationSeconds());

MemcacheService.SetPolicy setPolicy = MemcacheService.SetPolicy.SET_ALWAYS;

memcache.put(key, value, expires, setPolicy);

if(log.isLoggable(Level.FINE)) {
log.fine(“Stored to cache key ” + key + ” to group ” + options.group());
}
return true;

} catch (Throwable t) {
log.log(Level.WARNING, “Error storing object to cache.”, t);
return false;
}
}

}

{ 5 comments }

My first GAE application… for all web developers and testers

May 19, 2010

I’m proud today as my first GAE application is finally publicly available – BugDigger is entering open beta!
Combined with a browser extension, BugDigger allows users to easily report an issue in a web application. The process is trivial as BugDigger automatically makes a web page screenshot, collects environment details and recent site usage history.

BugDigger talks [...]

Read the full article →

Use GAE Appstats to discover unnecessary RPC calls

April 15, 2010

If you play seriously with Google’s App Engine, I guess you regularly scan an average CPU time per URL and other stats in your GAE Dashboard. I tend to do this especially after adding a new feature. Yesterday I added the first cron job to my new app and today was surprised with an average [...]

Read the full article →

Track estimated cost for your AppEngine site with Firefox add-on

April 6, 2010

If you were careful reading Google App Engine documentation, you could have read this: “If you access your site while signed in using an administrator account, App Engine includes per-request statistics in the response headers. The header X-AppEngine-Estimated-CPM-US-Dollars represents an estimate of what 1,000 requests similar to this request would cost in US dollars.”
Well, although [...]

Read the full article →

Trouble with jQuery form.submit()

March 17, 2010

Argh… I run into an issue with jQuery submitting a form using submit() that took me few hours to resolve. I had a problem submitting a form using a call like $(“#myForm”).submit(). What was driving me nuts was that event listener for “submit” was triggered (no, I didn’t use preventDefault()) but at the end nothing [...]

Read the full article →

Project Lombok annotations for Java

March 15, 2010

Tired of creating getters and setters for your Java POJO’s? Well, I was unti I’ve discovered Project Lombok. It’s definitely a tool worth checking out!
The Project Lombok comes with a set of Java annotations that can generate not only getter and setter methods for your beans, but also toString(), equals() and hashCode() methods. It can [...]

Read the full article →

Objectify

March 5, 2010

For the last few weeks I was working on a Google App Engine application that should be a kind of hosted version of the LogDigger Server. I quickly gave up of using Grails framework (more on that on another occasion) and decided to use plain Java, although I was still undecided on which persistence API [...]

Read the full article →

Stripes

February 17, 2010

Earlier this week I started working on a Google App Engine application. It’s not very complex so I wanted to use some simple presentation framework. (I believe using Spring would be really beneficial in my case.)
After a bit of research on the StackOverflow, I decided to tryout Stripes (“Stripes is a presentation framework for building [...]

Read the full article →

Saved by dnsmasq

February 17, 2010

At my home I have a DSL connection that is shared between my Ubuntu desktop machine (which is my main development machine), laptop and kids’ desktop.  For the last several weeks I was experiencing strange problems with Internet connection, mostly affecting my Ubuntu. The problem was looking like a DNS issue – I was using [...]

Read the full article →