ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Using a Request Filter to Limit the Load on Web Applications
Pages: 1, 2

Not All Requests Should be Serialized

The request queue is usually valuable for business transactions, which acquire expensive application resources, such as database queries or communication with other remote applications. However, this request queue need not be used to handle all requests. The designs of some applications mean that users will make multiple concurrent requests. For example, if an application creates pop-up windows, the user's browser will request the contents of a pop-up window at the same time as the contents of a new main page, all within the same session. Similarly, applications that encourage users to open links in new windows or tabs will have this same problem.



In addition to those examples, you would not want this filter to interfere with:

  • Requests for images and other static resources, because frequently, web browsers will request them concurrently.
  • Concurrent requests for different frames.
  • Multi-threaded robots spidering a web site.

We decided to implement selective request filtering by using regular expressions to match request paths. From our experience, we find that we want the filter to process most requests, so the patterns match paths that should be excluded from the queue. These requests are processed immediately, regardless of which other requests the server is handling.

Matching regular expressions to the context path means that the design of the context paths must consider the load control filter. For example, you might want all of your pop-up windows to load from a context path that starts with /popup/, so that the filter can exclude them. Likewise, if you are serving static images through your application server, you could make sure that their path starts with something like /images/ that can easily be matched with a regular expression.

The patterns to exclude are configured in the web.xml file as initialization parameters to the filter. Here is an example configuration that will prevent the filter from interfering with requests for images on a particular web site:

<filter>
  <filter-name>requestControlFilter</filter-name>
  <filter-class>RequestControlFilter</filter-class>
  <init-param>
    <param-name>excludePattern.1</param-name>
    <param-value>/images/.*</param-value>
  </init-param>
</filter>

Our filter reads this configuration on initialization, and pre-compiles all of the expressions. At the beginning of the doFilter() method, we quickly compare the request's context path to all of the patterns. If there is a match, the filter hands processing down the chain immediately instead of putting it in the queue.

There are other ways we could decide which requests to queue and which to exclude. For example, the application could keep state in the session to indicate whether the user will make multiple concurrent requests.

Relaxing the Filter to Only Queue Requests For a Few Seconds

Because this filter only allows the server to process one request at a time, users will have to wait for each request to complete before the server starts working on the next one. This can be frustrating to users who have changed their minds and requested another page. In the earlier example, the user needs to wait for the server to complete the headline article request before it processes the request for the sports article. If the online newspaper includes a healthy discussion board with many comments posted about the headline, then waiting potentially fifteen seconds for the server to finish rendering the page can be painful for the user.

We got around this problem by implementing a time limit for requests waiting in the queue. This means that if the server does not complete a request within a set amount of time, the filter allows the server to start working on the next request waiting in the queue. By default, the delay is five seconds.

We give up some of the benefits of synchronized requests by only holding the queue for a short time, and must once again worry about concurrent access to shared session objects. Since a new request could be processed every five seconds per session, users can still add to the load on a server, but the impact is not as high as if they could continuously create new requests. Also, pages that take more than five seconds to complete should rarely exist, if we expect the users to be happy.

The queue timeouts are configured in the web.xml file as parameters of the filter. You can specify a value (in seconds) for different context paths. Again, the paths are regular expressions as in this example, which limits wait time to four seconds for paths that start with /test/:

<filter>
  <filter-name>requestControlFilter</filter-name>
  <filter-class>RequestControlFilter</filter-class>
  <init-param>
    <param-name>excludePattern.1</param-name>
    <param-value>/images/.*</param-value>
  </init-param>
  <init-param>
    <param-name>maxWaitMilliseconds.4000.1</param-name>
    <param-value>/test/.*</param-value>
  </init-param>
</filter>

Much like the exclude pattern, these patterns are also loaded and pre-compiled when the filter is initialized. As requests are placed into the queue, the compiled patterns are matched against the requested path to determine the timeout. We use the Object.wait() and Object.notify() synchronization methods, which allow us to specify a timeout. This is the implementation of our waitForRelease() method, which handles the timeouts:

// wait for the currently running request to
// finish, or until this thread has waited the
// maximum amount of time
try
{
  getSynchronizationObject( session ).wait(
    getMaxWaitTime( request ) );
}
catch( InterruptedException ie )
{
  return false;
}

// This request can be processed now if it hasn't
// been replaced in the queue
return (request ==
    session.getAttribute(REQUEST_QUEUE));

Experience

Our experience with this filter has been a success in our environment, which handles a moderate size of user transactions and a high volume (10,000/minute) of machine-to-machine transactions. Of course, the design of our web application guarantees that the same server handles all requests within a session. We also do not worry about multi-threaded spiders, because our application is mostly dynamic and not meant to be indexed by search engines. Your configuration may be different, but we still believe that you will find this filter to be a valuable addition to your toolbox.

Ivelin Ivanov is a frequent contributor to the open source community and has recently participated in projects that include Apache Cocoon, JBoss, GNU XQuery and Jakarta Commons.

Kevin Chipalowsky is the principal software engineer and sole proprietor of Far West Software.


Return to ONJava.com.