HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

Performance Management Starts with IDL Design, by Khanh Chau

At the enterprise level, building and deploying distributed object-oriented components involves a dizzying number of choices and considerations. In contrast to a single-process monolithic system, distributed computing provides the flexibility to delegate computing processing power to a large number of nodes, allowing us to build highly complex systems. Coupled with this flexibility are issues that arise from a system's distributed nature.

From a technical perspective, making the transition to n-tier architecture requires risk management of such issues as network latency, system responsiveness, service availability, load management, distributed caching, distributed garbage collection, and system management. Furthermore, for every novel solution to optimize system efficiency, more issues are created that must be addressed. Despite this gloomy outlook, the technical risks in building distributed OO components for large-scale enterprise systems can be managed by using fundamental design techniques.

For instance, by carefully analyzing the problem domain requirements, early design decisions can help minimize network traffic, thus improving overall system responsiveness.

This article outlines a common approach at the Interface Definition Language (IDL) level using an iterator pattern to govern the amount of data passing over the wire. Several issues are also addressed such as caching and distributed garbage collection, both of which can be solved using JDK features.

Performance Issues
Although CORBA abstraction helps shield underlying network infrastructure complexity, it doesn't guarantee the construction of a reliable high-performance system. To achieve some goals, the overall system architecture design must consider the underlying network infrastructure. According to the authors of Enterprise CORBA, three factors affect CORBA-based system performance:

  1. Number of remote invocations
  2. Amount of data transferred
  3. Marshaling costs of different data types

Fortunately these issues can be mitigated if they're anticipated early in the design cycle. Observations indicate that a processing delay occurs when sending data over the wire. If a system design seeks to minimize network traffic caused by interactions among distributed components, the system performance improves accordingly. In CORBA-based systems the IDL plays an important role in component interactions as it defines interfaces in which servants comply. Hence, the logical place for applying design techniques is in the IDL design.

IDL Design
A common IDL design issue that's frequently overlooked is determining which interfaces are candidates for servants, and transient and persistent CORBA objects.

  • A servant is a programming-language-dependent object that implements an object's operations (CORBA 2.4 specs). In the CORBA programming model servants are registered with the Portable Object Adapter (POA), which arbitrates the lifetime of the servants for the requests.
  • In contrast to servants, transient CORBA objects aren't registered with the POA and are usually created by the servant during request processing. These transient objects don't live beyond the life of a process or (sometimes) the thread that created it. Their object references aren't published.
  • Persistent CORBA objects associate with a persistent state and have special uses.
This article focuses on using transient CORBA objects to manage large data transfers. These data-throttling techniques are desirable when the potential exists for discarding data. For example, the user specifies a query that returns a large result. After viewing the first 20 items, another query is made that results in discarding the remaining data. In a single-process application such usage isn't a problem, but in distributed computing it wastes network bandwidth and CPU processing time.

For the basis of discussion, Figure 1 provides a reference of a trivial interaction between a client and a server. The client proxy is a remote proxy class that manages the connection and delegates client application requests to a remote invocation. As indicated, the client makes a remote request to a servant that services the request and returns an array or sequence of product objects. If the result contains n product objects, n elements are marshaled and sent back over the wire. At first glance this approach is acceptable unless the requirement is to remedy undesirables as previously outlined.

Figure 1
Figure  1:

The following code shows an IDL snippet that defines the interaction. The IDL content is straightforward, as the interface contains only one operation. This operation defines the criteria used to return an array of products. We'll return to the IDL shortly after more discussion of the client-side implementation.

module productcatalog
{
struct ProductItem{
string productName;
...
};
typedef sequence Product ItemList;

interface ProductCatalog {
ProductItemList getProductItems(in string group,
in string category, in string status)
raises (SomeRemoteException);
};
};

Assume a remote service is functional. From the client perspective, making a request to retrieve product information involves a few steps (see Figure 2).

Figure 2
Figure  2:

The proxy object provides a wrapper that binds to a specific remote object instance and acts as an intermediary for managing remote invocation. Its responsibilities include:

  • Preparing the request
  • Delegating the request invocation to the stub
  • Trapping remote exceptions (and preferably mapping them to meaningful user-defined exceptions)
  • Preparing the received data structure for an object model that can be readily used by the client application
In the Product Catalog Proxy implementation (see Listing 1), the client proxy is implemented using a singleton pattern. This ensures that only one instance of the client proxy is created. For each request that requires remote invocation, the locateRemoteService() method is called to verify the remote object reference. If the connection is dropped or the object reference is no longer valid, the binding automatically occurs, enabling more resilience to failures. The implementation of locateRemoteService() isn't shown, as the programming model depends on the object location mechanism or is specific to an ORB product.

