Friday, June 12, 2009

Going live with your Grails app part 1 of X

So I thought I would right up some problem/solutions I had with deploying a Grails app into production.

First step is reducing grails WAR file size.

I have depolyed my latest app http://www.ebookstamper.com on Amazon EC2 (i will do a later step by step post for EC2 deployment). After a few round trips i realised that i was getting pi**ed off with the time it was taking to upload my 20-30meg war file to my server. So after a bit of digging around on the web and some trial and error I devised a way to reduce my WAR size. Here are the steps below:

1) Use Tomcat 6.X
I won't bother giving arguments for using Tomcat 6. Let me just say if you are developing a new app then take it from me this is what you want to be using for your app server. I worked on a large project for the last 12 month getting this website live http://www.carphonewarehouse.com/ and we really squeezed out the performace of Tomcat and the sun JDK1.6 with great results.

2) Set up a shared library in tomcat

Make a directory called shared/lib under tomcat home directory

mkdir -p $TOMCAT_HOME/shared/lib


3) Tell tomcat about this lib

Find the file $TOMCAT_HOME/conf/catalina.properties

Change this line

shared.loader=


to

shared.loader=${catalina.base}/shared/lib,${catalina.base}/shared/lib/*.jar


4) Build your Grails war file

Ok first off you should build your grails war file as normal to get all the jars.

grails war


Or if you going to deploy the app as root context "/" on tomcat do this

grails war ROOT.war


The main point of this exercise is to have grails build a complete WAR file including all the jar librarys you need. These jars come from all over. Grails home/dist, Grails home/lib, plugins and your app lib. So instead of you manually going to find all these jar files just have grails do it for you.

So once you have your WAR file you will have all the jars you need for your app inside the war file in WEB-INF/lib.

5) Copy all the jar files to $TOMCAT_HOME/shared/lib

So unpack the war file and copy all the jar files to the $TOMCAT_HOME/shared/lib directory you created earlier. My simple technique for this was.

a) Upload the ROOT.war file to your server and put it in the $TOMCAT_HOME/webapps directory. This way it is one (a big one) file to upload.

b) Start tomcat and have it deploy your application. This will get tomcat to unpack the war file for you.

c) Stop tomat then go into the unpacked war lib directory $TOMCAT_HOME/webapps/ROOT/WEB-INF/lib and move all the jar files in there to $TOMCAT_HOME/shared/lib. You can then delete the contents of webapps.

6) Next build yourself an empty war file

Now this is the magic bit that i found somewhere out on the web. The --nojars arguement.

grails war --nojars ROOT.war


The --nojars argument will build a grails war file without any jars in the WEB-INF/lib directory in the war file. You should see that this will reduce the war file size by rather pleaseable amount.

7) Redeploy your new lightweight ROOT.war on tomcat
You can then redeploy your new war on tomcat and have a new working app

Next time around you only need to build the war without any jar file.

Now this should be obvious but i will point it out anyway. If you add a new plugin, upgrade grails or add a new lib jar to your app then you should repeat all of this from the start to avoid class not found problems.

Enjoy

12 comments:

nacho said...

Hi, thanks for your post, very helpful.

Just wanted to point out that a war is a simple zip file, so in order to upload your jars you can just unzip it with winzip/winrar locally and ftp WEB-INF/lib/*.jar.

This way you avoid uploading the war, deploying it on tomcat the first time.

Cheers!

johnrellis said...

Cheers for blogging about your groovy/grails experiences, they are proving mighty helpful!
John

Peter Delahunty said...

Hi nacho

Yep I know that about war files. The main point was only having up upload 1 single file ROOT.war rather than 25 jar files. Then i recommended simply using tomcat to unpack it for you as it is easy. However if your server is command line linux like me the you can always call jar -xvf to extract it. But that is pain. Tomcat option is easy.

zack said...

Thanks Peter for putting together your lightweight deployment scenario! Talking about performance optimization for tomcat and java 6, what parameters did you exactly tweak?

Cheers, Niko

Peter Delahunty said...

Hi Zack

As for JDK 1.6 and tomcat optomizations. Unfortunately there is not one simple answer. The tweaks will be different for each app. I suggest you do this:

1) work out what performance you want to get from your app. Reasonable mesurement is Requests per second under a time limit. Say 100 rps under 1-2 seconds.

The best thing to do is to load test your application and monitor everything.

Start tomcat/tomcats with a basic JVM. Then run some load tests.

Most of the optomisation is done in the Garbage collector. Check out these docs for tweeks:

http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html

Basically you want to turn on GC logging and analyse the logs. Then tweak one parameter at a time and test again until you get what you want. Make sure to start doing this early in your project.

Anonymous said...

What does this get you other than a smaller war to deploy, at the cost of a substantially more error prone deployment process.

As far as I understand, this won't affect performance, and will really only save you space if you've got multiple apps under one app server.

I think ...

Anonymous said...

Once you need to upload your .war via DSL, you'll know.
Side note: I upload the .war, open it on the server, copy in the /lib directory from the server into the correct subdir of the war, then deploy. Works like a charm. Keeps lib directories separated between applications on tomcat.

Mike Miller said...

The thing that worries me about this approach is that the war is not really 'portable' at this point. In order to give it to someone else to deploy, you need to go back to the full version with jars, correct?

srinath said...

Hi,

My requirement is to reduce war size from 22MB to ~1MB.
Tried with running "grails war --nojars"

It failed to generate war and showing errors :

[delete] Deleting directory /home/srinath/.grails/1.1.2/projects/public_associations_jan09/stage
Error executing script War: : Directory does not exist:/home/srinath/.grails/1.1.2/projects/public_associations_jan09/stage/WEB-INF/lib
gant.TargetExecutionException: : Directory does not exist:/home/srinath/.grails/1.1.2/projects/public_associations_jan09/stage/WEB-INF/lib
at gant.Gant$_dispatch_closure4.doCall(Gant.groovy:331)
at gant.Gant$_dispatch_closure6.doCall(Gant.groovy:334)


My application contains 4 plugins :

1. hibernate-1.1.2
2. liferay-exploded-0.8
3. portlets-0.7
4. portlets-liferay-0.1

Could you please help me.

thanks.

Peter Delahunty said...

Hi

I am not at a machine with grails on so cannot test this but i suggests you first try to build your war normally.

grails war

To check that all is working normally. Then delete the war.

Then run:

grails war --nojars

If that does not work try giving your war file a name.

grails war --nojars test.war

Remember that you still need all the jars in your classpath on your app server too.

Peter

Marcin said...

Sure the build is less portable but this saves me time deploying; quite a bit of time. When you deploy often (and you should) this technique is very useful. Thanks for the article.

myltik said...

you saved me dude! thanks :-)