Lukasvan3L

A pragmatic programmer

Posts tagged performance

3 notes &

Don’t trust Modules!

One of the powers of Orchard is a gallery filled with useful modules. Just install them and you’re good to go. But if you want to build a high-performance website with Orchard CMS, you’re gonna have to do more than that. The modules are really generic and that could cost you in performance. Also, they’re contributed by the community, and not everyone has the performance focus you might need.

Output Caching module
The output caching module for instance, does a terrific job at outputcaching. If I had built it myself it wouldn’t be as flexible with a cms module where editors can change the cachetime per route and even invalide specific cache entries. But for my application it can be modified to greatly increase the response times of a cached page.
For each page that is outputcached, the OutputCacheFilter computes a CacheKey that’s unique for this specific url and serversettings by joining the url, tenant, culture and themeid.

private string ComputeCacheKey(ActionExecutingContext filterContext)
{
  var keyBuilder = new StringBuilder();
  keyBuilder.Append("tenant=").Append(_shellSettings.Name).Append(";");
  keyBuilder.Append("url=").Append(filterContext.HttpContext.Request.RawUrl.ToLowerInvariant()).Append(";");
  foreach (var pair in filterContext.ActionParameters)
    keyBuilder.AppendFormat("{0}={1};", pair.Key, pair.Value);
  keyBuilder.Append("culture=").Append(_workContext.CurrentCulture).Append(";");
  keyBuilder.Append("theme=").Append(_themeManager.GetRequestTheme(filterContext.RequestContext).Id).Append(";");
  return keyBuilder.ToString();
}

In my application each tenant has its own culture. So I don’t need both of them in my cachekey. When I take out the line that includes the CurrentCulture, the page response time drops from 45ms to 5ms!

SecureSocketsLayer module
The SecureSocketsLayer also gets executed for each page request (even the outputcached ones!). In it’s filter is the following code:

var settings = _services.WorkContext.CurrentSite.As<SslSettingsPart>();

This does multiple database queries and costs about 40ms. On an outputcached page that’s a lot of unneccesary overhead!. In my specific application I only needed to do one area over SSL. So I put the setting (EnableSSL) in the AppSettings, and created my own filter with a tiny piece of code.

var wishedPort = 80;
if (UseSsl)
{
  var area = filterContext.Controller.ControllerContext.RouteData.Values["area"];
  if (area != null && area.ToString() == "Viaselect.Checkout")
    wishedPort = 443;
}

Conclusion
When you install a module, take a good look at the code! The developer might have made choices differently than you would have made them. And sometimes it’s a really quick fix to tune a module and benefit from it’s extra’s!

2 notes &

Speeding up Orchard: Packaging and Minification

Orchard is really flexible and modular. This has a big drawback while performance tuning: it’s really hard to package the static resources. Each module has its own javascript and css files, and one module doesn’t know about the files of the other. Time to hack performance into it!

The solution I implemented was a plain batch file. It gets fed a list of css and js files and minifies them to one output file. It does take a few steps to include a new js or css file, but it performs a lot better!

Here’s the bat file, using the Microsoft Ajax Minifier and DotLess:

for /R %%i in (*.less) do (
  tools\dotless.compiler.exe "%%i" "%%i.css"
)

tools\AjaxMin-4.37.exe -js -clobber -xml "minify.xml" -out "Orchard.Web\min"

So first of all I take each *.less file and run it through the dotless compiler. The output is a *.less.css file next to each original, which I’ve included in the minify.xml file that AjaxMin parses. Here’s a portion of it:

<root>
  <output path="js.js">
    <input path="c:\inetpub/Modules/Viaselect/Scripts/plugins/jquery.ba-hashchange.min.js" />
    <input path="c:\inetpub/Modules/Viaselect/Scripts/menu.js" />
  </output>
  <output path="css.css">
    <input path="c:\inetpub/Themes/CharmeQuality/styles/base.less.css" />
    <input path="c:\inetpub/modules/viaselect/styles/datepicker.less.css" />
  </output>
