Posted by: Marco Fabbri | January 7, 2012

Warmup dynacache with a script

Sometimes you need to quickly fill dynacache entries using a script after invalidating the whole cache contents or after a staging propagation.

There are two main issues to solve in a Commerce cluster (horizontal and vertical): fill the entries in every JVM in every node using the store hostname and do it concurrently.

To do this we used an Ant script recycling WCBD configuration.

The script called project-warmup-dynacache.test.xml is very simple.

First it loads the WCBD properties configuration (project-test.properties and project-test.private.properties) with a new custom property

cluster.members=node1.store.com:9080,node1.store.com:9085,node2.store.com:9080,node2.store.com:9085

The Ant script cycle into these cluster clone hosts

<target name=”createCategoryURLs”>
<foreach list=”${cluster.members}”
target=”warmupCategorias”
param=”clone.host”
trim=”true”
inheritall=”true”
inheritrefs=”true”>
</foreach>
</target>

generating a file with category’s URLs using a SQL Ant task

<target name=”warmupCategorias”>
<sql driver=”${jdbc.driver}”
url=”${jdbc.url}”
userid=”${db.user.name}”
password=”${db.user.password}”
classpath=”${jdbc.driver.path}”
output=”categoryURLList.${clone.host}.txt”
print=”true”>select DISTINCT ‘url = “http://${clone.host}/webapp/wcs/stores/servlet/Category3_10151_’ || cata.catalog_id || ‘_’||CATESPADRE.CATGROUP_ID||’_-5_Y_image_0_____”‘ from catgroup catespadre, catgrprel rela, catgroup cateshija, catalog cata, store st, cattogrp catrel where CATESPADRE.CATGROUP_ID = RELA.CATGROUP_ID_PARENT and CATESHIJA.CATGROUP_ID = RELA.CATGROUP_ID_CHILD and CATA.CATALOG_ID = CATREL.CATALOG_ID and CATA.identifier = ‘Extended Sites Catalog Asset Store’ and ST.DIRECTORY = ‘MadisonsESite’ and CATESPADRE.MARKFORDELETE &lt;&gt; 1 and CATESPADRE.LASTUPDATE &gt; to_timestamp (’09/12/2011′,’DD/MM/YYYY’) UNION select DISTINCT ‘url = “http://${clone.host}/webapp/wcs/stores/servlet/Category3_10151_’ || cata.catalog_id || ‘_’||CATESPADRE.CATGROUP_ID||’_-5_Y_image_0___’||CATESHIJA.CATGROUP_ID||’__”‘ from catgroup catespadre, catgrprel rela, catgroup cateshija, catalog cata where RELA.CATALOG_ID = CATA.CATALOG_ID and CATA.identifier = ‘Extended Sites Catalog Asset Store’ and CATESPADRE.CATGROUP_ID = RELA.CATGROUP_ID_PARENT and CATESHIJA.CATGROUP_ID = RELA.CATGROUP_ID_CHILD and CATESPADRE.MARKFORDELETE &lt;&gt; 1 and CATESPADRE.LASTUPDATE &gt; to_timestamp (’09/12/2011′,’DD/MM/YYYY’);</sql>

Then with an exec task, the Ant script executes a single line sh script (for categories and products)

curl –header “Host: test.store.com” –header “\$WSSP: 80″ -K /opt/IBM/WC/wcbd/deploy/server/$1 -o “/opt/IBM/WC/wcbd/deploy/server/$2″ >/dev/null 2>&1

This curl command takes the category URLs like

http://node1.store.com:9080/webapp/wcs/stores/servlet/Category3_10151_11551_17510_-1_Y_image_0_____

changes the Host header to “test.store.com” and adds the websphere port header $WSSP (just like the plugin do).

Changing these http headers we solved the problem to fill cache entries for a specific JVM in a specific node using the store hostname “test.store.com”.

Finally we execute the curl script asynchronously using a specific sh script

