Wednesday, August 4, 2010

Implementing an efficient Id generator with Spring framework / Java

Unique identity generators are heavily used in the enterprise applications. Most often applications rely on the database features, such as sequences or auto incremented columns. Unfortunately, that requite extra trips to the database and as a result have impact on performance. The good news that there are few simple optimizations that can be used to significantly reduce this overhead. Let's see how we can use Spring framework to assemble high performance id generator. I'll introduce a basic abstraction for id generator. Note a "granularity" parameter, that will be explained shortly.

public interface IdGenerator {


public long getNextId(int granularity);

}

A naïve in-memory IdGenerator implementation may look something like one below and it would perform extremely well in a multi threaded environment.

public class SimpleIdGenerator implements IdGenerator {


private AtomicLong id = new AtomicLong(0L);

public long getNextId(int granularity) {
return id.getAndAdd(granularity);
}
}

In a real world application, generators rely on the id feed generated by relational database database. That affects performance because of transactional requirements and round trips to the database. As mentioned above, those issues can be worked around using well known optimizations that are used by application server providers and supported by most of the database vendors. So, instead of requesting new id from the database on every call, we can request a range of ids and save on database calls. Here is when we can make use of the "granularity" parameter passed to the getNextId() method. This logic can be factored out into a separate component and applied to the original generator using Spring AOP or delegation/proxy pattern. Using spring XML configuration we can assemble these beans together:

  id="idGenerator" class="org.javatx.spring.BatchingIdGenerator">

ref="simpleIdGenerator"/>


id="simpleIdGenerator" class="org.javatx.spring.SimpleIdGenerator"/>

A very naïve implementation of BatchingIdGenerator may look something like this.

public class BatchingIdGenerator implements IdGenerator {


private IdGenerator generator;
private int batchSize = 100;

private long current = 0;
private long max;

public BatchingIdGenerator(IdGenerator generator) {
this.generator = generator;
}

public void setBatchSize(int batchSize) {
this.batchSize = batchSize;
}

public long getNextId(int granularity) {
current
+= granularity;
if(current>max) {
current
= generator.getNextId(batchSize);
max
= current + batchSize;
}
return current;
}

}

At minimum, above IdGenerator implementation require synchronization on getNextId() method in order to work properly in multi threaded environment. Unfortunately, that will introduce locking contention and will not perform well under load. However there is a simple solution for this issue. With Spring 2.0 we can use custom bean scope for idGenerator bean. If we make this custom scope bound to the thread, it will completely eliminate needs for locking, because each thread will use its own batch of ids. I opened JIRA issue SPR-2581 to provide "out of the box" implementation for the thread scope in Spring framework. In a mean time the thread-bound scope can be implemented using ThreadLocal.

public class ThreadScope implements Scope {


private final ThreadLocal threadScope = new ThreadLocal() {
protected Object initialValue() {
return new HashMap();
}
};

public Object get(String name, ObjectFactory objectFactory) {
Map scope = (Map) threadScope.get();
Object object = scope.get(name);
if(object==null) {
object = objectFactory.getObject();
scope
.put(name, object);
}
return object;
}

public Object remove(String name) {
Map scope = (Map) threadScope.get();
return scope.remove(name);
}

public void registerDestructionCallback(String name, Runnable callback) {
}
}

The last piece of this puzzle is to assemble everything in Spring config.

  xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"
>

class="org.springframework.beans.factory.config.CustomScopeConfigurer">
name="scopes">

key="thread" value="org.javatx.spring.ThreadScope"/>




id="idGenerator" class="org.javatx.spring.BatchingIdGenerator" scope="thread">
ref="simpleIdGenerator"/>



id="simpleIdGenerator" class="org.javatx.spring.SimpleIdGenerator"/>


Custom "thread" scope is registered using CustomScopeConfigurer factory bean and then assigned to the idGenerator bean using "scope" attribute in the bean definition. Also note that "aop:scoped-proxy" element is used to wrap bean into the scope-aware proxy in order to make it work when idGenerator bean is injected into the other beans as dependency.

No comments:

Post a Comment