Now consider a slightly different IDL design that returns a list of product IDs instead of products (see Figure 3). The difference, in terms of network overhead, is that the ID sequence is much smaller in size than a products list. The assumption is that the UI can present the user with a list of product IDs, and the user selects one to return a complete product. This approach is an improvement over the previous one - data isn't wasted because information is transmitted according to user needs.

Figure 3
Figure  3:

One shortcoming of the previous design is determining whether it's acceptable to display product IDs. Keep in mind that retrieving every product by enumerating over the product IDs array results in the same problem previously shown, except it's more severe because now there's n+1 remote invocations.

The next solution (see Figure 4) is to redesign the IDL to take advantage of user behavior and computing constraints. The design change is based on the following observations.

Figure 4
Figure  4:

For most UI applications, such as Swing applications (thick GUI) and Web-based display, a limit exists on how many items are displayed at a time. Screen real estate limits the capability for viewing data. Usually, in the case of a thick GUI, a grid or table is used to hold a list of items.

For a Web-based presentation, search results can be broken down into pages and navigation controls that are used to display information.

In addition, it's safe to assume that given a screen of information the user needs a few seconds to digest results and determine the next action.

Developers can take advantage of the information by installing a mechanism to release the data across the wire as needed. Note: The basis of this need can be triggered by user interaction or autonomously (for example, by a read-ahead algorithm). For many uses, a simple implementation based on a user-initiated request is sufficient. The read-ahead approach adds complexity because it requires an algorithm and a caching mechanism. With this in mind, let's apply the following changes.

First, a mechanism is needed to regulate the flow of information. One way to accomplish this is to employ the iterator pattern. This technique requires rewriting the IDL so that large amounts of information can be broken down into chunks to be served via a remote iterator. To facilitate this implementation, it's beneficial to define the base iterator interface that specifies the behavior of the remote iterator. Listing 2 defines a BaseIterator and a BaseListIterator. This provides the basis for implementing a domain-specific remote iterator such as the ProductIterator as shown in Listing 3. Also notice that the method on the ProductCatalog is modified to return a ProductIterator instead of a Product ItemList.

On the server side the implementation of the ProductIterator plays an integral role in administering the amount of data for transfer. In essence, the ProductIterator is the transient CORBA object that materialized during the getProductItems(...). It's transient in nature because its content depends on a search result. If the result is collected and provided via a collection class such as ArrayList, ProductIterator implementation is simplified because ArrayList provides a ListIterator or Iterator on its content. Another change is in the implementation of the ProductCatalog servant. The getProduct Items(...) returns the ProductIterator object reference instead of an array of product IDs (scenario A) or products (scenario B). Since the product and product ID are CORBA data types and the server data presentation may use an enterprise object model, data mappings are required. By using the ProductIterator, the mappings are deferred to its implementation.

On the client side the proxy implementation fires off the initial request and caches the remote iterator object reference. Optionally, the proxy can immediately fetch the next n items before returning.

Figure 5 outlines the interactions among the components. When the client application specifies the initial search request, the client proxy object performs prerequest work by checking its cache. If no information for the given criteria is available in the cache, a search request is invoked. The result is a remote iterator (a CORBA object reference) that's then stored internally in the client proxy. Subsequent requests by the client application cause retrieval of one product or a block of products. It's optional to maintain the products in the cache area. If caching is used, the internal mechanics may specify that the cache areas are cleaned up when a new search is initiated. Listing 4 presents changes in interesting areas.

Figure 5
Figure  5:

As mentioned before, sometimes a solution can cause more problems. Since the remote iterator is a transient CORBA object, it depends on the servant's lifecycle. A mechanism must be put in place so it doesn't get deactivated until the client is finished. On the other hand, we don't want the iterator to get garbage collected when there's no need. The latter issue relates to distributed garbage collection and can be solved with an eviction scheme. One mechanism is to trigger the remote service via a remote invocation to clean up during the initial request. Another solution is to implement a leasing mechanism via a distributed callback using the timer API, such as the one supplied by the JDK. The proper scheme depends on several design factors such as server caching, load balancing, fault-tolerance capability, static data, and dynamic data.

An interesting application is to apply the iterator approach the other way around. Consider a scenario where the client needs to submit a large block of data (such as an application for home insurance) to the server for storing and processing. It's possible to break up the form into data chunks, allowing the server to process the information in the background, thus freeing the client application for other tasks.