<exec executable=”antRunAsync.sh” failonerror=”yes”>
<env key=”ANTRUN_NOHUP” value=”true” /> <!– optional –>
<env key=”ANTRUN_OUTPUT” value=”/opt/IBM/WC/wcbd/deploy/server/categoryURLListOutput.${clone.host}.txt” /> <!– required –>
<arg value=”/opt/IBM/WC/wcbd/deploy/server/getCategoriesCurl-test.sh categoryURLList.${clone.host}.txt categoryURLListOutput.${clone.host}.txt” />
</exec>

This way we can fill cache contents in every JVM at the same time.

To execute the script we use the WCBD directory

/opt/IBM/WC/wcbd/deploy/server/wcbd-ant -buildfile /opt/IBM/WC/wcbd/deploy/server/project-warmup-dynacache.test.xml

Posted by: Marco Fabbri | November 1, 2011

Activate Websphere Commerce Data Cache

Caching contents on WC is not only a cachespec.xml configuration task.

Another powerful feature is to cache database query results using Websphere Commerce data cache.

There are two ways to cache database query results: using cachespec.xml or configuring specified DistributedMap instances.

Probably you already know how to cache those contents caching the related commands.

For example if you want to cache the catalog entry data you need to add to your cachespec this cache entry

<cache-entry>
        <class>command</class>
        <sharing-policy>not-shared</sharing-policy>
        <name>com.ibm.commerce.catalog.objsrc.CatalogEntryCache</name>
        <property name="persist-to-disk">false</property>
        <cache-id>
            <component type="method" id="getFinderName">
                <required>true</required>
            </component>
            <component type="method" id="getFinderArgs">
            </component>
            <component type="method" id="getContextInfo">
                <required>false</required>
            </component>
            <priority>4</priority>
            <timeout>172800</timeout>
            <inactivity>900</inactivity>
        </cache-id>
    </cache-entry>

simply copying it from the samples files on WCD (<WCDFolder>\samples\dynacache\cachespec.xml).

But you can also use “specified DistributedMap instances”. I put it between quotes because is not so documented on infocenter.

First of all what are the “specified DistributedMap instances”?

The default list is here but how should I activate these instances?

This is the tricky part: you have to create an object cache instance using WAS console (on WC 7) or adding a cacheinstances.properties file to your Stores.war\WEB-INF\classes folder (on WC 6).

In theory this process is documented on Commerce infocenter but something on the WAS configuration part is missing.

Suppose you want to cache the catalog entry query results using DistributedMap and not with command caching.

What you have to do first is to find the default DistributedMap we are referring to.

Using the same example above, we find the DistributedMap associated to the com.ibm.commerce.catalog.objsrc.CatalogEntryCache command is “WCCatalogEntryDistributedMapCache”.

Then we create it in WAS console

create new Object Cache Instance on WAS

This is enough to WC to start using it to cache database query results.

If we ask Madisons home page and we open the Cache monitor, we already see the com.ibm.commerce.catalog.objsrc.CatalogEntryCache entries in the corresponding DistributedMap instance

using Object Cache Instance On Cache Monitor

Now you can configure the object instance behaviour in Commerce adding a tag to the Commerce instance file.

The tag is the “CrossTransactionCache” and, in our example, you can add this

     <CrossTransactionCache
		enabled="true"
		commandCaching="default"
		maxInactivityTime="86400"
		maxTimeToLive="172800"
		defaultResultSizeThreshold="8"
		clearUserOnLogoff="true"
		clearUserDataCacheOnLogoff="false"
		maxInvalidationIdsPerTransaction="100000"
		clearCacheOnMaxInvalidationIdsPerTransaction="false"
		reduceMemory="false"
		reduceInvalidationIds="false">
		<WCCatalogEntryDistributedMapCache enabled="true" reduceMemory="false" maxTimeToLive="172800" maxInactivityTime="86400"/>
	</CrossTransactionCache>

to refine the configuration.

Don’t forget the most important part: the invalidation.

As you saw in the cache monitor screenshot, every entry has a default TTL of 172800 seconds.

The best way (and the default) to invalidate these entries is to use the database triggers.