</root>

Unfortunately I didn’t get it to work with relative paths, so I had to make them absolute. We’ll cross that bridge when we get there :)

So now I have a js.js and a css.css that I include in the layout.cshtml:

  Script.Require("jQuery").AtHead();
  Script.Include("~/min/js.debug.js""~/min/js.js");
  Style.Include("~/min/css.debug.css""~/min/css.css");

And of course when you’re in debug mode, you want firebug to give you the right codelines! Here’s how to tackle that one; the debug files contain the following:

@import url("/Themes/CharmeQuality/styles/base.less");
@import url("/modules/viaselect/styles/datepicker.less");
document.write("<script src='/Modules/Viaselect/Scripts/plugins/jquery.ba-hashchange.min.js'><\/script>");
document.write("<script src='/Modules/Viaselect/Scripts/menu.js'><\/script>");

It’s not the best solution, but for now it speeds up the site considerably! I wouldn’t know how to create a module that takes care of this. The ResourceManager is way too complicated to just include the package if the file you’re requesting is inside the xml file. If any of you have good ideas, let me know! I’d love to facilitate this module :)

5 notes &

Profiling orchard with Eqatec Profiler

I can’t seem to get Red-Gate Ants Performance Profiler to attach to the right website. If I let it attach to IIS directly, it restarts IIS in .net2.0 mode instead of .net4.0. When I let it create its own webserver, I do get .net4.0, but am welcomed with the message “Operation could destabilize the runtime” in the WarmupHttpModule constructor. Not very comforting.

Luckily a colleague showed me Eqatec Profiler. It works really ingenious: it re-compiles selected DLL’s, injecting profiling logic into them. This should work nicely with Orchard CMS.

So I want Eqatec to handle the Orchard Core, and installed modules. First problem occurs: the modules are dynamically compiled. As Eqatec can only handle DLL files that are in the same folder, I made references from Orchard.Web to the modules I wanted to investigate. This is my config for Eqatec:

Eqatec Orchard configuration

All I have to do now is click “Build”, the selected dll’s are re-compiled and when I surf to my website (http://localhost) I see logs coming in in the “Run” tab. I don’t want to profile the start-up costs of Orchard, so I’ll refresh my browser a couple of times, then hit “reset counters”, refresh twice, and click “take snapshot”.

Eqatec Orchard profiling output

Perfect! This overview tells us the BuildDisplay was called 4 times, resulting in a total time of nearly 600ms that it took. And The repository did 126 database GetById() calls. And a whopping 316.100 time IsCurrent() on an IVolatileToken

So we’ve got some pointers on where to look for performance fixes ;)

ps. Eqatec has a time limited campaign where you can get a Corporate license worth $999 for free!

0 notes &

Speeding up Orchard: Working with the CacheManager

Caching can be a really nice solution if you’ve got some heavy queries, webservice calls or other stuff that takes a long time. Orchard has a nice, but different, cachemanager inside.

Let’s first have a look at how you can set it up in your project. I’m using the PackageUpdateService that comes in the Orchard.Packaging module as an example.

private readonly ICacheManager _cacheManager;
private readonly IClock _clock;
private readonly ISignals _signals;

public PackageUpdateService(
    ICacheManager cacheManager,
    IClock clock,
    ISignals signals)
{
  _cacheManager = cacheManager;
  _clock = clock;
  _signals = signals;
}

This is a simplified version, but these three elements are needed to get caching going. Now to get something from cache:

public PackagesStatusResult GetPackages(PackagingSource packagingSource)
{
  // Refresh every 23 hours or when signal was triggered
  return _cacheManager.Get("UniqueKey", ctx =>
  {
    ctx.Monitor(_clock.When(TimeSpan.FromMinutes(60 * 23)));
    ctx.Monitor(_signals.When("PackageUpdateService"));

    return GetPackagesWorker(packagingSource);
  });
}

