I've discovered a bug that is a symptom of a publishing bug. This symptom is what causes databases grow huge in load-balanced enviroments.
Another side-effect of this issue in my environment was that documents could not be properly published to load-balanced servers.
If you need an immediate fix for this problem it would require a change in the umbraco.presentation.cache.CacheRefresher() class in the Umbraco source code. If anyone knows the best way to submit patches to the core development team, please let me know and I will pass on this fix.
The bug exists in Umbraco 3.0.5 and I haven't seen any forum posts about it being fixed in V4.0, so this bug will prob also affect V4.
The bug is triggered in certain situations where a document does not finish publishing properly when someone publishes from the UI and then the background process that is set up in the global.asax.cs cannot fix the situation. The problem only happens in load balanced situations.
You will know if this bug affects you by checking the following:
1. You have a load balanced enviroment
2. The following query returns 1 or more rows
Code:select distinct nodeId, level, sortOrder from cmsDocument inner join umbracoNode on umbracoNode.id = cmsDocument.nodeId where newest = 1 and not releaseDate is null and releaseDate <= getdate() order by level, sortOrder
Here is my analysis of the code:
In the global.asax.cs file at line 62, this code sets up a timer event to run every 60 seconds:
Code: publishingTimer =
new Timer(new TimerCallback(publishingService.CheckPublishing), Context, 60000, 60000);
(Note to core team, I might suggest setting the first interval after startup to a random value between 30000 and 60000 instead of 60000, otherwise any servers that start at the same time will probably compete with each other to publish. Servers can start at the same time e.g. when Application Pools are set to recycle at a specific time of day.)
In publishingService.CheckPublishing(object sender) at line 31:
Code:foreach(Document d in Document.GetDocumentsForRelease())
{
try
{
d.HttpContext = (HttpContext)sender;
d.Publish(d.User);
library.PublishSingleNode(d.Id);
d.ReleaseDate = new DateTime(1, 1, 1); // Causes release date to be null
}
catch(Exception ee)
{
Log.Add(
LogTypes.Error,
BusinessLogic.User.GetUser(0),
d.Id,
string.Format("Error publishing node: {0}", ee));
}
}
GetDocumentsForRelease() runs the sql query that I quoted above
***********************************************************************************************
Notice that d.Publish() is called here before library.PublishSingleNode()
I will explain why this is important at the end
***********************************************************************************************
Which runs this code:
Code:public static void PublishSingleNode(int DocumentId) {
if (UmbracoSettings.UseDistributedCalls)
dispatcher.Refresh(
new Guid("27ab3022-3dfa-47b6-9119-5945bc88fd66"),
DocumentId);
else
PublishSingleNodeDo(DocumentId);
}
And in the dispatcher.Refresh() runs this code only for load-balanced servers when UmbracoSetttings.UseDistributedCalls is TRUE:
Code:try {
foreach (XmlNode n in UmbracoSettings.DistributionServers.SelectNodes("./server")) {
CacheRefresher cr = new CacheRefresher();
cr.Url = "http://" + xmlHelper.GetNodeValue(n) + GlobalSettings.Path + "/webservices/cacheRefresher.asmx";
cr.RefreshById(uniqueIdentifier, Id, _login, _password);
}
The umbraco.presentation.cache.CachRefresher() is your soap wrapper class and it has a default constructor (line 31):
Code:
public CacheRefresher() {
this.Url = "http://" + System.Web.HttpContext.Current.Request.ServerVariables["SERVER_NAME"] + "/umbraco/webservices/cacheRefresher.asmx";
}
And this default constructor is the cause of the problem! Because when the global.asax.cs timer fires and the code runs all the way to here, it is running in a background thread on the web-server. Therefore System.Web.HttpContext.Current is NULL and this will throw an exception. If anyone is reporting the problem with large databases, there will also be "Object Reference Not Set to Instance of Object" type errors in their UmbracoLog. I suggest testing if the HttpContext is null, putting try{} catch{} or commenting out that constructor line?
****************************************************************************************************
Earlier I mentioned that in publishingService.CheckPublishing(object sender) the d.Publish()
method is called before library.PublishSingleNode()....
****************************************************************************************************
The d.Publish() method succeeds every time and inserts a row into the cmsDocument table, but the PublishSingleNode() fails and throws an exception because of what I described above. Now this code runs in the background timer ON EACH WEBSERVER in the farm, so multiply the errors every 1 minute X number of servers and you will see how you get so many rows in the cmsDocument table in the database.
(EDIT)
I have tested this by artificially changing the releaseDate of a row in the cmsDocument table, after making sure I had set:
Code:
<distributedCall enable="true">
<user>0</user>
<servers>
<server>localhost</server>
</servers>
</distributedCall>
in the /config/umbracoSettings.config file.
Final recommendation is to change the umbraco.presentation.cache.CachRefresher() constructor to:
Code:
public CacheRefresher() {
if (System.Web.HttpContext.Current != null)
{
this.Url = "http://" + System.Web.HttpContext.Current.Request.ServerVariables["SERVER_NAME"] + "/umbraco/webservices/cacheRefresher.asmx";
}
}
Neil