Andy Grove's Blog

Wednesday, 4 June 2008

Is Maven the right choice for your project?

Over the past 6 months or so I have been working with some projects that have a Maven2 build system and other projects that use plain Ant build systems. I love the concepts behind Maven and see tremendous value in standardizing the build process across projects as well as having a better way of managing dependencies on third party jars without storing them in each project's source repository.

However, there are some pitfalls to be aware of before adopting Maven which may or may not be issues depending on your requirements.

First of all, adopting Maven will typically require a Maven server (repository) to be set up and maintained. For some smaller development departments this creates an extra IT support burden that can be avoided with an Ant build system.

Another potential issue is that third party projects might not have good support for Maven. For instance, another Apache project, Axis2, recently released version 1.4 but there is an open support ticket AXIS2-3069 regarding the Maven2 java2wsdl plugin which does not seem to be in a working state yet. The ticket has not been updated since 2007. Sure, the problem can be worked around by calling Ant from within the Maven2 project, but that's adding another layer of complexity compared to a standard Ant project.

Another issue that I have hit is that Maven2 has poor error reporting when it is unable to resolve a dependency. I eventually tracked my issue down to a java keystore / server certificate error but Maven didn't provide any hints that this was the problem, even with debug flags set.

Overall, I'm still a fan of Maven2 and I think the combination of Maven and Ant is extremely powerful but in my recent experience it does add extra cost to the development process.

Labels:


Wednesday, 10 October 2007

SimpleDateFormat and Thread Safety

It never fails to surprise me that so many developers are unaware that SimpleDateFormat is not thread-safe. It seems like almost all J2EE projects I work on have code that uses instance variables or static instance variables to store a SimpleDateFormat that is then used throughout the code base without any concurrency control. Here’s a classic example:
public class MyService // could be a web service or a session bean
{
public void someBusinessMethod(String datestring, ...) {
Date date = GlobalConst.DATE_FMT.parse(datestring);
// rest of code omitted
}
}
This is particularly nasty because the code may well run without producing any exceptions but could easily parse the date incorrectly. If you don't believe how likely this is, here's some code to prove the point:
public class ProveNotSafe {
static SimpleDateFormat df
= new SimpleDateFormat( "dd-MMM-yyyy" );
static String testdata[] = {
"01-Jan-1999", "14-Feb-2001", "31-Dec-2007"
};
public static void main(String[] args) {
Runnable r[] = new Runnable[ testdata.length ];
for (int i = 0; i < r.length; i++) {
final int i2 = i;
r[i] = new Runnable() {
public void run() {
try {
for (int j=0; j<1000; j++) {
String str = testdata[i2];
String str2 = null;
/*synchronized(df)*/ {
Date d = df.parse(str);
str2 = df.format( d );
}
if (!str.equals(str2)) {
throw new RuntimeException(
"date conversion failed after "
+ j + " iterations. Expected "
+ str + " but got " + str2 );
}
}
} catch (ParseException e) {
throw new RuntimeException( "parse failed" );
}
}
};
new Thread(r[i]).start();
}
}
}

When I run this code, I get the following output:
java.lang.RuntimeException: date conversion failed after 3 iterations.
Expected 14-Feb-2001 but got 01-Dec-2007
Note that "01-Dec-2007" isn't even one of the strings in the test data. It is actually a combination of the dates being processed by the other two threads!

If I uncomment the synchronized keyword then of course it runs without exception.

If I want to quickly remove these issues from a code base I usually create a simple replacement class like this that wraps SimpleDateFormat.

public class ThreadSafeSimpleDateFormat {

private DateFormat df;

public ThreadSafeSimpleDateFormat(String format) {
this.df = new SimpleDateFormat(format);
}

public synchronized String format(Date date) {
return df.format(date);
}

public synchronized Date parse(String string) throws ParseException {
return df.parse(string);
}
}

Labels:


Monday, 3 September 2007

JPerf - Performance Testing for Java

I've been researching some ideas in my spare time over the past couple of weeks where performance is critical so I decided to put together a framework for easily running performance and scalability tests. The idea is to provide a framework that is as easy to use as JUnit and that encourages performance testing as an integral part of the development process, rather than as an afterthought before a product release or deployment.

