WebCenter Sites 12c Performance with Asset Reader API and Groovy

image
WebCenter Sites 12c introduces a number of new APIs to use when building out your sites. The developer documentation puts an emphasis on using an MVC approach that is aided by the new groovy controllers and reader APIs including the new asset reader API. Using an MVC approach to build out a site allows for a nice and clean separation of your business logic from the display logic. You can see the official documenation on all the APIs at http://docs.oracle.com/middleware/1221/wcs/wcs-develop.htm and also at http://hostname:port/sites/samples/overview if you have a 12c installation setup already.
 
I wanted to see how the performance of these new APIs compares to using the APIs from the previous 11g version of the product. I also wanted to see if using groovy in places causes any performance hit in 12c as it did in the past on 11g. I focused on the API calls for reading assets. I'll describe what I found and include the code I used below. In each case I ran 4 tests, the first two made calls to the older AssetDataManager api and the last two made calls with the new asset reader api. The first call to each API loads and entire asset and the second reads just 2 fields from the asset. A different asset was used in each of the 4 calls. I also swapped around which asset was loaded in each call to see if that made any difference and it didn't. The samples use assets from the AVISports sample site in 12c. The actual numbers you'll get if you run these tests will of course be affected by your installation and environment but the differences should be similar.
 
For the first test I created a new groovy controller using the sites admin interface and a new page template. In the template edit screen you can select the controller to use if any. I then created a page and set its template to my new template. I averaged the results over 5 runs and here are the results.
 
Case 1: Using the old (AssetAPI) API - reading the full asset:  18 ms
Case 2: Using the old (AssetAPI) API - reading 2 fields:            7 ms
Case 3: Using the new (Reader) API - reading the full asset: 78 ms 
Case 4: Using the new (Reader)API - reading 2 fields:          17 ms
 
Both APIs were faster when loading specific fields of an asset over reading everything.
Here is the code I used for the controller.
package oracle.webcenter.sites.controller


import com.fatwire.assetapi.data.*

import com.openmarket.xcelerate.asset.*

import com.fatwire.assetapi.fragment.*

import com.fatwire.system.*

import org.slf4j.Logger

import org.slf4j.LoggerFactory

import java.util.List

import java.util.ArrayList

import COM.FutureTense.Interfaces.*

import com.fatwire.cs.core.db.PreparedStmt

import com.fatwire.cs.core.db.StatementParam

import COM.FutureTense.Util.IterableIListWrapper

import com.fatwire.assetapi.common.AssetAccessException


public class ArticlePageController extends BaseController

{

static final private Logger log = LoggerFactory.getLogger("oracle.wcsites.test.article")


    @RequiredParams(query="c,cid")

    public void doWork(Map models)

    {

     log.debug("Article Page Loading...")     

     runArticleTest()

    }


public AssetId findArticleByName(String name) throws AssetAccessException {

     AssetId result = null

     

     PreparedStmt stmt = new PreparedStmt("select id from AVIArticle where name like ?", Arrays.asList("AVIArticle"))

     stmt.setElement(0, "AVIArticle", "id")

     StatementParam param = stmt.newParam()

     param.setString(0, "%" + name + "%")

     IList records = ics.SQL(stmt, param, false)

for (IList record : new IterableIListWrapper(records)){

long id = Long.parseLong(record.getValue("id"))

result = new AssetIdImpl("AVIArticle", id)

}     

     return result

    }

    

