by Thomas Davis

Direct network traffic of EJBs

news
Nov 20, 19999 mins

Avoid bottlenecking by encapsulating bean properties into a single object

Before we get into the heart of this article, there are some things to keep in mind. You’re going to need a firm understanding of Enterprise JavaBeans or remote objects in general to follow the discussion here. In order to avoid confusion, the source-code snippets are color-coded : green for the bean, red for the client, and blue for objects that are shared by both.

Network traffic

When dealing with remote objects, such as Enterprise JavaBeans (EJB), some of the biggest performance problems can be traced to network traffic. A method call that has to be marshaled over a socket connection is going to be much slower than a method call issued in local memory. The following piece of client code, which assumes that bean is a handle to an EJB, makes six remote calls (remember, red here stands for client code):

   bean.setFirstName( "Thomas" );
    bean.setLastName( "Davis" );
    bean.setCity( "Boca Raton" );
    bean.setState( "Florida" );
    bean.setZipCode( "33487" );
    bean.setCountry( "United States" );

This code has the potential to be a performance nightmare. Six calls to bean methods means six requests over the network. In the EJB implementations with which I have worked, all of the client-to-EJB communication within a single virtual machine instance takes place through a single socket connection. This is a bottleneck. When one client is talking to a bean, other clients (within the same virtual machine, sharing the single socket) are waiting for their turn. Waiting is bad. You want to avoid remote calls whenever possible.

Property encapsulation

One solution to this problem, the solution I am going to promote in this article, is to encapsulate the bean properties into a single object, and then send that entire object back and forth between client and server (over the network). This lets you put all of the get/set methods into the transported object and have them executed within the client’s virtual machine — in local memory rather than over the network. The properties object must be serializable in order to be transported over the network, and it should be as compact as possible to ensure the fastest possible transfer rate. Here is an example that encapsulates the properties that the above code modified (note that it’s blue because it is shared between the client and the bean):

public class BeanProperties implements java.io.Serializable { private String firstName, lastName, city, state, zipCode;

public void setFirstName( String firstName ){ this.firstName = firstName; }

// more 'set' methods for the other string values... }

Using this technique, you can reduce the previous client example to only two remote calls. The first call retrieves the properties object from the bean, while the second sends the properties object back to the bean:

BeanProperties props = bean.getProperties(); // remote call #1

props.setFirstName( "Thomas" ); props.setLastName( "Davis" ); props.setCity( "Boca Raton" ); props.setState( "Florida" ); props.setZipCode( "33487" ); props.setCountry( "United States" );

bean.setProperties( props ); // remote call #2

This code is going to run more quickly due to the reduced amount of network calls. And any other processes that are sharing the same network connection will also run more quickly because they won’t be waiting as often for this client to relinquish the socket.

Synchronization issues

The properties encapsulation is a major improvement in performance, but it comes at a price — we now have a synchronization issue. If two clients are working with the same bean, it is possible for the properties to get out of sync. I’ll demonstrate this momentarily.

When a client requests the properties from the bean, the client actually receives a copy of the properties object. If a single client requests the properties object twice, it will receive two different instances of that object.

Observe the following code, explained below:

