ADF BC Tuning VI: View Links

Now that we’ve looked at tuning entity objects, associations, and, in three parts, view objects, lets look at tuning view links for best performance.

This is going to be a shortish post, because most aspects of tuning view links are similar to tuning associations. For example, in addition to affecting how new rows appear in view object instance result sets, view link consistency affects how new rows appear in view link accessor-returned rowsets, and you can use similar techniques to manipulate these accessors that I told you about for manipulating association accessors. And view links, like associations, can maintain accessor rowsets, with the same advantages and disadvantages of doing so.

But there’s one serious issue that comes up for view links that doesn’t come up for associations: Controlling view link query execution time. This can have such an amazing affect on dealing with bottlenecks in application performance that I’m surprised it isn’t discussed more frequently.

First, the problem: When you select a current row in a master view object instance, all of its detail view object instance’s queries are executed (with the appropriate WHERE clauses added, of course). If you’ve got, say, a single master-detail relationship corresponding to a master-detail page in the UI, this is probably the behavior you want.

But what if, for example, you’ve got an application like the one I’m currently developing: I’ve got a master view object instance with, let’s see…18 detail view object instances. If I let the default behavior go ahead, every time I select a new master row, I’ll be performing 18 queries against the database. And this isn’t even all that bad, as production applications go–I’ve heard of apps with upwards of 100 details for a master.

And obviously, executing 18 queries, much less 100, every time a new row is selected is not ideal if you can possibly avoid it. In my application, those details, while not all on different pages, are scattered among about 8 separate pages. For one thing, not every user is going to look at every page in every session for every master row they select, so some of those queries really don’t need to happen at all. And even if a user does look at every page in every session for every master row they select, I’d really rather have 8 separate 1/2 second delays per master row (as they load each page) than have a single 4 second delay per master row (as they select it). 1/2 second is annoying if it occurs constantly, but it’s not so bad every few minutes. A 4 second delay, even if it’s much rarer, will make your site look unresponsive. And let’s not even talk about a 50 second delay, beyond noting that it will make your site look broken.