Conclusion
Enterprise system development isn't easy. Decisions made early in the design stage have a pronounced effect on the system's overall responsiveness. In the CORBA environment, an early focus on IDL design is key because a casual design doesn't take advantage of the strengths of the middleware. Creative IDL design, however, can create many issues during implementation time. Via utilization of a JDK API, such as timer and collection framework, developers can apply solutions to those issues that ensure a robust, highly available distributed system.

References

  1. Gamma, E., Helm, R., Johnson, R., and Vlissides, J. (1995). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.
  2. Mobray, J.T., and Malveau, C.R. (1997). CORBA Design Patterns. Wiley.
  3. Object Management Group. (2000). CORBA/IIOP 2.4 Specification.
  4. Slama, D., Garbis, J., and Russell, P. (1999). Enterprise CORBA. Prentice Hall.
Author Bio
Khanh Chau, an infrastructure architect with The Technical Resource Connection, Inc., is also an instructor and project lead for The TRC Java Developer Boot Camp program. He helped design, implement, and deploy a Spine Infrastructure Framework (SIF) Architecture for a major national real estate services company and is currently developing an enterprise e-commerce portal for one of Germany's largest insurance companies. Khanh.Chau@trcinc.com

	

Listing 1: Product Catalog Proxy Implementation

public class ProductServiceProxy {
	private static ProductServiceProxy instance;
	private ProductServiceProxy () {}

	public static ProductServiceClient getInstance() {
   if(instance==null)  instance= new ProductServiceProxy ();
   return instance;
}

public ArrayList getProducts(String group, String category, String status) 
throws SomeException{
	  ArrayList productList = new ArrayList ();
	  try {
	 ProductService productService = locateRemotetService();
		 if ( productService == null ) {
		   // return empty list or throw user defined exception
		 }
		// get product list
		try{
		   // get products using criteria
		  // when request returns data, prepare data and store results in collection object
		} catch(SystemException se){  // handle exception}
	   } catch ( Exception e) { //handle exception }
  return productList;		
}
...
}

Listing 2: Base Iterators in IDL

#include "util/exceptions/idlexceptions.idl"
module iterator
{
	interface BaseIterator {
	  boolean hasNext() raises (SomeRemoteException);
	  short count() raises (SomeRemoteException);
	};
	interface BaseListIterator {
	 boolean hasPrevious() raises (SomeRemoteException);
	  short previousIndex() raises (SomeRemoteException);
	  boolean hasNext() raises (SomeRemoteException);
	  short nextIndex()raises (SomeRemoteException);
	  short count() raises (SomeRemoteException);
	};
};

Listing 3: IDL Design of Service-Specific Iterator

#include "util/iterator/iterator.idl"
module productcatalog
{
	struct ProductItem{
	 string productName;
	...
	};
	typedefsequence<ProductItem> ProductItemList;

	interface ProductIterator : iterator::BaseListIterator {
	   ProductItem next()  raises (SomeRemoteException);
	   ProductItemList nextBlock (in short size) raises (SomeRemoteException);
	  ProductItem previous () raises (SomeRemoteException);
	  ProductItemList previousBlock (in short size) raises (SomeRemoteException);
	};

	interface ProductCatalog {
	   ProductIterator getProductItems(in string group, in string category, in string status)
	     raises (SomeRemoteException);
	};
}

Listing 4: Changes to ProductServiceProxy implementation

public class ProductServiceProxy {
	private int nItems=15;
	private ProductIterator remoteIterator;
	private static ProductServiceProxy instance;
 	private ProductServiceProxy () {}

public static ProductServiceClient getInstance() {
   if(instance==null)  instance= new ProductServiceProxy ();
   return instance;
}

public ArrayList getProducts(String group, String category, String status) 
throws SomeException{
	  ArrayList productList = new ArrayList ();
	  try {
	 ProductService productService = locateRemoteProductService();
		 if ( productService == null ) {
		   // return empty list or throw user defined exception
		 }
		// get product list
		try{
		   remoteIterator=productService.getProducts(...);
		} catch(SystemException se){  // handle exception}
	   } catch ( Exception e) { //handle exception }
  return this.getMoreProducts(nItems);		
}
public ArrayList getMoreProducts(int nItems) throws SomeException{
 	    // retrieve the next 10-15 items using cache iterator
}
...
}


  
 
 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: info@sys-con.com

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.