   BeanProperties propsA = bean.getProperties();
    BeanProperties propsB = bean.getProperties();
    propsA.setFirstName( "Edward" );
    propsB.setState( "Ohio" );
    bean.setProperties( propsA );
    bean.setProperties( propsB );
    BeanProperties propsC = bean.getProperties();
    System.out.println( propsC.getFirstName() ); // "Thomas" !!!

The client retrieves a copy of the bean’s properties and assigns them to propsA, then immediately requests the properties again and assigns them to propsB. The client now has two copies of the bean’s properties.

The client modifies the first name value in propsA, then changes the state value of propsB. Now each copy of the properties is unique — one has a different first name and the other contains a different value for the state.

Next, the client sends the propsA copy back to the bean. This means that the remote bean now has a first name value of "Edward". But then the client sends up the propsB copy. Now the bean has the state set to "Ohio"but the value for the first name has been reset to its original value! Why? Because propsB was a copy of the properties, and the first name was never changed for it. When we sent the propsB version back to the bean, the propsA version was overwritten; thus, the change to the first name was lost.

This is exactly what can happen when two different clients start modifying the properties of the same bean. This is called a race condition. Each client is essentially racing the other to update the bean’s properties.

Synchronization solutions

If all of the clients were running in a single virtual machine, we could use the synchronization keyword in a strategic manner to ensure that only one client accessed the remote bean’s properties at a time. However, the clients will most likely be running on completely different machines, and thus not within the same virtual machine.

One way to tackle this is to have the bean timestamp the properties object and check that timestamp each time the properties get passed back from the client. If the timestamps don’t match up, in the case of the client passing back an old copy of the properties, then a race condition has occurred. Let’s add some more logic to the BeanProperties class to facilitate a timestamp:

private long timestamp;

public void updateTimestamp(){ timestamp = System.currentTimeMillis(); }

public long getTimestamp(){ return timestamp; }

Now the bean can brand the properties object with the current time. This will be done as soon as the object is created (which happens in the ejbLoad() method for beans):

private BeanProperties props;

public void ejbLoad() throws RemoteException { // perform normal bean stuff, including loading state from database...

props = new BeanProperties(); // initialize properties based on database query...

props.updateTimestamp(); }

Now the bean can compare the timestamp of its local properties with the properties copies that are sent back from the client (via the setProperties() method). If the timestamps do not match, then there was a synchronization issue resulting from a race condition. Rather than apply the changes, and possibly lose data by overwriting the newer properties, the bean can throw an exception. Here’s how the setProperties() method might look:

public void setProperties( BeanProperties newProps ) throws RemoteException, RaceConditionException {
        if( newProps.getTimestamp() != props.getTimestamp() )
        {
            throw( new RaceConditionException( "Client attempted to apply old properties!" ) );
        }
        else
        {
            props = newProps;
            props.updateTimestamp();
        }
    }

Addendum: November 5, 1999

In theory, there are two problems with this solution. I’ll discuss both problems and then provide a uniform solution. First, the bean could have been passivated after the properties were retrieved and then reactivated when the properties were returned. In this case, the time stamp would be reset, and the client would receive a false race condition exception. Thanks to reader Mike Klumpenaar for pointing this out. Second, the ejbLoad() method is supposed to execute before every client-called method. In all of the EJB servers that I have evaluated, the container is intelligent enough to not call the load method unless the bean is dirty. However, because the code should be cross-platform and in compliance with the EJB specification, I should have accounted for such a scenario. Thanks to reader Richard Monson-Haefel, author of Enterprise JavaBeans (O’Reilly & Associates, 1999), for pointing this out. The solution to both of these problems is to store the time stamp in the database along with the bean’s state. This approach ensures that time stamp survives bean passivation/activation and precursory calls to the ejbLoad() method.

This solution requires that the client deal with all synchronization conflicts. In this particular case, the client has two options. If there is any user interaction, it can inform the user of the problem and query for its next action (for example, cancel or retry). If there is no user interaction, the client will have to retrieve the properties (the latest version of them) again from the bean, reapply the changes, and finally resend the properties back to the bean. Of course, it is possible that another client has outraced this client yet again, so this client may have to repeat this procedure until it finally succeeds. Here’s an example of such a loop:

boolean bad = true;

while( bad ){ BeanProperties props = bean.getProperties();

props.setFirstName( "Thomas" ); // more properties setting...

try { bean.setProperties( props ); bad = false; } catch( RaceConditionException e ) { // we have to loop again } }

Intelligence on the bean side

In order to relieve the stress of the client and avoid the possible looping scenario discussed in the last paragraph, we can add a little intelligence to the bean. The bean can figure out which properties have changed since the last time setProperties() was called in comparison to the current time. If there are no direct conflicts between different parts of the properties object being modified, the bean can apply the new changes, leaving the client none the wiser. This solution, however, is very complex, and, upon closer scrutiny, becomes a virtual Pandora’s box. This exact issue is common in building object-relational mappings for databases — and fortunately, the topic is out of the scope of this article.

Optimize your beans

Encapsulating your EJB’s properties into a serializable object for passing back-and-forth with the client buys you a performance gain and clean encapsulation. However, the clients must be aware of the race conditions and be able to recover when they occur. With careful planning, this technique goes a long way toward optimizing your beans. As always, comments, criticisms, and, especially, design improvement suggestions are welcome.

Thomas E. Davis is a Sun Certified Java Developer. He lives in sunny southern Florida, but spends most of his time in front of the computer. He has spent the past three years designing and building large-scale, multitier distributed applications in Java.