You can find the default database triggers on WCD samples folder (<WCDFolder>\schema\db2\wcs.cacheivl.trigger.sql).

In our example the trigger we have to apply is

CREATE TRIGGER civu_c21_modified
 AFTER UPDATE ON CATENTRY
 REFERENCING NEW AS trow
 FOR EACH ROW MODE DB2SQL
  INSERT INTO cacheivl (dataid) VALUES('WCT+CATENTRY+CATENTRY_ID:%:'
  || RTRIM(CHAR(trow.CATENTRY_ID))
  )
;

to create an invalidation row in CACHEIVL every time there is a modification on a specific record in the CATENTRY table.

I slightly modified the original “civu_c21″ trigger to match one of the invalidation ids associated to our entries

WCT+CATENTRY+CATENTRY_ID:%:10185
WCT+CATENTRY+?
WCT+CATENTRY+CATENTRY_ID:?
WCT+?

The default scheduled DynacacheInvalidationCmd will do the invalidation work for us.

There are many ways to cache static contents in a web site. Without playing with cache headers, you can use different techniques to change file name to be sure the browser is renewing his local cache.

El Pais for example use the querystring to do the trick

http://www.elpais.com/js/util_nuevo.js?update=201104251641

I prefer to change the file name

http://www.sears.ca/stores/combined.js.h355855108.pack

because there are a lot of different opinions about if all browsers consider querystring part of the URL.

Again there are a lot of different ways to change the filename in a page.

I like to change it in the build process to completely decouple the renaming process with the development and deployment process.

Usually you are working in a development environment using a code repository and a build server.

To generate javascript versioned filenames, you just have to slightly modify the checkout process adding an Ant task like this

<replaceregexp match=”javascript/([a-zA-Z0-9]+)\.js” replace=”javascript/\1.v${env.BUILD_NUMBER}.js” flags=”g”>
<fileset dir=”${source.dir}/${cvs.package}” includes=”**/*.jsp, **/*.jspf”/>
</replaceregexp>

This way when you checkout your application code from CVS for example, you can use the BUILD_NUMBER environment variable (Jenkins has this variable available for script for example) to generate a link like this

<script type=”text/javascript” src=”<c:out value=’${storeImgDirHostname}javascript/MessageHelper.v3344.js’/>”></script>

from the original one

<script type=”text/javascript” src=”<c:out value=’${storeImgDirHostname}javascript/MessageHelper.js’/>”></script>

using the very efficient replaceregexp Ant task.

Now you have changed all the javascript links in your JSPs, you only have to cheat the browser to always load the same javascript filename in the HTTP filesystem.

You can do this with a simple rewrite rule like

RewriteRule ^/wcsstore/(.*)\.v(.*)\.js$ /wcsstore/$1.js

This way anytime a user will ask the versioned javascript file link

<script type=”text/javascript” src=”<hostname>/javascript/MessageHelper.v3344.js”></script>

the HTTP server will return the file without versioning

<script type=”text/javascript” src=”<hostname>/javascript/MessageHelper.js”></script>

But with an important difference: reloading the file from the http server with use the cached version.

This strategy allows also to keep the same filename from the developer workstation to the production http server.

Last step is to add the expires rule to cache javascript contents for a year for example

ExpiresByType text/javascript “access plus 1 year”

There is a clean way to keep using SEO URLs (static version) and allow caching at IHS level.

The infocenter says:

“Static URLs can only use WebSphere dynamic caching at the Application Server.”

Then, in theory, you can’t cache at IHS level a static URL like

http://localhost/webapp/wcs/stores/servlet/TopCategories1_10001_10001

Buy if you “rewrite” this URL to his dynamic version

http://localhost/webapp/wcs/stores/servlet/TopCategoriesDisplay?storeId=10001&catalogId=10001

BEFORE it’s processed by the plugin, you can still cache it with ESI.

This is what I did: I added this simple rewrite rule to the Commerce virtual host on IHS

