TITLE
    WebObjects 4: Performance Tips
Article ID:
Created:
Modified:
70102
9/21/99
10/4/99

TOPIC

    Aggressive schedules and the quick pace of web time don't always leave developers with much time for performance optimization. This article is a collection of practical tips on speeding up your WebObjects application after most of your code has been written. This performance tuning information has been gathered while assisting our developers in support, and from in-house projects and testing. This article concentrates on the I/O processing between your application and the database; we'll also tell you about some of the features of WebObjects 4 that you might have missed.


DISCUSSION

    Measure your application

    In a WebObjects application, there are four basic sources of response time delays. Ordered with the most expensive first, they are:

      • I/O & processing between your application and the database
      • WOF request/response handling
      • Custom application action handling
      • I/O between browser, web server, and Application
    Of course, the source of performance delays in deployed application will vary from project to project. This article focuses on database I/O; however, before you spend a lot of time fine-tuning your app, we recommend measuring your application to see where your bottlenecks are. You may find problems where you least expect them; sometimes adding hardware is the best solution to a performance problem.

    How much speed do you need?

    You can get a rough estimate of your application's ability to generate pages by running one instance and observing the response time of your pages. Run through what you think is a typical session, using the WOStats page to tell you the relative speed of each page in your application. Run through several sessions and subtract out the times for the first hit on any page to allow for the one-time overhead in HMTL/wod parsing. Inverting the resulting average response time will give you a pages per second measurement. For an app that is CPU limited, this figure can serve as an estimate of the total page throughput that your app can deliver. If it is greater than the expected load for your app by an order of magnitude or more, you probably don't need to worry about further performance tweaks.

    For a more reliable performance estimate, WebObjects 4 provides the WOPlaybackManager. This lets you create a few typical scenarios and let them run simultaneously against your server. You can start them all up to warm up the application, then reset your WOStats and let them run for a few minutes. For more information on WOPlaybackManager, please see the WebObjects documentation.

    Add timing code

    In addition to WOStats, consider adding timing code to your Application object to establish timing for the different phases in the request/response loop. The timing for the methods takeValues and appendToResponse relates to WebObjects, while the timing for invokeAction measures your code. This is usually where fetching happens. If invokeAction is taking a long time, the first place to look is at your For an example of timing the request/response loop, use the WOAssociationTiming framework; for details, see TIL article 70105 .

    Optimize your fetches

    To test the performance of your fetches, run your application with EOAdaptorDebugEnabled set to YES. This will give you an idea of how much SQL you are sending on a given page. Keep in mind that the Object Uniquing feature in EOF means that previously fetched data won't get fetched again when navigating the same relationships. For each page, look at the SQL generated by each hit after the warmup phase.

    Pay special attention to any SQL that looks like a fault firing. Faults are a convenient feature in EOF, but incrementally firing several of them can slow down your app significantly. If you can identify that some of the faults are being fired for most requests, try prefetching them instead. If the usage pattern is not clear, the "batch faulting" feature might be a better choice. Both prefetching and batch faulting are documented in the Enterprise Objects Framework Developer's Guide.

    Looking at the SQL should also help you determine if performance problems occur during fetching or during saving. Web apps tend to do mostly fetching, but if you are regularly saving data, this could also cause a performance hit.

    Set a fetch limit

    With the EOFetchSpecification API, fetch limits can be set on an application-wide basis or for each fetch.

    Use one global editing context or share display groups

    Using a global editing context, instead of the default of one editing context per session, will reduce the session size and speed up your app if you need to archive sessions. Having one shared editing context will also speed up the retrieval of read-only data.

    Here is one way to implement a globally shared editing context in Objective C. Put this code in your Application.m file. If the shared global editing context does not exist, this method creates a new one. At the same time, a global fetch limit is set to 20. Please note that the "fetchLimit" here can be stored as a user default and can be changed in a convenient way. When a session needs to refer to the editing context, it can do the following:

    ec = [(Application *) WOApp sharedGlobalEditingContext];

    Remember to send a release message to your ivar _sharedGlobalEditingContext in Application's dealloc method to avoid leaks.

    int globalFetchLimit;
    - (EOEditingContext*)sharedGlobalEditingContext {
    if (!_sharedGlobalEditingContext) {
    EOObjectStore* anOS = [EOEditingContext  defaultParentObjectStore];
    _sharedGlobalEditingContext = [[EOEditingContext alloc] initWithParentObjectStore:anOS];
    globalFetchLimit = [[NSUserDefaults standardUserDefaults] integerForKey:@"fetchLimit"];
    if (!globalFetchLimit)
    globalFetchLimit = 20;
    return _sharedGlobalEditingContext;
    }

    In addition, you might want to share WODisplayGroups in the same session, instead of putting DisplayGroups on every page. If you are displaying the same objects on multiple pages, you won't need to refetch them to get the same result set again or a subset of the original result set.

    Cache the fetch results

    If one page is slow and is accessed very frequently, you may want to add some code to cache recent fetches. If a fetch is repeated, the database will not be accessed the second time. A sample implementation of such a cache is provided in the download package that accompanies this article, as EOKeyValueCache.[hm]. In addition, the Application class needs to implement the EODatabaseContext delegate methods:

    -databaseContext: shouldFetchObjectsWit hFetchSpecification: editingContext:

    and

    -databaseContext: didFetchObjects: fetchSpecification: editingContext:

    to keep the cache up-to-date. Please note that this type of cache is primarily for read-only data, or data that changes infrequently.
    Use raw rows

    If you need to fetch very large amounts of data, you may want to use raw row fetches. This is a new feature in WebObjects 4; the API is documented in EOUtilities. You can also set raw mode fetching directly in the fetch specification using EOModeler. If you use raw row fetching, you will no longer be able to access to-many relationship information or use uniquing, but most fetches will execute at least twice as fast. Fetching objects with multiple relationships, or only fetching a subset of your attributes, will result in even greater speed improvements.

    EOF has API to convert raw rows into enterprise objects, so once you have identified the objects you need, it is easy to put them into the editing context. To convert a raw row to an EO in Objective C, use the method:

    eo = [globalEditingContext faultForRawRow:anObject entityName:@"Product"];

    Cache primary key sequence values

    If you notice poor performance on your Insert or Save Page, you may want to implement a better primary key generation mechanism to reduce the number of round trips to the database. One possibility is to use a batch of primary keys, instead of generating them each time.

    For example, you can modify the SQL for the Oracle SEQUENCE to hand out sequences incremented by 100. Your application will grab a range of 100 primary keys and hand them out as needed. In addition, you will need to implement the EODatabaseContext delegate method databaseContext: newPrimaryKeyForObject:entity: in your Application.m file to return the proper primary key in case of a fast save. Be aware that if your application is restarted regularly, there will be gaps in the range of your primary keys.

    Tune your database queries

    Use the RDBMS "Explain Plan" functionality to get the exact execution plan, and verify that all of your queries are using indexes. If you are doing a case insensitive comparison, the database must do a full table scan and you will not be able to use indexing. If you are using Oracle and doing a LIKE comparison, you will need to turn off bind variables for that query and make sure that you aren't passing in a pattern that begins with a wildcard; otherwise you will not be able to use indexing. In addition, never mark a BLOB as used for locking. Instead of marking all attributes as used for locking, mark one attribute, preferably a 'date changed' attribute.

    To turn off the use of bind variables, you can do something like:

    [EOSQLExpression setUseBindVariables:NO];

    Another possibility, on the MacOS X Server platform, is to set the user default named EOAdaptorUseBindVariables. For example, at a shell prompt, type:

    defaults write NSGlobalDomain EOAdaptorUseBindVariables NO

    Compile or wrap your EO's

    If you have a few Enterprise Objects that are accessed a lot, moving them to Objective C can help. Consider compiling these objects and creating Java wrappers from them. You can add some code to your accessor methods to help determine which enterprise objects, if any, should be converted to Objective C. For example:

    static int firstNameAccessorCount = 0;
    public String firstName() {
    if ((++ firstNameAccessorCount % 1000) == 0)
    System.out.println("firstName accessor called " +
    firstNameAccessorCount + " times");
    return firstName;
    }

    Of course, if you find that some enterprise objects or keys are being accessed very often, you should try to reduce the number of times they get called as well as wrapping them to speed up access.

    Take advantage of EOModeler caching

    With the EOModeler Advanced Entity Inspector, you can mark an entity as "cache in memory". EOF will evaluate queries against that entity in memory and avoid round trips to the database. This is most useful for read-only entities, since there is no danger that the cached data will get out of sync with the database data. Since EOF will keep all of the entity's objects in memory, this technique should only be used for small tables.

    Use EOModeler to create fetch specifications

    If you have an EOFetchSpecification object that will be used frequently, use EOModeler to create and store it in the model file, instead of re-creating it programmatically each time you perform a fetch.


Document Information
Product Area: WebObjects
Category: WebObjects 4
Sub Category: Deployment

Copyright © 2000 Apple Computer, Inc. All rights reserved.