I've called the framework JPerf and I have made the code available on sourceforge - it's just a few classes at the moment but I will be evolving this as and when I need the functionality myself.

The key interface is PerfTest which provides setUp(), test() and tearDown() methods. The test runners call setUp() once and then repeatedly call test() for the specified period of time and then call tearDown() at the end to free up resources, such as database connections.

There are currently two test runners - PerfTestRunner and ScaleTestRunner, which measure performance and scalability, respectively. Here's some sample output from the example that ships with JPerf.

PerfTestRunner:

[java] org.jperf.example.SimpleDateFormatTest: 243,971 iterations in 500ms (486,968 iterations per second)
[java] org.jperf.example.MyDateFormatTest: 371,968 iterations in 500ms (742,451 iterations per second)

ScaleTestRunner:

[java] With 1 client threads, throughput is 100.0 tps and memory usage is 5137656
[java] With 2 client threads, throughput is 202.0 tps and memory usage is 5175144
[java] With 3 client threads, throughput is 300.0 tps and memory usage is 5223616
[java] With 4 client threads, throughput is 402.0 tps and memory usage is 5271592
[java] With 5 client threads, throughput is 504.0 tps and memory usage is 5330056
[java] With 6 client threads, throughput is 602.0 tps and memory usage is 4888336
[java] With 7 client threads, throughput is 702.0 tps and memory usage is 4979288
[java] With 8 client threads, throughput is 798.0 tps and memory usage is 5057864
[java] With 9 client threads, throughput is 900.0 tps and memory usage is 5137568
[java] With 10 client threads, throughput is 1000.0 tps and memory usage is 5215200


I'd be interested to hear ideas on what features people would like to see in this framework to make it useful for their projects. If you have any requirements please leave a comment here or post a feature request on sourceforge.

Labels: ,


Wednesday, 15 August 2007

Waking up to Hibernate, Part 2

I've been busy building out a fairly realistic test application for the past couple of days so I can really get a handle on Hibernate's performance and scalability. The application is a book store e-commerce site very loosely based on Amazon. I'll make the code available once it's complete.

I've defined a stateless session bean interface with the business methods e.g. login, findBooks, createOrder, addBookToOrder, and so on. I currently have two implementations of the bean - one that uses JDBC DAOs and one that uses Hibernate. I'm considering implementing another version that uses Spring JDBC as well.

I've developed a tool that can populate the database with configurable amounts of random data so that the tests are representative of real-world data volumes.

I then have one standard multi-threaded test client that will work with any of these bean implementations. I'm hosting the beans in JBoss on a dedicated server so that each implementation will be using the same connection pool and transacation management to try and make the tests as fair as possible.

The initial results are showing poor scalability with Hibernate and I have a thread open on the Hibernate forum where I'm looking for guidance on my configuration and approach in case I'm not using Hibernate correctly. At this stage it's also possible I have bugs in my code so I'm not going to post any figures until I'm confident that my tests are correct.

I'm starting a new contract tomorrow so I'll have to put this work on hold for a couple of days but hopefully I'll be able to post some stats at the weekend.

Labels: ,


Monday, 13 August 2007

Waking up to Hibernate

Those of you who know me will be well aware of my bias towards the DAO approach to writing Java persistence code but I'm currently spending time getting to grips with Hibernate. As a freelance developer, Hibernate is now an essential skill to list on my resume due to it's widespread use. It's easy to see why Hibernate is so popular - it allows database driven applications to be developed using natural idiomatic Java code and doesn't require volumes of tedious code to be written to a low level API like JDBC.

My only real concern with Hibernate is the overhead it adds to an application compared to using JDBC directly but I don't have any empirical evidence to back this up. Also, Hibernate could potentially have better performance than JDBC because, as a runtime framework, there are opportunities for caching data. Ultimately, as with most technology comparisons, I expect that both Hibernate and plain JDBC code have particular use cases that they are best suited to.

I'm currently putting together a test application to try and understand the performance characteristics of Hibernate compared to low level JDBC code. I'm also using this as a means to learn Hibernate.

I'll post the results here as I get them.

Labels: ,