RewriteRule ^/webapp/wcs/stores/servlet/TopCategories1_(.+)_(.+)$ /webapp/wcs/stores/servlet/TopCategoriesDisplay?storeId=$1&catalogId=$2 [PT]

and voilá. This way I’m still using the SEO URLs but I’m caching it on ESI.

Be carefully: the PT (Pass Through) option in the rule is VERY important.

According to the Websphere plugin documentation:

“The PT flag is required, as this is what lets the WebSphere plug-in observe the results of the mod_rewrite processing.”

this option allows you to “cheat” the plugin and cache the home page contents on the edge.

Posted by: Marco Fabbri | August 20, 2011

Caching Madisons home page with ESI

Apparently to cache Madisons home page using ESI at http level is a simple task.

But the instructions to follow are quite complicated to line up in a step’s sequence.

I try to resume what you have to do to get it working in WCD 7 environment.

1. Install and configure IHS on your workstation

Deluca explains how to do in this article. I suggest to install the last FP (now 17) for IHS, Plugin and JDK.

2. Install DynacacheESI and Dynacache Monitor applications

It’s better to install the applications on top of WAS 7.0.0.17 and update the Cache monitor to the Extended version.

3. Configure ESI on IHS Plugin

<Property Name="ESIEnable" Value="true"/>
<Property Name="ESIMaxCacheSize" Value="1024"/>
<Property Name="ESIInvalidationMonitor" Value="true"/>

4. Configure IHS process to work with ESI

On windows there is no problem to leave default values but on Linux you have to remember to limit the number of processes (only one is better for ESI)

5. Enable Servlet Caching and verify Cache External groups members on WAS console

This last configuration is very important in a cluster environment where you must have one entry for every remote http server.

5. Modify cachespec.xml

<cache-entry>
        <class>servlet</class>
	<name>com.ibm.commerce.struts.ECActionServlet.class</name>
        <property name="consume-subfragments">true</property>
        <property name="EdgeCacheable">true</property>
        <property name="save-attributes">false</property>
        <!-- TopCategoriesDisplay?storeId=<storeId>&catalogId=<catalogId> -->
        <cache-id>
                <component id="" type="pathinfo">
                        <required>true</required>
			<value>/TopCategoriesDisplay</value>
                </component>
                <component id="storeId" type="parameter">
                        <required>true</required>
                        </component>
                <component id="catalogId" type="parameter">
                        <required>true</required>
                </component>
        </cache-id>
<cache-entry>

Until here the setup was quite easy.

Let’s check the results on Cache Monitor using the TopCategories dynamic URL.

This is the initial state on cache monitor

There is no entries on dynacache or ESI.

Calling this URL

http://localhost/webapp/wcs/stores/servlet/TopCategoriesDisplay?storeId=10001&catalogId=10001

the page contents are cached on IHS

and the page is cached at WAS level

Now hitting F5 to reload the page, the request will come directly from the IHS without generating traffic against WAS (look at the cache entry hit)

Done. We are caching pages at IHS level using ESI.

But…

This is working only for dynamic URLs not for the static ones

Search engine optimization (SEO) for WebSphere Commerce Version 7 Feature Pack 2 or earlier

“Static URLs can only use WebSphere dynamic caching at the Application Server”

This means that the corrispondent SEO static URL for Madisons Home Page (OOB in version 7)

http://localhost/webapp/wcs/stores/servlet/TopCategories_10001_10001

CANNOT be cached with ESI at IHS level. And no, this is not solved on FEP 3 :-)

Remember also that

IBM HTTP Server Performance Tuning

“Each child processes uses 1 ESI Invalidation thread (when the feature is configured), and this thread is used synchronously in the web container.”

And you will find this error

[8/20/11 20:47:19:205 CEST] 00000015 ThreadMonitor W   WSVR0605W: Thread "WebContainer : 4" (00000068) has been active for 652414 milliseconds and may be hung.  There is/are 1 thread(s) in total in the server that may be hung.
	at java.lang.Object.wait(Native Method)

on WAS SystemOut for every webcontainer thread connected with ESI invalidation servlet when is not used for a while.

