Wednesday, 23 March 2016

Upgrading Sitecore - my experience

Upgrading Sitecore is one of the things that every Sitecore developer should do. Depending on solution design, it can be very easy or very painful. I would like to present my experience of this process.

Solution that I was upgrading, was developed on Sitecore 7.2 and I was upgrading it to version 8.0.

Application was relatively small. There was one application server and one database server. Requests from clients was passed by nginx proxy server. Architecture looked like this:



After checking how application was built and maintained, I sadly realize that it will be kind of hard time.

There are two ways to upgrade Sitecore:
  • Upgrade version by version, using update packages, to achieve desired state
  • Install clean Sitecore and merge it with existing application code and database content
Knowing that solution had a lot of legacy code and was developed poorly, I decided to take second approach.

1. Cleaning up the solution

Solution had very large technological debt. Developers have made a number of sins Sitecore application can have:
  • Changes in configuration files was made directly in Web.config and default Sitecore patches.
  • Changes in Sitecore functionality was made directly in Sitecore directory (/sitecore)
  • Some files was edited directly on the production application server and were not committed to code repository
To clean up this mess, what I had to do, was:
  • Create new project in solution that will contain all custom configurations and new functionalities
  • Find all references to custom functionalities, such as pipelines or events, in configurations and move it to prepared project
  • Create new project in solution that will contain all overridden Sitecore functionalities
  • Find all references to all overridden functionalities and move it to prepared project
I decided to separate this two things, to make new functionalities more portable, to be used in other projects.

2. Upgrade

After cleaning up the solution, I could go to upgrade. I downloaded new Sitecore and I set it up. Next I downloaded fresh production databases from backup, so I had up to date data. After setting it all up, I was having two instances - fresh Sitecore 8.0 with clean databases, and old Sitecore 7.2 with fresh production data. At that time RAZL, tool from Hedgehog Development, was REALLY helpful. I could easily transfer content into 8.0's databases. Unfortunately that time available version didn't have great features: deep compare and lightning mode (http://hedgehogdevelopment.github.io/razl/comparing#deep-compare), so I had to check branches deeply by hand or just override whole branches.



Some of the custom features that was developed for 7.2 didn't work as expected, so I had to fix them (contact with Support was very helpful). But at the end of the day, application, upgraded to version 8.0, started working!

Some of the main glitches I've been through:

3. Deployment to new servers

Present website was running on Windows Server 2008 and MS SQL Server 2008 R2, so we decided to create fresh servers with Windows Server 2012, IIS 8.0 and MS SQL Server 2014. These servers was used to test if all contents and functionalities work, and afterwards, performance tests. After that these servers will become new production servers.

To make sure that content is up to date, I installed on the new application server, another RAZL instance. I connected it with local Sitecore 8.0 and currently used Sitecore on production. In this way I could easily download new content directly from production. You need to know that connecting RAZL with Sitecore will cause application pool recycle, because new dll and configuration will be installed.

To make sure that application result is transferred from new server, I have added new additional header in applicationHost.config. Thanks to that, I could easily check it from server response:

X-Server: NEW APPLICATION

4. Deployment to production

Regarding that nginx was the first gate to application server, it was possible to bind new application server to this existing server. New domain alias was created, secured with http basic authentication and binded to new application server. It was something like application-sc8.mycompany.com.

New alias domain was added in:
  • IIS website domain bindings
  • Sitecore site configuration (hostName attribute)

In this approach:
  • Changes in domain records was not needed - current domain still points to existing proxy. Only one change was necessary - switch domain point locations on nginx. Production domain now points to new application server and alias domain, used for test, points to old production server - just to check if all content was transferred.
  • Switching the servers was instant.
In the deployment night, we just switched server bindings on nginx, cleared content cache and voila! Application that runs on Sitecore 8, was pushed to production.

Friday, 4 March 2016

Excluding Standard Values items from search results

Last time I was configuring my search index and I found that there are Standard Values items in search results. Quick research in google showed me that I can hide this items by creating computed field, which will mark these items.

Few moments later, after another glimpse at the index configuration, I found my issue. In crawler configuration I forgot to define root of the search:

<locations hint="list:AddCrawler">
  <crawler type="Sitecore.ContentSearch.SitecoreItemCrawler, Sitecore.ContentSearch">
    <Database>web</Database>
    <Root>/sitecore/</Root>
  </crawler>
</locations>

I want to search only under the Home item, so I should define it like this:

<Root>/sitecore/content/home</Root>

:)

Well, it was easy one. But then I though how could I exclude Standard Values items from index, without setting root. Solution with creating computed field and then checking it while fetching results, sounds too tricky for me. So I created custom crawler which checks if item is standard values item:

/// <summary>
/// Custom Item Crawler
/// </summary>
public class CustomItemCrawler : SitecoreItemCrawler
{
    protected override void DoAdd(IProviderUpdateContext context, SitecoreIndexableItem indexable)
    {
        var item = indexable.Item;
 
        // - Don't add Standard values Item -
 
        if (!StandardValuesManager.IsStandardValuesHolder(item))
        {
            base.DoAdd(context, indexable);
        }
    }
}

And configuration:

<locations hint="list:AddCrawler">
  <crawler type="MyAssembly.Crawlers.CustomItemCrawler, MyAssembly">
    <Database>web</Database>
    <Root>/sitecore/</Root>
  </crawler>
</locations>