    public void runArticleTest(){

     // read in avisports articles for test

     log.debug("Initializing")

     long startTime

     long endTime

     Session wcsession = SessionFactory.getSession();

     AssetDataManager adm = (AssetDataManager)wcsession.getManager(AssetDataManager.class.getName())

     

     AssetId testArticle1 = findArticleByName("Veteran Skier Says Goodbye")

     List<AssetId> assetsToGet = new ArrayList<AssetId>()

     assetsToGet.add(testArticle1)

     

     log.debug("test 1 - reading asset")

     startTime = System.nanoTime()

     adm.read(assetsToGet)

     endTime = System.nanoTime()

     log.debug("test 1 completion time: " + String.valueOf((endTime-startTime) / 1000000 ))

ics.StreamEvalBytes("test 1 completion time: " + String.valueOf((endTime-startTime) / 1000000 ))

     

     // second test read just the fields we need from an asset

     AssetId testArticle2 = findArticleByName("Skier Makes Her Mark")

     log.debug("test 2 - reading specific fields")

     startTime = System.nanoTime()

     adm.readAttributes(testArticle2, Arrays.asList("name", "body"))

     endTime = System.nanoTime()

     log.debug("test 2 completion time: " + String.valueOf((endTime-startTime) / 1000000))

ics.StreamEvalBytes("<br>test 2 completion time: " + String.valueOf((endTime-startTime) / 1000000))

     

     // third test, read using selectAll

     AssetId testArticle3 = findArticleByName("25 Nevada Resorts Serving Snow")

     log.debug("test 3 - reading asset new api")

     startTime = System.nanoTime()

     newAssetReader().forAsset(testArticle3).selectAll(true).read()

     endTime = System.nanoTime()

     log.debug("test 3 completion time: " + String.valueOf((endTime-startTime) / 1000000))

ics.StreamEvalBytes("<br>test 3 completion time: " + String.valueOf((endTime-startTime) / 1000000))

     

     // fourth test, read using select specific fields

     AssetId testArticle4 = findArticleByName("Ski Castle and Snowboard Wizards Unite")

     log.debug("test 4 - reading specific fields new api")

     startTime = System.nanoTime()

     newAssetReader().forAsset(testArticle4).select("name,body").read()

     endTime = System.nanoTime()

     log.debug("test 4 completion time: " + String.valueOf((endTime-startTime) / 1000000))

ics.StreamEvalBytes("<br>test 4 completion time: " + String.valueOf((endTime-startTime) / 1000000))

    }

}
 
For a second test I ported the groovy code from the controller into a JSP to see if the performance of the API would be any different from there. The results were similar.
 
Case 1: Using the old (AssetAPI) API - reading the full asset:  17 ms
Case 2: Using the old (AssetAPI) API - reading 2 fields:            6 ms
Case 3: Using the new (Reader) API - reading the full asset: 70 ms 
Case 4: Using the new (Reader) API - reading 2 fields:          15 ms
 
Similar to the first test in groovy but the numbers seem to be just a little better. The end result of the new asset reader API being slower by about the same margin holds.
Here is the code for the JSP. To recreate this just create a new template for a page and paste in the code. Then setup a test page and set it to use the template.
 
<%@ taglib prefix="cs" uri="futuretense_cs/ftcs1_0.tld"

%><%@ taglib prefix="ics" uri="futuretense_cs/ics.tld"

%><%@ taglib prefix="render" uri="futuretense_cs/render.tld"

%><%@ taglib prefix="fragment" uri="futuretense_cs/fragment.tld"

%><%@ page import="com.fatwire.assetapi.data.*,

  com.openmarket.xcelerate.asset.*,

  com.fatwire.assetapi.fragment.*,

  com.fatwire.system.*,

  org.slf4j.Logger,

  org.slf4j.LoggerFactory,

  java.util.List,

     java.util.ArrayList,

  COM.FutureTense.Interfaces.*,

  com.fatwire.cs.core.db.PreparedStmt,

  com.fatwire.cs.core.db.StatementParam,

  COM.FutureTense.Util.IterableIListWrapper,

  com.fatwire.assetapi.common.AssetAccessException,

  java.util.Arrays" 

%><cs:ftcs><%-- Page/PerformanceTest2 --%>

<%-- Record dependencies for the Template --%>

<ics:if condition='<%=ics.GetVar("tid")!=null%>'><ics:then><render:logdep cid='<%=ics.GetVar("tid")%>' c="Template"/></ics:then></ics:if>


<%!

static final private Logger log = LoggerFactory.getLogger("oracle.wcsites.test.article");

private BuildersFactory builder;


public AssetId findArticleByName(ICS ics, String name) throws AssetAccessException, NoSuchFieldException {

AssetId result = null;


PreparedStmt stmt = new PreparedStmt("select id from AVIArticle where name like ?", Arrays.asList("AVIArticle"));

stmt.setElement(0, "AVIArticle", "id");

StatementParam param = stmt.newParam();

param.setString(0, "%" + name + "%");

IList records = ics.SQL(stmt, param, false);

for (IList record : new IterableIListWrapper(records)){

long id = Long.parseLong(record.getValue("id"));

result = new AssetIdImpl("AVIArticle", id);

}     

return result;

}