Posted by: Marco Fabbri | August 11, 2011

wcf:getData tag demystified

Commerce BOD interface is a powerful tool if you know how to use it.

Using wcf:getData tag is an easy way to exploit the power of BOD.

Madisons uses it on category and product pages. On the product page,  <WCDE_Home>\workspace\Stores\WebContent\Madisons\Snippets\Catalog\CatalogEntryDisplay\CachedProductOnlyDisplay.jspfor example

<wcf:getData type="com.ibm.commerce.catalog.facade.datatypes.CatalogEntryType[]" var="catalogEntriesForAttributes" expressionBuilder="getCatalogEntryByParentCatalogEntryId" varShowVerb="showVerb">
<wcf:contextData name="storeId" data="${param.storeId}"/>
<wcf:param name="catEntryId" value="${product.productID}"/>
<wcf:param name="dataLanguageIds" value="${WCParam.langId}"/>
<wcf:param name="accessProfile" value="IBM_Store_CatalogEntryAttributes"/>
</wcf:getData>

we are using it to get the attribute’s list for a product.

But wcf:getdata tag can do a lot more.

Suppose I want to put in a variable in my JSP all the products that are beyond a specific category using wcf:getdata.

First of all I have to look for in WC Infocenter if IBM already implemented this query for me.

The table on Noun CatalogEntry help us to find the values I have to put on wcf:getdata attributes to get such a resultset.

In the table I find what I was looking for: “Get child catalog entries of a catalog group”.

I write down the fields I need:

  • Expression Builder = getCatalogEntryByParentCatalogGroupId
  • Corrispondent XPath = /CatalogEntry[ParentCatalogGroupIdentifier[(UniqueID=$uniqueId$)]]
  • Access profile = “All access profiles listed in the ‘Access Profile’ section can be applied to this expression”

As access profile I choose “IBM_Admin_Summary” (at the bottom of the table).

To know which are the contextData, I check the xml Request example

http://publib.boulder.ibm.com/infocenter/wchelp/v7r0m0/topic/com.ibm.commerce.services.doc/Catalog/CatalogEntry/getCatalogEntriesForParentCatalogGroup_req.xml

Now compose the corrispondent wcf:getdata tag is very easy.

I found the Expression Builder on the table (“getCatalogEntryByParentCatalogGroupId”), the contextData on the xml request example (storeId and catalogId), the parameter on the XPath expression (the catalog group id) and the access profile I choosed (“IBM_Admin_Summary”)

<wcf:getData type="com.ibm.commerce.catalog.facade.datatypes.CatalogEntryType[]" var="catalogEntriesInCategory" expressionBuilder="getCatalogEntryByParentCatalogGroupId" varShowVerb="showVerb">
	<wcf:contextData name="storeId" data="${param.storeId}"/>
	<wcf:contextData name="catalogId" data="${param.catalogId}"/>
	<wcf:param name="catGroupId" value="${WCParam.categoryId}"/>
	<wcf:param name="accessProfile" value="IBM_Admin_Summary"/>
</wcf:getData>

Before putting this tag in a JSP, I can test what is returning using the Query Template File Syntax Validator included on WCDE

If you use “10004″ as category group id (“Lounge Chairs” on Madisons) and submit the query

you’ll see the 4 products included on “Lounge Chairs” category and the queries executed by BOD under the covers.

Unfortunately the default dsl.jsp is only an example and it needs a little customization to add the catalogId parameter.

You can find the working version on my Box.net space. Enjoy.

Using Hudson as continuous integration server allows you to checkout source code directly without using WCBD to do this.

The configuration is quite simple.

First you create a job to execute WCBD script and then you configure “Source Code Management” section this way

HudsonJobConfigurationCVS

Every time you’ll execute this job, Hudson will checkout WC source code (using “update” option) in the Hudson workspace located on (default location)

/var/lib/hudson/jobs/<HudsonJobName>/workspace

Then you have to add a new property on build.properties file, I called “hudson.home”, to point to Hudson home directory

