<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><language>en</language><title>Blog posts by Andreas J</title> <link>https://world.optimizely.com/blogs/andreas-j/</link><description></description><ttl>60</ttl><generator>Optimizely World</generator><item> <title>How to use CacheTagHelper with content areas in Optimizely CMS 12</title>            <link>https://world.optimizely.com/blogs/andreas-j/dates/2022/12/how-to-use-cachetaghelper-with-content-areas-in-optimizely-cms/</link>            <description>&lt;p&gt;&lt;em&gt;I might be going out on a limb here - if you have a better solution, feel very free to share it!&amp;nbsp;&lt;/em&gt;&lt;/p&gt;
&lt;div class=&quot;pt-block&amp;#32;pt-text-block&amp;#32;pt-text-block-style-normal&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div class=&quot;sc-pVTFL&amp;#32;heKAjU&quot;&gt;
&lt;div class=&quot;sc-pVTFL&amp;#32;ekDDlL&amp;#32;sc-crHmcD&amp;#32;kJPyWA&amp;#32;sc-jHwEXd&amp;#32;iTKoOT&quot;&gt;
&lt;div class=&quot;sc-pVTFL&amp;#32;egJman&quot;&gt;
&lt;div class=&quot;sc-czvZiG&amp;#32;iwbNYk&quot;&gt;
&lt;div class=&quot;sc-pVTFL&amp;#32;ekDDlL&amp;#32;sc-crHmcD&amp;#32;iPOKpB&amp;#32;sc-dXNJws&amp;#32;RJZK&quot;&gt;
&lt;p&gt;&lt;span&gt;Upgrading your Optimizely web application from .NET Framework to .NET Core should provide a huge performance win. However, the &lt;a href=&quot;/csclasslibraries/cms/EPiServer.Web.Mvc.ContentOutputCacheAttribute?version=11&quot;&gt;&lt;span class=&quot;sc-kMyqmI&amp;#32;jzOrlf&quot;&gt;ContentOutputCacheAttribute&lt;/span&gt;&lt;/a&gt; did not make the cut and was not migrated by Optimizely. If your web application was reliant on this, chances are you had some bottlenecks that will remain even after the upgrade. Not being able to use the output cache attribute will make those bottlenecks come alive once more.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;PS. Don&#39;t have any bottlenecks? Continue reading anyway - learning how to use CacheTagHelper can dramatically improve your performance.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;pt-block&amp;#32;pt-text-block&amp;#32;pt-text-block-style-normal&quot;&gt;
&lt;div class=&quot;sc-pVTFL&amp;#32;heKAjU&quot;&gt;
&lt;div class=&quot;sc-pVTFL&amp;#32;ekDDlL&amp;#32;sc-crHmcD&amp;#32;kJPyWA&amp;#32;sc-jHwEXd&amp;#32;iTKoOT&quot;&gt;
&lt;div class=&quot;sc-pVTFL&amp;#32;egJman&quot;&gt;
&lt;div class=&quot;sc-czvZiG&amp;#32;iwbNYk&quot;&gt;
&lt;div class=&quot;sc-pVTFL&amp;#32;ekDDlL&amp;#32;sc-crHmcD&amp;#32;iPOKpB&amp;#32;sc-dXNJws&amp;#32;RJZK&quot;&gt;
&lt;p&gt;&lt;span&gt;To once again (shame on you!) hide those bottlenecks, we can pretty easily make use of the &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/built-in/cache-tag-helper?view=aspnetcore-7.0&quot;&gt;&lt;span class=&quot;sc-kMyqmI&amp;#32;jzOrlf&quot;&gt;CacheTagHelper&lt;/span&gt;&lt;/a&gt; that .NET Core provides. An unrealistically simple example of this would be:&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;&amp;lt;cache&amp;gt;
    @Html.PropertyFor(m =&amp;gt; m.ContentArea)