public void runArticleTest(ICS ics, BuildersFactory builer) throws AssetAccessException, NoSuchFieldException{

// read in avisports articles for test

log.debug("Initializing");

long startTime;

long endTime;

Session wcsession = SessionFactory.getSession();

AssetDataManager adm = (AssetDataManager)wcsession.getManager(AssetDataManager.class.getName());


AssetId testArticle1 = findArticleByName(ics, "Veteran Skier Says Goodbye");

List<AssetId> assetsToGet = new ArrayList<AssetId>();

assetsToGet.add(testArticle1);


log.debug("test 1 - reading asset");

startTime = System.nanoTime();

adm.read(assetsToGet);

endTime = System.nanoTime();

log.debug("test 1 completion time: " + String.valueOf((endTime-startTime) / 1000000 ));

ics.StreamEvalBytes("test 1 completion time: " + String.valueOf((endTime-startTime) / 1000000 ));


// second test read just the fields we need from an asset

AssetId testArticle2 = findArticleByName(ics, "Skier Makes Her Mark");

log.debug("test 2 - reading specific fields");

startTime = System.nanoTime();

adm.readAttributes(testArticle2, Arrays.asList("name", "body"));

endTime = System.nanoTime();

log.debug("test 2 completion time: " + String.valueOf((endTime-startTime) / 1000000));

ics.StreamEvalBytes("<br>test 2 completion time: " + String.valueOf((endTime-startTime) / 1000000));


// third test, read using selectAll

AssetId testArticle3 = findArticleByName(ics, "25 Nevada Resorts Serving Snow");

log.debug("test 3 - reading asset new api");

startTime = System.nanoTime();

builder.newAssetReader().forAsset(testArticle3).selectAll(true).read();

endTime = System.nanoTime();

log.debug("test 3 completion time: " + String.valueOf((endTime-startTime) / 1000000));

ics.StreamEvalBytes("<br>test 3 completion time: " + String.valueOf((endTime-startTime) / 1000000));


// fourth test, read using select specific fields

AssetId testArticle4 = findArticleByName(ics, "Ski Castle and Snowboard Wizards Unite");

log.debug("test 4 - reading specific fields new api");

startTime = System.nanoTime();

builder.newAssetReader().forAsset(testArticle4).select("name,body").read();

endTime = System.nanoTime();

log.debug("test 4 completion time: " + String.valueOf((endTime-startTime) / 1000000));

ics.StreamEvalBytes("<br>test 4 completion time: " + String.valueOf((endTime-startTime) / 1000000));

}

%>


<%


try {

builder = new DefaultBuildersFactory(ics);

runArticleTest(ics, builder);

} catch(Exception ex){

log.error("error running tests");

}


%>


</cs:ftcs>
For a final test I wanted to see if there is any difference between executing an element written as a jsp vs one in groovy. I created 2 new cs elements in the admin interface and put in versions of the code from before. I'll put the listings below. I also created another template as a JSP that simple does a ics:callelement to either of the 2 elements.
 
I averaged the template by reloading it 10 times for each element.
Groovy element: 123 ms
JSP element:       119 ms
 
Template:
 
<%@ taglib prefix="cs" uri="futuretense_cs/ftcs1_0.tld"

%><%@ taglib prefix="ics" uri="futuretense_cs/ics.tld"

%><%@ taglib prefix="render" uri="futuretense_cs/render.tld"

%><%@ taglib prefix="fragment" uri="futuretense_cs/fragment.tld"

%><cs:ftcs><%-- Page/PerformanceTest3 --%>

<%-- Record dependencies for the Template --%>

<ics:if condition='<%=ics.GetVar("tid")!=null%>'><ics:then><render:logdep cid='<%=ics.GetVar("tid")%>' c="Template"/></ics:then></ics:if>


<%

long startTime = System.nanoTime();

%>

<ics:callelement element="PerformanceTest/JSPTest"></ics:callelement>

<%

long endTime = System.nanoTime();

ics.StreamEvalBytes("<br>completion time: " + String.valueOf((endTime-startTime) / 1000000));

%>


</cs:ftcs>
Groovy Element
 
import com.fatwire.assetapi.data.*

import com.openmarket.xcelerate.asset.*

import com.fatwire.assetapi.fragment.*

import com.fatwire.system.*

import org.slf4j.Logger

import org.slf4j.LoggerFactory

import java.util.List

import java.util.ArrayList

import COM.FutureTense.Interfaces.*

import com.fatwire.cs.core.db.PreparedStmt

import com.fatwire.cs.core.db.StatementParam

import COM.FutureTense.Util.IterableIListWrapper