The Get function is a generic, so it just returns the type of object that the function itself returns. This is common for most caching solutions. The magic is with the Monitor functions, which you can pass an IVolatileToken. There are currently two implementations of IVolatileProvider: IClock and ISignals. As you can see in the code above, the clock can invalidate the cache after a specific timespan. 

The signals have a When and a Trigger function. The Trigger function will invalidate any caches that have been set with an object with the same value in the When function.

Enjoy!

3 notes &

Speeding up Orchard: Database Indexes in SQL Server

I’m currently doing some performance tweaking of my Orchard setup. One of the things I found was that a few core database indexes are missing.

The way I’m going at it is using a query that outputs the most read-intensive queries, and then having a look at their execution plan. I’m doing it on a Sql Server instance, not the file-based version that’s default selected in the setup screen. The query I got from here: http://blog.brianhartsock.com/2008/12/16/quick-and-dirty-sql-server-slow-query-log/.

SELECT TOP 20 SUBSTRING(qt.text, (qs.statement_start_offset/2)+1, 
        ((CASE qs.statement_end_offset
          WHEN -1 THEN DATALENGTH(qt.text)
         ELSE qs.statement_end_offset
         END - qs.statement_start_offset)/2)+1), 
qs.execution_count, 
qs.total_logical_reads, qs.last_logical_reads,
qs.min_logical_reads, qs.max_logical_reads,
qs.total_elapsed_time, qs.last_elapsed_time,
qs.min_elapsed_time, qs.max_elapsed_time,
qs.last_execution_time,
qp.query_plan
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
WHERE qt.encrypted=0
ORDER BY qs.total_logical_reads DESC

Now, if you take the top query (so this is one that’s used very often or it’s extremely slow, or both if you’re unlucky), and execute it with the “execution plan” option enabled (Ctrl+M), you’ll see how it executes. What I found, for instance, was this query:

SELECT this_.Id as Id57_2_, this_.Number as Number57_2_, this_.Published as Published57_2_, this_.Latest as Latest57_2_, this_.Data as Data57_2_, this_.ContentItemRecord_id as ContentI6_57_2_, contentite1_.Id as Id54_0_, contentite1_.Data as Data54_0_, contentite1_.ContentType_id as ContentT3_54_0_, advancedme2_.Id as Id35_1_, advancedme2_.Text as Text35_1_, advancedme2_.Url as Url35_1_, advancedme2_.Position as Position35_1_, advancedme2_.MenuName as MenuName35_1_, advancedme2_.SubTitle as SubTitle35_1_, advancedme2_.Classes as Classes35_1_, advancedme2_.DisplayText as DisplayT8_35_1_, advancedme2_.DisplayHref as DisplayH9_35_1_, advancedme2_.RelatedContentId as Related10_35_1_ 
FROM Orchard_Framework_ContentItemVersionRecord this_ 
inner join Orchard_Framework_ContentItemRecord contentite1_ on this_.ContentItemRecord_id=contentite1_.Id 
inner join Szmyd_Orchard_Modules_Menu_AdvancedMenuItemPartRecord advancedme2_ on contentite1_.Id=advancedme2_.Id 
WHERE advancedme2_.MenuName = @p0 and this_.Published = @p1

The execution plan shows me that the slowest part is the mapping of Orchard_Framework_ContentItemRecord to Szmyd_Orchard_Modules_Menu_AdvancedMenuItemPartRecord.

And thus I found out it would be smart to include an index on the field Orchard_Framework_ContentItemVersionRecord.ContentItemRecord_id. This can be achieved by adding to “FrameworkDataMigration.cs” the following:

SchemaBuilder.AlterTable(
  "ContentItemVersionRecord",
  table => table
    .CreateIndex("Index_Orchard_Framework_ContentItemVersionRecord_ContentItemRecord_id",
    "ContentItemRecord_id"));

That way it’ll be included if you re-recipe. Be advised, the core modules will not run database migrations, so if you have a live site it’s better to just update the database tables themselves, or put this code in another module that’s not part of the core.