Steve Muench has a great solution to this problem on his Not Yet Documented ADF Sample Applications page (it’s #74). Look particularly at the classes in his  demo.model.fwkext package, in particular the exportable service method CustomApplicationModuleImpl.enableDetailsForViewInstances(String[] voInstanceNames). This method works well for his purposes, but I think it can actually be improved on, and I actually think Steve’s application module class contains a small bug. The “improved” method still relies on Steve’s view object framework class, though, so be sure to check it out.

The New Application Module Method

The thing is, I like a bit more control than Steve’s method provides–all the view object instance names you pass to his method are used in all possible ways–as either masters or details of other passed view objects. So the method I’m proposing takes two arguments: a master view object instance name and a list of detail view object instance names–the details being the only view object instances you want to execute the query for until the method is called again with different arguments. You can always call the method multiple times for multiple masters if you want, or even jigger it so that you can pass in multiple masters and multiple details for each. Here’s the method:

public void lazyLoadDetailsForMaster(String master, List<String> detailsToLoad) {
    ViewObjectImpl masterVo = (ViewObjectImpl) findViewObject(master);
    ViewLink[] links = masterVo.getViewLinks();
    for (ViewLink link : links) {
        if (link.getSource() == masterVo) {
        ViewObjectImpl detailVo = (ViewObjectImpl) link.getDestination();
        boolean enable = detailsToLoad != null && detailsToLoad.contains(detailVo.getName());
        RowSetIterator rsi = enableViewLink(masterVo, detailVo, enable);
        if (enable && masterRowDiffersFromDestFilterKeys(rsi, link, detailVo)) {
            detailVo.clearCache();
        }
    }
}

Note that I’m still relying on Steve’s helper method, masterRowDiffersFromDestFilterKeys() (look at his application module framework extension class for that).

I’ve done a number of things differently, though, apart from changing the signature:

  • I’ve factored out the functionality that actually enables/disables view links, into a method, enableViewLink(ViewObjectImpl, ViewObjectImpl, boolean). That makes the code a bit easier to read than the sample code.
  • I’m leaving out the configuration check that Steve does, but you’d probably leave that out in a production version of his app anyway–it’s just there to help you compare lazy loading with immediate loading. 

I’m also about to do some things that don’t show up above.

  • I’m going to implement enableViewLink() a bit differently, and much more simply (yet still, I think, a bit more powerfully), than the sample version’s enabling/disabling functionality.
  • I’m going to use a slightly different Map than the one Steve provides to keep track of disabled row set iterators.

And here’s the Map and the enableViewLink method:

private Map<String[], RowSetIterator> disabledMasterRSIs = new HashMap();
private RowSetIterator enableViewLink(ViewObjectImpl masterVo,
                                      ViewObjectImpl detailVo,
                                      boolean enable) {
    String[] key = new String[] {masterVo.getName(), detailVo.getName()};
    RowSetIterator rsi = disabledMasterRSIs.get(key);
    if (enable) {
        if (rsi != null) {
            detailVo.setMasterRowSetIterator(rsi);

            disabledMasterRSIs.remove(key);
        }
    } else if (rsi == null) {
        RowSetIterator[] masterRSIs = detailVo.getMasterRowSetIterators();
        for (rsi : masterRSIs) {

            if (rsi.getRowSet().getViewObject() == masterVo) {
                detailVo.removeMasterRowSetIterator(rsi);
                disabledMasterRSIs.put(key, rsi);
            }
        }
    }
    return rsi;
}

OK, so what’s different here? Well, for one thing, my map isn’t just from detail VO instance names to row set iterators. It’s from master-detail pairs to row set iterators. Why is that important? Well, it’s kind of a corner case, but if you have a detail VO instance with two masters, and you only keep track of one master RSI per detail VO instance, and you disable both master-detail relationships…well, you’ll get in trouble. Only one master RSI will get stored in the map, and you won’t be able to re-enable the other when you want to.  This fixes that, by allowing different RSIs for different master-detail pairs.

This method also relies a lot more heavily on that map than Steve’s version does–not actually cycling through the detail instance’s RSIs until it first determines that the request is to disable the relationship and that it hasn’t already been disabled. I don’t want to overplay the performance advantage of this–we’re not generally talking about cycling through tens of thousands of RSIs–but I think it makes the code much easier to follow.

Fixing the Bug

The bug I alluded to doesn’t really show up when you run a sample application for your own edification, but I think it will come back to bite you when you go production, unless you deal with it.

The issue is that the application module class relies on maintaining an member variable–disabledMasterRSIs. This works great until the recycle threshold is exceeded, and a user gets a different application module instance than the one they checked in. Sure, most of the application module state will be retrieved during the recycling process (which I’ll talk about lots more next week), but member variables don’t get retained in recycling unless you explicitly provide for it. Like so:

protected void prepareForPassivation(Document document, Element element) {
    super.prepareForPassivation(document, element);
    Iterator<String[]> keysToWrite = disabledMasterRSIs.keySet().iterator();
    for(String[] key : keysToWrite) {
        Element keyElement = document.createElement("disabledVoMapEntry");
        keyElement.setAttribute("master", key[0]);
        keyElement.setAttribute("detail", key[1]);
        element.appendChild(keyElement);
    }
}
protected void activateState(Element element) {
    super.activateState(element);
    NodeList disabledVoMapEntries = element.getElementsByTagName("disabledVoMapEntry");

    for (Node entryNode : disabledVoMapEntries) {
        Element entryElmt = (Element) entryNode;
        ViewObjectImpl master = (ViewObjectImpl) findViewObject(entryElmt.getAttribute("master"));
        ViewObjectImpl detail = (ViewObjectImpl) findViewObject(entryElmt.getAttribute("detail"));
        disableViewLink(master, detail, false);
    }
}

As with any information you want to retain across recycles, you add an element (well, in this case, multiple elements) representing the data you want to save (which master/detail pairs have been disabled–no point saving the particular RowSetIterators, because those objects won’t be valid for a new application module instance) to the application module’s tree while preparing for passivation, and you retrieve that data and process it accordingly (in this case, by re-disabling them) during activation.

 

Like I said, this sort of thing (the whole principle exposed by Steve’s application, not specifically the passivation/activation functionality) can really massively improve and smooth out performance in applications with complex data models. This is probably more important, from a performance perspective, than anything else you might do with view links.

Next week: Application modules.

Edit 1/12: Oh, one last thing. Here’s a replacement for Steve’s Object[] getMasterViewLinkAttrValues(Row, ViewLink) method. Again, it’s a limited case, but this handles cases where the master is not the source (because the view link definition is bi-directional, and you’re using it “backwards” in your data model). Of course, when you call this method, you’ll need to cast the ViewLink to a ViewLinkImpl.

private Object[] getMasterViewLinkAttrValues(ViewRowImpl master,
                                             ViewLinkImpl link) {
    ViewLinkDefImpl vlDef = (ViewLinkDefImpl)((ViewLinkImpl)link).getDef();
    AssociationEnd masterEnd =
        link.isReversedUsage() ? vlDef.getDestinationEnd() :
        vlDef.getSorceEnd();
    AttributeDef[] masterAttrDefs = masterEnd.getAttributeDefImpls();
    Object[] attrValues = new Object[masterAttrDefs.length];
    for (int z = 0; z < masterAttrDefs.length; z++) {
        attrValues[z] = master.getAttribute(masterAttrDefs[z].getName());
    }
    return attrValues;
}

The only difference from Steve’s method of any interest here is that I’m not just assigning masterAttrDefs to be vlDef.getSorceEnd().getAttributeDefImpls(); it might be vlDef.getDestinationEnd.getAttributeDefImpls() depending on whether the particular view link instance is reversed or not.