import com.fatwire.assetapi.common.AssetAccessException

import java.util.Arrays


new PerformanceTest(ics)


public class PerformanceTest {

Logger log = LoggerFactory.getLogger("oracle.wcsites.test.article");

BuildersFactory builder;


AssetId findArticleByName(ICS ics, String name) throws AssetAccessException, NoSuchFieldException {

AssetId result = null;


PreparedStmt stmt = new PreparedStmt("select id from AVIArticle where name like ?", Arrays.asList("AVIArticle"));

stmt.setElement(0, "AVIArticle", "id");

StatementParam param = stmt.newParam();

param.setString(0, "%" + name + "%");

IList records = ics.SQL(stmt, param, false);

for (IList record : new IterableIListWrapper(records)){

long id = Long.parseLong(record.getValue("id"));

result = new AssetIdImpl("AVIArticle", id);

}     

return result;

}


void runArticleTest(ICS ics, BuildersFactory builer) throws AssetAccessException, NoSuchFieldException{

log.debug("running groovy tests")

        // read in avisports articles for test

log.debug("Initializing");

long startTime;

long endTime;

Session wcsession = SessionFactory.getSession();

AssetDataManager adm = (AssetDataManager)wcsession.getManager(AssetDataManager.class.getName());


AssetId testArticle1 = findArticleByName(ics, "Veteran Skier Says Goodbye");

List<AssetId> assetsToGet = new ArrayList<AssetId>();

assetsToGet.add(testArticle1);


log.debug("test 1 - reading asset");

startTime = System.nanoTime();

adm.read(assetsToGet);

endTime = System.nanoTime();

log.debug("test 1 completion time: " + String.valueOf((endTime-startTime) / 1000000 ));

ics.StreamEvalBytes("test 1 completion time: " + String.valueOf((endTime-startTime) / 1000000 ));


// second test read just the fields we need from an asset

AssetId testArticle2 = findArticleByName(ics, "Skier Makes Her Mark");

log.debug("test 2 - reading specific fields");

startTime = System.nanoTime();

adm.readAttributes(testArticle2, Arrays.asList("name", "body"));

endTime = System.nanoTime();

log.debug("test 2 completion time: " + String.valueOf((endTime-startTime) / 1000000));

ics.StreamEvalBytes("<br>test 2 completion time: " + String.valueOf((endTime-startTime) / 1000000));


// third test, read using selectAll

AssetId testArticle3 = findArticleByName(ics, "25 Nevada Resorts Serving Snow");

log.debug("test 3 - reading asset new api");

startTime = System.nanoTime();

builder.newAssetReader().forAsset(testArticle3).selectAll(true).read();

endTime = System.nanoTime();

log.debug("test 3 completion time: " + String.valueOf((endTime-startTime) / 1000000));

ics.StreamEvalBytes("<br>test 3 completion time: " + String.valueOf((endTime-startTime) / 1000000));


// fourth test, read using select specific fields

AssetId testArticle4 = findArticleByName(ics, "Ski Castle and Snowboard Wizards Unite");

log.debug("test 4 - reading specific fields new api");

startTime = System.nanoTime();

builder.newAssetReader().forAsset(testArticle4).select("name,body").read();

endTime = System.nanoTime();

log.debug("test 4 completion time: " + String.valueOf((endTime-startTime) / 1000000));

ics.StreamEvalBytes("<br>test 4 completion time: " + String.valueOf((endTime-startTime) / 1000000));

}


public PerformanceTest(ICS ics){

    try {

    builder = new DefaultBuildersFactory(ics);

    runArticleTest(ics, builder);

    } catch(Exception ex){

log.error("error running tests");

        log.error(ex.getMessage())

        log.error(Arrays.toString(Thread.currentThread().getStackTrace()))

    }

}


}
 
 
JSP Element
 
<%@ taglib prefix="cs" uri="futuretense_cs/ftcs1_0.tld"

%><%@ taglib prefix="ics" uri="futuretense_cs/ics.tld"

%><%@ page import="com.fatwire.assetapi.data.*,

  com.openmarket.xcelerate.asset.*,

  com.fatwire.assetapi.fragment.*,

  com.fatwire.system.*,

  org.slf4j.Logger,

  org.slf4j.LoggerFactory,

  java.util.List,

     java.util.ArrayList,

  COM.FutureTense.Interfaces.*,

  com.fatwire.cs.core.db.PreparedStmt,

  com.fatwire.cs.core.db.StatementParam,

  COM.FutureTense.Util.IterableIListWrapper,

  com.fatwire.assetapi.common.AssetAccessException,

  java.util.Arrays" 

