I deployed the initial version of my simple-but-fast CMS, using VirtualPathProvider. It allows me to add new content without reloading the entire site. The cool part is that the database holds entire pages and their code-behind. After the pages are read from disk, they are compiled and cached by the runtime. This allows them to contain controls and other dynamic aspects, rather than just having static content. In addition, they can now be indexed and searched by SQL Server.
Another cool aspect of my implementation is that the cached pages are replaced automatically when the database is updated. So, no restarts or other hacks are needed when the content changes. I still have a few admin-related improvements left to go, but enough is working now that I'm going to start using it for real content. I will be moving my content pages into the DB next, and will add search support after that.
Also, this update fixed a very odd problem that I still can't fully explain. Apparently, in some scenarios, when the home page redirects to ~/p/default.aspx, IIS was returning a page with Content-Type: application/xhtml, which worked fine with Firefox, but would cause IE8 to hang. If you navigated directly to the target page, the content type was text/html, as it should be. I've read about something similar happening with pages declared using DOCTYPE Strict, but these pages use Transitional, so I don't have a good explanation. Even worse, the problem seems to have gone away on its own after the recent update, with no specific fix on my part. Something tells me that it's not really over yet. Sigh.
I was checking out the site's stats on Google's Webmaster Tools site today. According to them, as of 2 Dec 2009, pages from this site were loading in an average of 0.5 sec, which is faster than 96% of all sites. Google measures that number from the client side, using the Google Toolbar. It will be interesting to see if I can improve it further, although there's a good chance that router hops, Internet weather, etc, will start to dominate at around that speed. Might need a CDN to do much better.
Added an article on avoiding errors caused by generating robots.txt from within Visual Studio.
Added more detailed services description pages. Added the code download zip file for the book. Added an (old) article on Enterprise class software. Completed the first-pass migration to UltraDNS, although I think I may still have some tuning left to go.
I'm starting to migrate DNS for this site to UltraDNS to help maximize performance in that area as I describe in my book. Since their fees are based in part on the number of queries, I'm trying to minimize the load by moving some resource records to other domains, such as the mail servers. I also had several other domains using a local DNS server that's registered in this domain, so I've moved those NS records too.
After the last update, the elapsed time numbers as reported in the IIS logs look much better.
While reviewing the IIS logs, I noticed that the elapsed time for some requests occasionally were unusually long: a very simple page could take well over a second. Some of this is just "Internet weather," with slow links between the server and the client, but that's not the whole story. It turns out that minimizing initial request latency on a low-traffic site can requires a few tricks that aren't needed with high-traffic sites. Part of the problem is that this server is shared with a couple of other applications at the moment (mainly SQL Server for another site). Since the site isn't very busy yet, if it's idle for long enough, the IIS worker process can get paged out or the data pages in SQL Server can be released—then things are slow for the first requests after that.
A key design goal of mine has been to make sure that most pages and images on the site are cacheable by http.sys and proxies. To meet that goal, I take care to make sure that both the HTML and the HTTP response headers are the same for all users. For example, I don't set cookies from most of the pages themselves. The only exceptions at the moment are the registration and login pages (which aren't enabled yet).
Since most pages can be cached in the kernel, I decided to increase the UriScavengerTime for http.sys from the default of 120 seconds to 43200 seconds (12 hours). This will allow cacheable items to stay in kernel memory longer, which will reduce access latency after a long idle period. The site is still pretty small, so the increase in kernel memory use should be minimal.
I also disabled compression on id.ashx from web.config. That reduced the payload size from 36 bytes to 3.
Cleaned up the content and layout on a few pages, and added navigation menus on the left and right, using a three-column fluid layout. Since the CSS file uses a far-future cache expiration date, I just changed the filename. I'm also using ASP.NET themes, so that's all I need to do: the new file is picked up automatically by the theme.
Also: modified web.config to set a cookie on robots.txt, as part of a long-term effort to automate bot detection without relying on the User-Agent string. I'm also forcing Cache-Control: public, to help encourage caching by proxies, in spite of the cookie.
Also: made a few changes to the AppPool and IIS settings. Since the overall traffic is still light, I decided to disable recycling for now, which also requires disabling timeouts. I also tweaked the IIS log settings from the default, to include cookie values and the HTTP referrer. That will allow me to run some interesting reports later on. I double-checked to make sure that changing the settings didn't disturb http.sys caching; everything is OK there.
As part of my regular routine, I ran logparser against the site's IIS logs, to check for HTTP 500 and 404 errors. To my surprise, I found a 500 error; it was in the ajax1.aspx code sample for the book (called from file28.htm). It turns out that I introduced a bug when it was integrated into the site from the standalone Visual Studio solution. Since I use ASP.NET themes, the code required StyleSheetTheme to be set to the empty string, to avoid the runtime insisting on the presence of a <head> element.