[VERY URGENT] - Sitefinity Cache Configuration
Hi,
We have performance problems on our website (4.3.1885) in all of our environments. It may be partly be due to our cache configuration on Sitefinity, so we are in need of some explanations.
In backend "Administration > Settings > Advanced > System > Output Cache Settings > Output Cache Profiles" we create our own caching like this :
<
outputCacheSettings
>
<
profiles
>
<
add
enabled
=
"True"
duration
=
"2700"
slidingExpiration
=
"False"
maxSize
=
"500"
name
=
"Insite Caching"
/>
</
profiles
>
</
outputCacheSettings
>
Hi,
Could a Sitefinity admin answer this question, or post a link to the documentation explaining everything we want to know about the cache?
Thanks.
Hello,
In general Sitefinity implements two types of caching - OutputCache through its Output cache settings and DataCache through its OpenAccess implementation. While the data cache is turned on my default and cannot be controlled directly by Sitefinity, OutputCache is available for fine tuning by user requirements.
In backend "Administration > Settings > Advanced > System > Output Cache Settings > Output Cache Profiles" we create our own caching like this :
<
outputCacheSettings
>
<
profiles
>
<
add
enabled
=
"True"
duration
=
"2700"
slidingExpiration
=
"False"
maxSize
=
"500"
name
=
"Insite Caching"
/>
</
profiles
>
</
outputCacheSettings
>
using
Telerik.Microsoft.Practices.Unity;
using
Telerik.Sitefinity.Abstractions;
using
Telerik.Sitefinity.Web;
namespace
SitefinityWebApp
public
class
CustomPageRouteHandler: PageRouteHandler
protected
override
void
InitOutputCache(System.Web.Routing.RequestContext requestContext, PageSiteNode siteNode)
base
.InitOutputCache(requestContext, siteNode);
var cache = requestContext.HttpContext.Response.Cache;
cache.VaryByHeaders.UserAgent =
true
;
public
static
void
RegisterType()
ObjectFactory.Container.RegisterType<PageRouteHandler, CustomPageRouteHandler>();
protected
void
Application_Start(
object
sender, EventArgs e)
Bootstrapper.Initialized +=
new
EventHandler<Telerik.Sitefinity.Data.ExecutedEventArgs>(Bootstrapper_Initialized);
void
Bootstrapper_Initialized(
object
sender, Telerik.Sitefinity.Data.ExecutedEventArgs e)
if
(e.CommandName ==
"RegisterRoutes"
)
CustomPageRouteHandler.RegisterType();
staticContent>
<
clientCache
cacheControlMode
=
"UseMaxAge"
cacheControlMaxAge
=
"14.00:00:00"
/>
</
staticContent
>
<
urlCompression
doDynamicCompression
=
"true"
doStaticCompression
=
"true"
dynamicCompressionBeforeCache
=
"true"
/>
Hi,
Thanks for your answer. We are testing your solution.
I have a question about it, when we set clienCache MaxAge in web.config it's an override of OutputCache original MaxAge value ?
Regards,
Nicolas
Hi,
Thanks for your answer, Boyan, much appreciated.
It's surprising that a user agent-based cache is the default, and that there is no simple flag to revert to a classic cache. By the way, as you know, many things can make the user agent vary (whether .NET 4.0 is installed, etc...), and in our company, IE8 users do not all have the same UA.
Anyway, we do not serve browser-specific content. Does Sitefinity do it, with its components (like, say, the standard navigation menu), or does it serve the same HTML to everyone?
Thanks.
Hi,
Follow-up: we created a class derived from PageRouteHandler, as advised, in order to control the way caching works. However, we're encountering an unexpected problem: pages set to "No Caching" are now cached. I guess that in the code sample you showed, we were supposed to add a check for whether the page cache is enabled or not.
Edit: our method now looks like this:
protected
override
void
InitOutputCache(System.Web.Routing.RequestContext requestContext, PageSiteNode siteNode)
base
.InitOutputCache(requestContext, siteNode);
var config = Config.Get<SystemConfig>();
if
(config.CacheSettings.EnableOutputCache)
var cacheProfile = siteNode.OutputCacheProfile;
if
(cacheProfile.IsNullOrEmpty())
cacheProfile = config.CacheSettings.DefaultProfile;
OutputCacheProfileElement profile;
if
(!config.CacheSettings.Profiles.TryGetValue(cacheProfile,
out
profile))
throw
new
ArgumentException(
"Invalid output cache profile specified: \"0\"."
.Arrange(cacheProfile));
if
(profile.Enabled)
var cache = requestContext.HttpContext.Response.Cache;
cache.VaryByHeaders.UserAgent =
false
;
cache.SetMaxAge(
new
TimeSpan(0, 0, 172800));
Hello Thomas,
Can you please try adding an else clause where you'll set explicitly the Cacheability to None. Like this:
var config = Config.Get<SystemConfig>();
if
(config.CacheSettings.EnableOutputCache)
var cacheProfile = siteNode.OutputCacheProfile;
if
(cacheProfile.IsNullOrEmpty())
cacheProfile = config.CacheSettings.DefaultProfile;
OutputCacheProfileElement profile;
if
(!config.CacheSettings.Profiles.TryGetValue(cacheProfile,
out
profile))
throw
new
ArgumentException(
"Invalid output cache profile specified: \"0\"."
.Arrange(cacheProfile));
if
(profile.Enabled)
var cache = requestContext.HttpContext.Response.Cache;
cache.VaryByHeaders.UserAgent =
false
;
cache.SetMaxAge(
new
TimeSpan(0, 0, 172800));
else
requestContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
Hi Boyan,
Thanks for the info.
By the way, our updated method already worked for our case. I guess your update helps if we disable the cache for the whole site, as opposed to disabling the cache for certain pages, so we'll add this bit of code.
I was wondering whether we should also add the same no cache instruction in a else for the profile.Enabled. That is to say:
if
(profile.Enabled)
var cache = requestContext.HttpContext.Response.Cache;
cache.VaryByHeaders.UserAgent =
false
;
cache.SetMaxAge(
new
TimeSpan(0, 0, 172800));
else
requestContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
Hello,
Yes that should do, or you could just say:
if
(config.CacheSettings.EnableOutputCache)
.
.
.
if
(!profile.Enabled)
return
;
.
.
.
//if output cache is enabled set flag to add cache dependencies
requestContext.HttpContext.Items[PageRouteHandler.AddCacheDependencies] =
true
;
else
requestContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
Hi Boyan,
So I don't need to call Cache.SetCacheability(HttpCacheability.NoCache); explicitly there? Okay.
I added the AddCacheDependencies instruction, although I'm not sure what it does exactly, and I can't find any documentation for it.
This is our method now:
protected
override
void
InitOutputCache(System.Web.Routing.RequestContext requestContext, PageSiteNode siteNode)
base
.InitOutputCache(requestContext, siteNode);
var config = Config.Get<SystemConfig>();
if
(!config.CacheSettings.EnableOutputCache)
requestContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache);
else
var cacheProfile = siteNode.OutputCacheProfile;
if
(cacheProfile.IsNullOrEmpty())
cacheProfile = config.CacheSettings.DefaultProfile;
OutputCacheProfileElement profile;
if
(!config.CacheSettings.Profiles.TryGetValue(cacheProfile,
out
profile))
throw
new
ArgumentException(
"Invalid output cache profile specified: \"0\"."
.Arrange(cacheProfile));
if
(profile.Enabled)
var cache = requestContext.HttpContext.Response.Cache;
cache.VaryByHeaders.UserAgent =
false
;
cache.SetMaxAge(
new
TimeSpan(0, 0, 172800));
requestContext.HttpContext.Items[PageRouteHandler.AddCacheDependencies] =
true
;
Hello,
By default we add dependency on a page site node cache key, so when page site node is invalidated (e.g. Publish) the output cache key of the page should be refreshed.
All the best,
Boyan Barnev
the Telerik team
Hi Boyan,
I just noticed something else that I do not understand: pages set no "No Caching" are still cached (whether we override the InitOutputCache method or not). If I modify the content of a page set to No Caching, I cannot see the changes on other servers where the site is deployed. I can only see the changes on the server from which the modification has been done (until the site is restarted on the other servers). Although I haven't configured load balancing, so other servers aren't informed the page has been modified, shouldn't I see the changes on the page considering both the data and page caches are disabled?
I also want to add that we have not changed the "No Caching" profile settings at all, it's the default, unaltered one.
We are seeing the same issue: with two separate SF 4.3 instances that share a database, content changes made on one instance are not visible on the other. This is even with global output caching disabled, data caching set to "False", and page-level cache set to "No Caching".
John, did you ever find a solution for this? Running into the same problem for us.
Glenn: The only working solution was to configure the servers as if they were in the load-balanced configuration (following Sitefinity's load-balanced setup instructions). Basically, we had to enter the IP address(es) of the each other server into each servers SF config to allow each SF instance to communicate with the others. You also need to be sure to manually set the machineKey in web.config to the same value in all SF instances.
Ahh okay, that is what I was afraid of.
Hey Glenn,
Hi all,
Just a quick follow-up to this thread,as it seems to be getting hits every now and then.
The initially provided example wouldn't work due to the fact that the .NET implementation for the VaryByHeaders.UserAgent would not allow us to set it to false directly (techincally it would but in the background, if you check the System.Web.HttpCacheVaryByHeaders implementation you'd notice that the setter of all headers has this line:
if
(!value)
return
;
using
System;
using
System.Collections.Specialized;
using
System.Configuration;
using
System.Linq;
using
System.Web.Routing;
using
Telerik.Microsoft.Practices.Unity;
using
Telerik.Sitefinity.Abstractions;
using
Telerik.Sitefinity.Web;
namespace
SitefinityWebApp
public
class
PRHCustom : Telerik.Sitefinity.Web.PageRouteHandler
/*NOTE! - for this sample we're reading the value of the of the VaryByHeaders.UserAgent form the web.config <appSettings> section
* To specify whether you want the same chached version of the page to be served on all browsers (VaryByHeaders.UserAgent=false;)
* please add an entry to your web.config <appSettigns> similar to:
*
<appSettings>
<add key="VaryByHeaders_UserAgent" value="false"/>
*
* where value can be true (default Sitefintiy chache behavior) or false
*/
protected
override
void
InitOutputCache(RequestContext requestContext, PageSiteNode siteNode)
base
.InitOutputCache(requestContext, siteNode);
if
(!(ConfigurationManager.AppSettings[
"VaryByHeaders_UserAgent"
]).IsNullOrEmpty())
if
(!Boolean.Parse(ConfigurationManager.AppSettings[
"VaryByHeaders_UserAgent"
].ToString()) && requestContext.HttpContext.Response.Cache.VaryByHeaders !=
null
&& requestContext.HttpContext.Response.Cache.VaryByHeaders.UserAgent)
//Need to use reflection as .NET does not allow for setting false value once we've set it to true
var headers = requestContext.HttpContext.Response.Cache.VaryByHeaders;
var headersCollection = (NameObjectCollectionBase)headers.GetType().GetField(
"_headers"
, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(headers);
var method = headersCollection.GetType().GetMethod(
"BaseRemove"
, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
method.Invoke(headersCollection,
new
string
[]
"User-Agent"
);
/// <summary>
/// This method has to be invoked in your Global.asax (subscribe to Bootstrapper_Initialized and verify inside your Bootstrapper_Initialized event handler that e.CommandName == "Bootstrapped"),
/// as it would effectively carry out replacing the default PageRouteHandler with the custom one through the ObjectFactory
/// </summary>
public
static
void
RegisterType()
ObjectFactory.Container.RegisterType<Telerik.Sitefinity.Web.PageRouteHandler, PRHCustom>();
Boyan,
I have tried your latest example and it appears to not cache at all. When I comment out the registration of the handler I get caching on a per user-agent basis. But if I run with that route it hits the database for every visit from the same machine/browser.
Any idea what I may be doing wrong?
Thanks,
Ryan