%><cs:ftcs>


<%!

static final private Logger log = LoggerFactory.getLogger("oracle.wcsites.test.article");

private BuildersFactory builder;


public AssetId findArticleByName(ICS ics, String name) throws AssetAccessException, NoSuchFieldException {

AssetId result = null;


PreparedStmt stmt = new PreparedStmt("select id from AVIArticle where name like ?", Arrays.asList("AVIArticle"));

stmt.setElement(0, "AVIArticle", "id");

StatementParam param = stmt.newParam();

param.setString(0, "%" + name + "%");

IList records = ics.SQL(stmt, param, false);

for (IList record : new IterableIListWrapper(records)){

long id = Long.parseLong(record.getValue("id"));

result = new AssetIdImpl("AVIArticle", id);

}     

return result;

}


public void runArticleTest(ICS ics, BuildersFactory builer) throws AssetAccessException, NoSuchFieldException{

// read in avisports articles for test

log.debug("Initializing");

long startTime;

long endTime;

Session wcsession = SessionFactory.getSession();

AssetDataManager adm = (AssetDataManager)wcsession.getManager(AssetDataManager.class.getName());


AssetId testArticle1 = findArticleByName(ics, "Veteran Skier Says Goodbye");

List<AssetId> assetsToGet = new ArrayList<AssetId>();

assetsToGet.add(testArticle1);


log.debug("test 1 - reading asset");

startTime = System.nanoTime();

adm.read(assetsToGet);

endTime = System.nanoTime();

log.debug("test 1 completion time: " + String.valueOf((endTime-startTime) / 1000000 ));

ics.StreamEvalBytes("test 1 completion time: " + String.valueOf((endTime-startTime) / 1000000 ));


// second test read just the fields we need from an asset

AssetId testArticle2 = findArticleByName(ics, "Skier Makes Her Mark");

log.debug("test 2 - reading specific fields");

startTime = System.nanoTime();

adm.readAttributes(testArticle2, Arrays.asList("name", "body"));

endTime = System.nanoTime();

log.debug("test 2 completion time: " + String.valueOf((endTime-startTime) / 1000000));

ics.StreamEvalBytes("<br>test 2 completion time: " + String.valueOf((endTime-startTime) / 1000000));


// third test, read using selectAll

AssetId testArticle3 = findArticleByName(ics, "25 Nevada Resorts Serving Snow");

log.debug("test 3 - reading asset new api");

startTime = System.nanoTime();

builder.newAssetReader().forAsset(testArticle3).selectAll(true).read();

endTime = System.nanoTime();

log.debug("test 3 completion time: " + String.valueOf((endTime-startTime) / 1000000));

ics.StreamEvalBytes("<br>test 3 completion time: " + String.valueOf((endTime-startTime) / 1000000));


// fourth test, read using select specific fields

AssetId testArticle4 = findArticleByName(ics, "Ski Castle and Snowboard Wizards Unite");

log.debug("test 4 - reading specific fields new api");

startTime = System.nanoTime();

builder.newAssetReader().forAsset(testArticle4).select("name,body").read();

endTime = System.nanoTime();

log.debug("test 4 completion time: " + String.valueOf((endTime-startTime) / 1000000));

ics.StreamEvalBytes("<br>test 4 completion time: " + String.valueOf((endTime-startTime) / 1000000));

}

%>


<%


try {

builder = new DefaultBuildersFactory(ics);

runArticleTest(ics, builder);

} catch(Exception ex){

log.error("error running tests");

}


%>


</cs:ftcs>

Conclusion

Based on what I found doing these tests it appears there is no real difference in performance between creating elements in groovy instead of using java in JSPs.  That is a significant improvement over the performance of groovy elements over 11g. The new asset reader API does appear to be quite a bit slower than using the older AssetDataManager from 11g but it has some benefits. It does reduce the amount of code needed to interact with your assets and the data returned can be put into the model in the MVC framework rather than having to be manipulated or transformed first. In fact some of the differences in performance may be made up for depending on what needs to be done with the asset data after being loaded using the AssetDataManager. The best API to use may not be the same in every situation but it does appear that the older 11g API may be needed if your page isn't performing fast enough under the new ones from 12c.

Subscribe to Our Newsletter

Stay In Touch