#===============================================================================
# CUSTOMIZATION: Add new properties required by customization below.
#===============================================================================
#-------------------------------------------------------------------------------
# hudson.home
#
# Home directory Hudson.
#-------------------------------------------------------------------------------
hudson.home=/var/lib/hudson

Last change you have to do is to modify

/opt/IBM/WebSphereCommerce70/wcbd/extract/extract-cvs.xml

this way

<target name=”all”>
<copy todir=”${source.dir}/${cvs.package}”>
<fileset dir=”${hudson.home}/jobs/${job.name}/${cvs.package}”/>
</copy>

to get updated source code from Hudson workspace instead of checkout it directly from the repository.
Executing the command

/opt/IBM/WebSphereCommerce70/wcbd/wcbd-ant -buildfile /opt/IBM/WebSphereCommerce70/wcbd/wcbd-build.xml

Hudson will checkout the code and WCBD will create the zip to deploy as usual

Establish an asynchronous communication between Commerce and a back-end system is mandatory in many integration projects. And asynchronous messages means MQ in the Commerce world.

But test this integration can be complicated if you are a developer and you need to install MQ in every WCD workstation.

There’s a solution: use SIB (Service Integration Bus), the WAS default messaging provider.

Creating a bus in the SIB and then the connection factory and the queues, you can test an outbound message like Report_NC_PurchaseOrder sent when you close an order. Remember to enable it updating CMDREG table.

Let’s see the SIB configuration.

First you create a bus called “sibWC” and add “server1″ default application server as a member

Remember to activate two services on server1: sib service and ActivitySession service.

Then you have to create the queue connection factory and the queues.

And the queues

The messaging infrastructure is ready. Let’s configure the Commerce side.

We activate “Websphere MQ” transport using Administration console at SITE level

and then create the message types at STORE level (Madisons in this case)

WC Admin Console Transport Store Level Detail

Ready to play!!

Now when you’ll close an order, a JMS message with the Report_NC_PurchaseOrder xml will be sent to the JMSOutboundQueue queue in the sibWC bus.

Going to the sibWC messaging engine, you’ll find the message

Posted by: Marco Fabbri | August 27, 2010

Websphere Commerce Developer 7 error activating transports

Using Administration Console you can activate the default Commerce transports like email or file to send a email notification for a closed order for example.
But activate a transport can trigger a publish operation for the Commerce Test Server.
This publish can generate this error using an outbound message:

“[8/24/10 16:31:36:078 CEST] 00000050 CommerceSrvr E JMSMessageBean sendImmediate CMN0422E: The following naming exception has occurred during processing: “lookup(eis/JCAFile)javax.naming.NameNotFoundException: Context: localhost/nodes/localhost/servers/server1, name: eis/JCAFile: First component in name JCAFile not found. [Root exception is org.omg.CosNaming.NamingContextPackage.NotFound:IDL:omg.org/CosNaming/NamingContext/NotFound:1.0]“

To solve this issue you have to remove and add WC application.

[org.omg.CosNaming.NamingContextPackage.NotFound: IDL:omg.org/CosNaming/NamingContext/NotFound:1.0 in Toolkit]

http://www-01.ibm.com/support/docview.wss?uid=swg21298876

All Commerce developers has complained at least one time about Commerce Server startup time.

But if you’re using WCD 7 and Derby (many developers doesn’t need to connect to a real database because they just need to change JSPs), there is a solution.

You just need to apply these simple tips.

1) Defragment your HD. Derby is a filesystem based database and it VERY affected from a fragmented disk.

2) Change the WAS heap default settings. The server1 heap size has to be between 512 and 1024 MB (remember you need at least 3 GB RAM to work)

WCD7 Heap Settings

3) Add two lines to Derby setup file to tune it.

Just add these two properties to the file C:\IBM\SDP\runtimes\base_v7\derby\derby.properties

derby.storage.pageCacheSize=10000
derby.storage.pageSize=8192
Restart Commerce Server and let’s verify the difference.

Older Posts »

Categories

Follow

Get every new post delivered to your Inbox.