Get Near-Zero Load Time in Both Development and Production
Even with the Roslyn compiler deployed, individual pages of Kentico can still take some time to load for the first time. This article tells you how to speed it up significantly using precompilation, even in development installations. Manual testing can then be done with very short load times.
Once you use the PowerShell script below (and some configuration steps), you should be able to navigate across the Kentico administration interface and all live sites quickly, even during the very first session. The speed of the initial requests will be the same as if Kentico had been precompiled to production using Visual Studio’s MSDeploy functionality. The advantage of this how-to, against MSDeploy, is that instance files are not modified at all (as with MSDeploy). The instance can be further developed. The script can be scheduled to precompile files and changed for each development cycle.
Moreover, in production environments, the script can warm up Kentico after each regular recycling of its application pool.
The difference in speed can be seen in the video. The left side shows Kentico with precompilation, the right side shows dynamic compilation. Both test runs have been done in clean environments (IIS restarted, SQL server restarted, temporary folders cleaned, etc.). Navigating through the administration UI and the pages took about half of the time.
To skip directly to the solution, go to the How to Decrease Load Times chapter below.
A Bit of .NET Background
The main artifacts used in every ASP.NET Web Forms application are:
- Declarative markup files (.aspx, .ascx, .ashx and .asmx) files
- Code-behind files (.aspx.cs and similar)
- Designer files (.aspx.designer.cs and similar)
- Other code files (.cs)
A web application is always compiled into a .dll file. That file usually has the same name as the Visual Studio (VS) project. In case of Kentico, it is the CMSApp.dll file.
The important thing is that this file isn’t, by far, the only executable file of the application. During the VS build, the web application project gets checked for type safety, but, in fact, it isn’t being compiled in its entirety.
All the markup files and their designer files get compiled later—usually at runtime—and they produce separate .dll files.
Let’s get a bit deeper into the .NET technology for the moment.
Every .NET code must be compiled at least twice before it performs its actions. In a very basic scenario (like console apps), it is being compiled:
- From the C# or other CLI language into the Instruction Language (IL or MSIL in our case)
- From IL (MSIL) into the native machine code of the specific processor architecture (x86, AMD64, etc.)
In the case of the ASP.NET Web Forms applications, there has to be one more phase in the beginning:
- Web Forms declarative XML markup + designer files + code-behind files → C#
- C# → MSIL
- MSIL → machine code
In ASP.NET MVC, the flow is similar:
- Razor → C#
- C# → MSIL
- MSIL → machine code
First Two Phases
For the first two phases, the following rules apply:
Every markup file in the solution usually gets compiled into its own ‘shadow’ .dll file. That file isn’t being compiled into the project output folder (the ‘bin’ folder) but into the temporary folder. Normally, it is the ‘C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\’ folder. And most importantly, these files get created by the Just In Time (JIT) compiler at first requests of particular pages. This is why the first load of each page takes more time than the subsequent requests do.
The third phase (MSIL → machine code) happens right after the first two. The compiled machine code is then cached in the memory until the application pool’s worker process (w3wp.exe) times out, is recycled, or IIS has stopped entirely.
With the steps below done, the first two compilation phases are skipped on each application start. The only compilation at start time will be MSIL → machine code.
A Bit of Kentico Background
Kentico stores select types of code files in its SQL Server database. These are the ‘virtual objects’:
- Alternative form layouts
- Form layouts
- Page layouts
- Page template layouts
- Web part layouts
- CSS stylesheets
- Web part containers
Except for the last two, all those types may contain .NET code, mostly in the form of a declarative markup. As with the markup stored in the file system, these files also don’t get compiled during the VS build. At runtime, they are dynamically retrieved from the database (using a virtual path), and their shadow .dll files get compiled into the aforementioned temporary folder. Unless the code of a virtual object is changed, the shadow file is reused during each subsequent MSIL → machine code compilation (each application start).
How to Decrease Load Times
The following steps will precompile both the physical code files and virtual objects. Upon each start of the application, only the last one of the three compilation steps will have to be done by the Common Language Runtime:
Web Forms declarative XML markup + designer files + code-behind files → C#
C# → MSIL
- MSIL → machine code
1. Optional: Optimize your IIS and web.config settings
In general, I recommend developing with a ‘web application’ project type, using ‘Local IIS’, not ‘IIS Express’.
Then, especially in production environments, it can be a good idea to tweak timeout settings.
By default, application pools’ worker processes are terminated after 20 minutes of inactivity. Then all the compiled machine code in the RAM cache gets destroyed. This timeout value can be set to a longer period or disabled completely by setting it to zero.
In IIS Manager, right-click an app pool, select ‘Advanced Settings …’, and alter the ‘Idle Time-out (minutes)’ setting.
There is also another setting that influences when the worker processes get terminated: automatic recycling. By default, it is set to 1,740 minutes. It is often recommended to schedule the recycling event to a certain off-peak time of day.
It can be set in IIS Manager by right-clicking the app pool and selecting ‘Recycling …’
The PowerShell script below can then be scheduled to precompile and warm up the instance immediately upon recycling. Recycling usually takes just a few seconds. And, in the default configuration, recycling happens in an overlapping manner (a new worker process is started while the old one serves the last few running requests). So, if fixed recycling daytime is set to, e.g., 1:00 AM, then the script can be scheduled to 1:01 AM or later. It then warms up the site as if it had been used actively before.
Moreover, the ‘/configuration/system.web/compilation’ element in the web.config can be tweaked to suit developers’ specific needs better. It also might be beneficial in production.
For example, the ‘tempDirectory’ attribute can be used to redirect the compilation output to an instance-specific ASP.NET temporary folder. In larger development projects, it is recommended to redirect to a RAM drive.
Assuming that the developer usually makes only certain types of changes to the code, the ‘optimizeCompilations’ element can also help significantly.
2. Download the PowerShell script
The Precompile-KenticoInstances.ps1 script uses the aspnet_compiler.exe utility to compile to MSIL. If multiple Kentico instances are installed on the workstation or server, it precompiles them in parallel.
Get the code
The next step is to write IIS IDs and paths into the script file.
3. Get the IIS site ID and application path
For each Kentico instance, do the following:
Note down the filesystem physical path of the Kentico CMS folder.
Then, in IIS Manager, do the following:
- Navigate to the ‘Sites’ node in the ‘Connections’ pane.
- Note down the ‘ID’ value of the site.
If Kentico lives inside a virtual application, do these additional steps:
- Right-click the virtual application
- In the right-hand pane, select ‘Advanced Settings …’
- Note down the ‘Virtual Path’ property value.
In the Precompile-KenticoInstances.ps1 file, locate the ‘$Paths’ array in the first line of the script. For each Kentico instance, create an array item with the following format:
If the instance is in the root of an IIS site, you can omit the second semicolon and ‘virtual path’ part completely.
One virtual application called ‘Kentico9InVirtualApplication’ under ‘Default Web Site’ and another instance in its own IIS site called ‘Kentico9InSite’ would result in the following:
4. Recommended: Mirror virtual objects in the filesystem
If you wish to do a fully automated regular precompilation and warm up, you can turn on the Deployment Mode. Just go to the Kentico administration interface à ‘System’ → ‘Virtual objects’ and click ‘Store all virtual objects in file system’. The script will then be able to compile them automatically.
5. Run the PowerShell script
The script can either be run manually (after a manual build) or automatically. It can be set up as a task in an automated build process or scheduled to be run right after the production instance’s application pool has been recycled.
Please note that in the scheduler you need to set the task to be run with elevated credentials.
6. Optional: Precompile virtual objects in the administration UI
If virtual objects can’t be mirrored in the file system (e.g., when the Continuous Integration feature is used), then they can still be precompiled semi-automatically.
In ‘System’ → ‘Virtual objects’, click ‘Test virtual objects’. The shadow .dll files will be compiled into a temporary folder.
The script can take a few minutes to run. A Kentico 9 web application project with the Dancing Goat site and Deployment Mode gets compiled in about 13 minutes on an i5-3550 CPU.
With web site projects the subsequent runs take around 30 seconds (if no files got changed in the meantime).
The advantage is that pages can be viewed with very little initial load time. No more waiting for each and every page to compile.
Note about Virtual Objects
It would be a completely legitimate question of whether virtual objects can be precompiled right off the database. Yes, with some changes in the Kentico platform code, it would be possible to write a slightly different PowerShell script. The Precompile-KenticoInstancesAdvanced.ps1 script is capable of creating an ad-hoc Kentico child application domain, marshalling the .NET objects into that domain and precompiling them with the correct Kentico base folder (instead of powershell.exe’s base folder).
But, the hard roadblock turned out to be the ASP.NET platform code, which uses the unsafe code blocks for constructing virtual objects’ virtual paths. Such code cannot be executed in the PowerShell host, even when the execution policy is set to ‘Bypass’.
The workaround would be to write a simple custom web service in Kentico that would initiate the compilation. The service could be invoked by the script.
A Vision of Absolutely Zero Load Time
There are techniques to precompile to the machine code. This way, there would have been absolutely no load time. But the techniques proved themselves to be highly error-prone and should be avoided. The only way to use them would be to write a very robust precompilation solution in Visual Studio.
Using the techniques described in this article, you can leverage the benefits of precompilation in all your environments without some of the downsides of MSDeploy. Manual testing in development environments can be done with the speedy load times of precompiled sites without losing the ability to edit the files. Production environments can remain highly responsive, even after application recycling—this can also be applied to MVC sites. With all the time that you can save using this approach, you can be even more productive.