Wednesday, October 13, 2010

Google Checkout and JAXBContext vs. Google AppEngine

 

I am using the Google Checkout Java SDK v2.5 with my app running on Google App Engine. When sending a new order to Google Checkout, the 30 second threshold has been frequently exceeded and stopping people from being able to buy anything.

I spent a number of hours debugging this problem, eventually pinpointing the cause in the Google Checkout code:   a call to JAXBContext.newInstance("com.google.checkout.sdk.domain") in com.google.checkout.sdk.util.Utils.

Sure enough after pinpointing this, googling for JAXBContext and App Engine revealed that this is not an unusual problem for App Engine users. The killer for me is that I want to use the Google Checkout Java SDK, out of the box.

Here is my workaround.  I checked out the source code for the Google Checkout SDK and changed the Utils.java class. First I commented out the context variable, and the code that initialized it. For my strategy, I plan to initialize the JAXB context variable “in advance”, so that it is ready prior to sending out a new order to google checkout.

//  private static final JAXBContext context = makeJaxbContext();
//  private static JAXBContext makeJaxbContext() {
//    try {
//      return JAXBContext.newInstance("com.google.checkout.sdk.domain");
//    } catch (JAXBException e) {
//      throw new RuntimeException(e);
//    }
//  }

 

Now, for the places in Utils.java where “context” is now broken, I replaced it with a “getter” to a singleton that has the JAXB context prepared for me. For example, the fromXML() method now looks like:

public static <T> JAXBElement<T> fromXML(InputStream is) throws JAXBException {
  JAXBContext context = GaeGoogleCheckoutJaxbHelper.INSTANCE.getJaxbContext();
  return (JAXBElement<T>) context.createUnmarshaller().unmarshal(is);
}

 

And here’s the singleton that prepares the JAXB context:

import java.util.logging.Logger;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
public enum GaeGoogleCheckoutJaxbHelper {
  INSTANCE;
  
  private JAXBContext jaxbContext;
  private GaeGoogleCheckoutJaxbHelper() {
    try {
      Logger logger = Logger.getLogger(GaeGoogleCheckoutJaxbHelper.class.getSimpleName());
      logger.info("creating new JAXB context.");
      this.jaxbContext = JAXBContext.newInstance("com.google.checkout.sdk.domain");
      logger.info("done creating new JAXB context.");
    }
    catch (JAXBException e) {
      throw new RuntimeException(e);
    }
  }
  
  public JAXBContext getJaxbContext() {
    return this.jaxbContext;
  }
  
}

 

In my case, I have three steps leading up to a purchase, with the third page being a review page with the Google Checkout button present. When I reach my “step 2” page, I queue a task that calls GaeGoogleCheckoutJaxbHelper.INSTANCE.getJaxbContext() and ignores the result. By the time the user gets to the end of step 3, the JAXB context is awaiting in memory.

This by no means is foolproof. If the user waits *too* long, then App Engine will kick out our app, and we are no better off.

So this will get me by for now while I seek a better solution. I am sad that I may have to roll my own non-JAXB google checkout client. This would be really bad.

Furthermore, I cannot understand why loading that JAXB context takes so long on appengine?! It only took 4 seconds on my wimpy 2GHz machine, yet App Engine consistently takes 20 seconds…!?!?!

This topic relates to being able to control what parts of your app “stay warm” in the JVM, which is a topic circulated elsewhere. I would clearly put my JAXB context stuff there. Google, can we have a “startup” hook for our app’s lifecycle, and keep a few things “warm”?  And can we have a Checkout SDK that integrates with App Engine nicely?

UPDATE 11/2/2010:  I am seeing timeouts on AppEngine when GoogleCheckout is calling me. Unlike the case where the client is about to contact GoogleCheckout, I can’t anticipate when to “warm up” JAXB. I have filed a bug report to the AppEngine folks, though I don’t know what they can do without giving “special treatment” to some classes or libs. I suppose they could just increase the timeout. On the other hand, it really should not take so long for JAXBContext.newInstance() to execute.

2 comments:

  1. It is a very old blog post but I'll write my thouts anyway.

    I use JAXB to serialize and deserialize KML files using JAK (http://labs.micromata.de/display/jak/Home) coupled with the JAXB connector for Restlet on GAE.
    The JAXB connector uses a strategy similar to yours. I've always experienced delays of around 10 seconds due to the JAXB context warmup. For that reason I've deferred all the KML creation to task queues.

    Not sure if you still use GAE and if you've still got this problem (unlikely) but anyway, one way to work around it would be to defer the task of context warmup to a task queue. Not sure how to force the task to be executed by any particular instance and have no experience with backends, that could be another option.

    ReplyDelete
  2. Hey Emanuele, it's interesting to hear how JAXB is still behaving slow on GAE and how you've worked around it.

    In the Google Checkout code, JAXB was behaving slow where it was doing classpath scanning. I suspect Google's classloader security checking gums this up even more. Because I had already gone as far as modifying the Google Checkout sources, I could have tried to substitute the scanning call with some declarative statements to setup all the Checkout objects.

    But around the time I had this problem, Google began offering "always on" for $9/mo. I simply spent the money to solve the problem. Shortly thereafter "always on" was replaced with backends (which I haven't used). Now I use Stripe for payments.

    I am still using GAE, but somedays I feel it is simply an awkward fit for a JVM.

    ReplyDelete