&amp;lt;/cache&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&#39;ve now &quot;solved&quot; one problem (the bottleneck), but gained one new - cache invalidation.&lt;/p&gt;
&lt;p&gt;In order to re-evaluate the code block within the cache node - you need to add&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code&gt;vary-by-*&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;conditions. Since we&#39;re caching a content area, we need to make sure the cache is invalidated whenever visitor groups are applied or when content is published.&lt;/p&gt;
&lt;p&gt;Let&#39;s first use&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code&gt;vary-by&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;to depend on visitor group setup of the content area.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public static string GetContentAreaCacheDiscriminator(this IHtmlHelper htmlHelper, ContentArea contentArea)
{
    var httpContext = htmlHelper.ViewContext.HttpContext;

    if (contentArea == null)
    {
        return httpContext.Request.GetDisplayUrl();
    }

    var requiredRoles = string.Join(&quot;,&quot;, contentArea.Items.Select(x =&amp;gt; x.ContentGroup));

    return $&quot;{httpContext.Request.GetDisplayUrl()}-{requiredRoles}&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;... and in the Razor view ...&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;&amp;lt;cache vary-by=&quot;@Html.GetContentAreaCacheDiscriminator(Model.ContentArea)&quot;&amp;gt;
    @Html.PropertyFor(m =&amp;gt; m.ContentArea)
&amp;lt;/cache&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code&gt;ContentGroup&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;property of every&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code&gt;ContentAreaItem&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;in the content area seems to keeps track of what users can see the content.&lt;/p&gt;
&lt;p&gt;So, now the cache should be invalidated if visitor groups are applied to the content area.&lt;/p&gt;
&lt;p&gt;The ability to re-evaluate the content area whenever some nested block is published is more tricky. The only simple-ish way I can come up with is listening to the publish event, increment some counter and include that counter in the cache discriminator method.&lt;/p&gt;
&lt;p&gt;It would look something like this:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public static class AtomicState
{
    public static int Counter = 0;

    public static void Increment() =&amp;gt; Interlocked.Increment(ref Counter);
}

[InitializableModule, ModuleDependency(typeof(FrameworkInitialization))]
public class ContentEventsInitializableModule : IInitializableModule
{
    private IContentEvents _contentEvents;

    public void Initialize(InitializationEngine context)
    {
        _contentEvents = context.Locate.Advanced.GetRequiredService&amp;lt;IContentEvents&amp;gt;();

        _contentEvents.PublishedContent += OnPublished;
    }

    public void Uninitialize(InitializationEngine context)
    {
        _contentEvents.PublishedContent -= OnPublished;
    }

    private static void OnPublished(object sender, ContentEventArgs e)
    {
        AtomicState.Increment();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span&gt;Now we must make use of AtomicState.Counter in our cache discriminator method, so just add it to the return statement:&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;return $&quot;{httpContext.Request.GetDisplayUrl()}-{AtomicState.Counter}-{requiredRoles}&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code&gt;AtomicState.Counter&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;will be read zero or more times per request, and can be updated at any point, we must make it thread safe - which in this case is achieved with&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code&gt;Interlocked.Increment(...)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you wan&#39;t more granular control over what cache to invalidate, you need to check the published event for what content type was published and you need another counter for the specific case.&lt;/p&gt;
&lt;p&gt;That&#39;s it. Thank you for your time! &#128527;&lt;/p&gt;
&lt;p&gt;I originally wrote this post on my personal blog:&lt;br /&gt;&lt;a href=&quot;https://andreas.jilvero.se/blog/how-to-use-cachetaghelper-with-content-areas-in-optimizely/&quot;&gt;https://andreas.jilvero.se/blog/how-to-use-cachetaghelper-with-content-areas-in-optimizely/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>            <guid>https://world.optimizely.com/blogs/andreas-j/dates/2022/12/how-to-use-cachetaghelper-with-content-areas-in-optimizely-cms/</guid>            <pubDate>Fri, 02 Dec 2022 10:41:34 GMT</pubDate>           <category>Blog post</category></item><item> <title>Benchmarking Episerver serializable carts</title>            <link>https://world.optimizely.com/blogs/andreas-j/dates/2017/6/benchmarking-episerver-serializable-carts2/</link>            <description>&lt;p&gt;In version 10.2, Episerver introduced a feature called &lt;a href=&quot;/link/2b7b481e527c40c1baaa0c224082b886.aspx&quot;&gt;serializable carts&lt;/a&gt;. This enables you to store carts as json in a key-value manner to improve performance.&lt;/p&gt;
&lt;p&gt;This project aims to benchmark the performance improvements of Episervers new serializable carts system. I will compare:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The old cart helper&lt;/li&gt;
&lt;li&gt;The new order repository&lt;/li&gt;
&lt;li&gt;The new order repository combined with the serializable carts feature&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In order to create good measurements, I will benchmark the same operations performed through the three different cart systems. These operations include:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create an empty cart and persist it&lt;/li&gt;
&lt;li&gt;Add a line item with a meta field to that cart and persist it&lt;/li&gt;
&lt;li&gt;Validate the cart and apply campaigns and persist it&lt;/li&gt;
&lt;li&gt;Apply payment, process it and persist the cart&lt;/li&gt;
&lt;li&gt;Persist the cart as a purchase order&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I find it difficult to benchmark only one of these operations at a time, therefore when I execute a step, I also execute the steps prior to that step &amp;mdash; i.e. in order to benchmark step 3, I also run step 1 and 2 before.&lt;/p&gt;
&lt;p&gt;The source code is available at &lt;a href=&quot;https://github.com/AndreasJilvero/BenchmarkingEpiserverCarts&quot;&gt;GitHub&lt;/a&gt;. Honestly,&amp;nbsp;you should just continue reading there.&lt;/p&gt;
&lt;h2&gt;Definition of&amp;nbsp;success&lt;/h2&gt;
&lt;p&gt;If operations 1 to 3 (the most common operations) are quicker when using serializable carts, I think we can consider it a good improvement.&lt;/p&gt;
&lt;p&gt;To measure the performance, I execute the operations in a Web API and stress the API using Apache Benchmark:&lt;/p&gt;
&lt;pre class=&quot;brush:csharp;auto-links:false;toolbar:false&quot;&gt;ab.exe -c 50 -n 1000 http://localhost:61651/api/cart?operationsToExecute=1&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;markup--code&amp;#32;markup--li-code&quot;&gt;-c 50&lt;/code&gt; indicates 50 clients&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;markup--code&amp;#32;markup--li-code&quot;&gt;-n 1000&lt;/code&gt; indicates the total number of requests&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The output of each benchmark will be the time it takes to process 1000 requests.&lt;/p&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;See the &lt;a href=&quot;https://github.com/AndreasJilvero/BenchmarkingEpiserverCarts#results&quot;&gt;GitHub page&lt;/a&gt; for a well-formed table.&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp;Cart helper&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp;Order repository&lt;/td&gt;
&lt;td&gt;&amp;nbsp; &amp;nbsp;Serializable carts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;00:00:02.910&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;00:00:03.349&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;00:00:01.991&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;00:00:04.972&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;00:00:05.354&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;00:00:02.534&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;00:00:10.099&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;00:00:11.883&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;00:00:04.306&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;00:00:16.519&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;00:00:16.692&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;00:00:04.331&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;00:00:20.217&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;00:00:19.387&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;00:00:08.422&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;&lt;/h2&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;The results speaks for themselves. Serializable carts performs really well compared to the other two. The newer order repository (without serializable carts enabled) is just as fast/slow as the cart helper. Just getting rid of cart helper namespaces won&amp;rsquo;t bring you any performance improvements.&lt;/p&gt;
&lt;p&gt;I think that serializable carts are mature enough to be used in production, but there are drawbacks though. The serialized cart is stored as json in the database, so if you want to query carts based on some metadata, that will be tricky. If you don&amp;rsquo;t need to do that, go with serializable carts!&lt;/p&gt;
&lt;h2&gt;Contribute&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m no expert in neither benchmarking nor Episerver Commerce and there can be things to improve. Please create a pull request if you think you can improve these benchmarks.&lt;/p&gt;
&lt;p&gt;In order to try this yourself, follow the steps below:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Clone repository at GitHub
&lt;pre class=&quot;brush:csharp;auto-links:false;toolbar:false&quot;&gt;git clone&amp;nbsp;https://github.com/AndreasJilvero/BenchmarkingEpiserverCarts.git&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Create two local SQL server databases and replace the existing connection strings in the web config&lt;/li&gt;
&lt;li&gt;Run Episerver SQL install script&amp;nbsp;
&lt;pre class=&quot;brush:ps;auto-links:false;toolbar:false&quot;&gt;PM&amp;gt; Install-EPiDatabases&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Run Episerver SQL update script
&lt;pre class=&quot;brush:ps;auto-links:false;toolbar:false&quot;&gt;PM&amp;gt; Update-EPiDatabases&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Run Star.Epi.CMS&lt;/li&gt;
&lt;li&gt;Login as your Windows user&lt;/li&gt;
&lt;li&gt;Execute all migration steps&lt;/li&gt;
&lt;li&gt;Use Apache Benchmark (or any other tool) to perform requests&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Andreas J&lt;/p&gt;
&lt;p&gt;Read more on my personal blog - &lt;a href=&quot;https://andreas.jilvero.se/blog&quot;&gt;https://andreas.jilvero.se/blog&lt;/a&gt;&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/andreas-j/dates/2017/6/benchmarking-episerver-serializable-carts2/</guid>            <pubDate>Mon, 26 Jun 2017 10:57:05 GMT</pubDate>           <category>Blog post</category></item></channel>
</rss>