This is a first part of a long series (which have no planned number of parts) as the lessons I learned during trouble shouting customers’ performance problems. I’m quite of addicted to the support cases reported by customers, especially the ones with performance problems. Every time I jump into such support case, I’ll be with less hairs, but I also learn some new things: Implementations are different from cases to cases, but there are some common mistakes which will hurt your website performance. This series will try to point out those mistakes so you get your performance gain, for (almost) free:
Mistake 1: Loading to much content
It’s easy to load contents, especially with the new content APIs. Given an universal ContentReference, you can load a content with a simple line of code. By default, the loaded content is cached, so you might think it’s cheap, or even free to load a content. Think again.
Each content is quite expensive to load (in term of I/O operation to load the data) and to cache (in term of object size in the cache). A normal catalog content can easily takes up 10MB in memory (and it’s not uncommon to take up 20MB or so). (Truth be told, this includes the shared parts, such as the property definitions which can be shared with other contents). An average Commerce catalog can have something like 30.000-50.000 entries (I’ve seen much bigger ones), and there is no way all of those entries can fit in the memory of a normal server. Old, less used cache entry will be eventually trimmed from the cache to make way for new requested contents. First few thousand contents in cache are fine. But if you are constantly loading the content, the cache will be constantly trimmed and filled. Database will constantly hit. GC will be constantly called. Your server will be constantly in high memory consumption and high CPU usage. Whenever GC kicks in, the site appears to be very slow, or even freeze. If it’s too much then the IIS Apppool can even restart itself – if you reached the extreme end.
How can that be prevented? Well – by loading less contents.
If you are loading an entry content just to get the code from the id, or vice versa – there is a better way to go. That kind of task is what ReferenceConverter is for. In the latest version of Commerce (10+), ReferenceConverter is highly effective resolving between code and id. The performance for each operation is very fast, and the memory footprint is much smaller than loading a content.
If you are getting data from Find and then call UrlResolver.GetUrl to get the product’s url – there is a better way to go. Internally, UrlResolver.GetUrl will load the contents recursively until it reaches the “starting point”. You can simply include the Url into the Find index, and avoid loading the content everytime a search query returns. On a case I worked on, the default product listing page has 45 products, which means on each view, it will load (at least) 45 contents. Including the Url in the Find index reduce the number of contents needed to be load to almost 0, and dramatically improved the website performance.
If you have properties which needs to load contents – there might be better way. I saw a case when product content have several properties which internally load all of its variants to calculate something. There were no caching involved in those properties, which means the variants can be loaded multiple times, or, being kept in memory for a longer period than they should. Those properties, again – should be included in Find index. Calculate one, use anywhere. There might be a need to listen to the variant updating events, so you’ll properly reindex the products when needed, but the performance gain far outweighs in this case.
TL;DR: never assume that loading a content is cheap, or the content you need is already in cache. Before you write a code to load a content, think twice to see if you really need the whole content, or if the data you want can be cached somewhere else more effective.
Caching does not only mean ISynchronizedObjectInstanceCache!