Announcement: You can find the guides for Commerce 7.5 and later on the new Elastic Path Documentation site. The Developer Center continues to support Commerce 6.13.0 through 7.4.1.Visit new site

Performance and scalability

Performance and scalability

This section provides information and guidelines for developing scalable high-performance applications with Elastic Path. The focus is on issues and considerations that arise during development. This section covers topics:

Database

Create indexes on condition columns for faster queries

Creating an index on columns containing values used in query conditions improves your query's performance. While this may introduce some overhead when updating data, query performance is usually more important in Elastic Path.

Denormalize tables to reduce joins

Normalized table designs reduce duplicate data, but this is usually at the expense of performance. Querying normalized data requires queries to join several tables, which can be slow. For this reason, denormalization can be beneficial for performance critical queries.

OpenJPA

See the Apache OpenJPA Optimization Guidelines for recommendations on OpenJPA options that can improve performance.

Use the Commerce Manager to perform batch jobs

To conserve Storefront CPU resources, calculate statistics and customer profiling data using Commerce Manager batch jobs, rather than calculating them during Storefront transactions. Schedule batch jobs with the Quartz scheduler and run them during off hours. For more information on Commerce Manager batch jobs, see Scheduled Jobs (Commerce Manager).

Use Lucene for faster searches

Use Lucene index search to achieve faster search results. Lucene is well suited for full text search and performs much faster than database queries.

Storefront

Use load tuners to retrieve only the data you need

Some domain objects aggregate a lot of data. For example, a Product aggregates prices, skus, attributes, inventory, recommendations etc. In most cases, it's not necessary to load all the object's aggregate data. For example, a search result page only shows the product's name and price. The product's attributes, sku, recommendations, etc do not need to be loaded. A load tuner instructs the persistence layer on which collections to load. Several pre-defined load tuners are declared in domainModel.xml. You may wish to create your own tuner to load exactly the data you need. See Load tuners for more information on configuring load tuners.

Attribute performance overhead

The Elastic Path attribute system provides a flexible way for business users to add or change category and product information. However, developers should be aware of the overhead of using attributes. A database table join and a Java map lookup is required to retrieve an attribute value for a category or product. Therefore, the number of attributes used should be limited in performance-critical situations.

Only store critically necessary data in the HTTP session

Thousands of HTTP sessions may be created in an application server during peak time; therefore, store the minimum data in your session. Consider the following calculations:

Session timeout : 30 minutes
Average customer visit time : 5 minutes
Average customer arrival rate : 10 users / second
Average session size : 100Kbyte / session
Total sessions =  10 users / sec * 60 sec / minute
        * (30 minutes + 5 minutes) = 21,000 sessions
Total memory consumed by sessions = 100Kbyte / session
        * 21,000 sessions = 2.1GB

The above calculations show how session size affects the overall memory usage. The larger the session, the more it adversely affects memory usage. Another thing to consider is some session objects are long-lived. Long-lived objects can be pushed to older segments of the JVM memory where garbage collect them is expensive. A site's throughput severely drops when the available memory is consumed and the JVM performs garbage collection.

When you consider storing additional session data, ask yourself the following questions to determine if this is necessary.

  • How much memory will the data consume when multiplied across all sessions?
  • Can this data be shared by customers? If not, is it acceptable to have thousands of copies of this data?
  • What is the probability successive requests will use this data?
  • If the data is not stored in the session, is there a better way for successive requests to generate or retrieve this data?

Optimize load balancer polls

Load balancers that make frequent health check requests to the server can cause performance problems. For each request, a CustomerSession object is created to track information about the customer. If the load balancer makes frequent health check requests, a large number of unnecessary customer sessions are created. To solve this issue, create a specialized page to handle health checks and set the page to not create session data.

Commerce Manager

Limit the number of search results

Searches can hit thousands of products, but returning all the results may not be necessary. To improve search response time, set the maximum number of search results. Also, consider displaying a message with links to more matches and ask for refined search criteria.

Java code performance tips

Use Collections.EMPTY_LIST, Collections.EMPTY_SET, Collections.EMPTY_MAP

If a method returns a list, set or map, it should return an empty list, set or map rather than null. If the returned list, set or map is just for reference (which should be in most cases), you should consider using Collections.EMPTY_LIST, Collections.EMPTY_SET and Collections.EMPTY_MAP instead of creating a new collection. Theses Collections constants are immutable instances and can be used to avoid unnecessary memory allocation.

public Collection getProductUids() {
  if (topSellerProducts == null) {
    return Collections.EMPTY_SET;
  }
  return topSellerProducts.keySet();
}

Map Iteration

Avoid iterating on maps in the way shown below.

for (Iterator keyIter = mapToSort.keySet().iterator(); keyIter.hasNext();) {
  String currKey = (String) keyIter.next();
  String currValue = (String) mapToSort.get(currKey);
  reverseMap.put(currValue, currKey);
  sortedValueSet.add(currValue);
}

This method accesses the value of a Map entry using a key that was retrieved from a keySet iterator. It is more efficient to use an iterator on the entrySet of the map to avoid the Map.get(key) lookup. Use the following code instead.

for (Map.Entry entry : mapToSort.entrySet()) {
  final String currKey = (String) entry.getKey();
  final String currValue = (String) entry.getValue();
  reverseMap.put(currValue, currKey);
  sortedValueSet.add(currValue);
}

Explicit garbage collection

Runtime.getRuntime().gc();

Explicit requests for garbage collection as shown above are abnormal. They should only be used in benchmarking or profiling code. Explicitly invoking the garbage collector in routines such as close or finalize methods can lead to extremely poor performance. Garbage collection can be expensive and any situation that forces hundreds or thousands of garbage collections will bring a machine to a crawl.

Concatenating Strings using plus in a loop

Concatenating Strings using + is slower than using StringBuffer. Slow example:

for (int i = 0; i < optionValueCodes.size(); i++) {
  query += "optVal.optionValueKey = '" + (String) optionValueCodes.get(i) + "' ";
  if (i < optionValueCodes.size() - 1) {
   query += "or ";
  }
}

Better example:

final StringBuffer sbf = new StringBuffer("Select sku.uidPk
from ProductSku as sku inner join sku.optionValueMap as optVal where (");

for (int i = 0; i < optionValueCodes.size(); i++) {
  sbf.append("optVal.optionValueKey = '")
     .append((String) optionValueCodes.get(i)).append("' ");
  if (i < optionValueCodes.size() - 1) {
    sbf.append("or ");
  }
}

Use static inner classes when possible

If you have an inner class that does not use its embedded reference to the object which created it, it can be declared static. The reference makes instances of the class larger and may keep the reference to the creator object alive longer than necessary. If possible, the class should be made static.