HOWTO: Mass Shared Hosting on Windows with IIS6

I got this recent question concerning the "limitations of ISAPI" as it pertains to mass shared hosting on IIS6.

Hmm... I had to do a double-take because I was not aware of such limitations of ISAPI - in fact, Microsoft has published a mass shared hosting accelerator solution based on ISAPI, as I will shortly explain... so, yup, mass shared hosting is definitely possible with IIS6.

Question:

Hi David,

Please tell me the limitations of ISAPI as we are in the process to develop ISAPI filter for Scalable Windows Hosting . We are having around 80,000 websites, migrating from Unix(Zeus) to Windows (IIS6.0) sever. As we will be having some dynamic sites which use CGI-Perl scripts.

As single IIS server can only host around 20,000 sites so we are creating a ISAPI filter so that we can use virtual directory concept and can host sites as virtual directories (as many as we want ) . But will it be good if we are planning for ISAPI (Performance) and does ISAPI(IIS) support dynamic sites which will be having CGI-Perl scripts.

Please give your suggestion and inputs on this,It will really be very much helpfull for me to carry out this migration process.

Thanks and Regards

Answer:

Hey now... let's first get into the right mindset: IIS 6.0 and Windows Server 2003 is more than scalable and performant enough to handle your scenario. :-) You just have to get a couple of non-obvious things straightened out:

  • Software - Run the right code/customization
  • Hardware - Server can handle the simultaneous load of the provisioned websites
  • Policy - Make the right server configuration and provision the websites

I am glad to assist and point you in the right direction on this endeavor

Why do all this on IIS6?

First off, I want to say that you had to do the same sort of customization with other web servers like Zeus or Apache since VHosting is not default and requires new directives and configuration. The key difference here is that there were well known modules and configuration for Zeus/Apache to do this.

Well, now there is a similarly "well known" module  to do the same for IIS... in the form of Windows-based Shared Hosting Accelerator (I swear I am not involved with the naming of any of these things; I am just the messenger ;-) ). Actually, it is closer to a whole set of solutions and pre-developed code, one of which is the ISAPI Filter that you are talking about implementing (but we have already implemented it and added several extensibility points to it that you can probably quickly take advantage of, like custom-authentication). I have pinged a couple of people internally and hopefully they will be contacting you soon (they are all currently on vacation until the end of the month).

As for dynamic sites... that should not be a big problem as long as you stick with an ISAPI implementation of Perl that fits your functional, performance, and reliability requirements.

On Migration...

I think the key thing you want to do when migrating across platforms is to make good effort to take advantage of the new platform. I think the worst thing is to simply take what "has always worked" and try to make it work on the new platform - that is like bringing over old baggage which will inevitably slow you down, and you never realize the benefits of the new platform.

Now, I am not advocating that you simply throw everything out and start over; I am simply suggesting that you look at the advantages of the new platform and write new code/processes to take advantage of them, while simultaneously try to re-use as much of the existing code base (possibly small tweaks here and there).

For example, when choosing between ISAPI and CGI, always choose ISAPI for IIS. Why?

  • Spawning new processes is cheap on *nix but not on Windows, so do not insist on poking at the Windows Achille's Heel by insisting CGI - this is why IIS favors ISAPI yet still supports CGI. If the ISAPI version is not "stable" enough for whatever reason, do not back down on your quality bar - get the vendor to fix their ISAPI issues - I'm serious, I would not mind giving a hand on this if need be (unless the vendor has a fundamental architectural issue, at which point, that is obviously out of my hands... ;-) ).
  • Use ISAPI to take advantage of asynchronous File/Network IO on Windows that is not prevalent on *nix platforms - you get tremendous scalability and performance for free. The tradeoff here is that you have to get used to asynchronous IO programming, which is not that hard of a design pattern once you understand it, but it is definitely not "obvious". If needed, I actually have some planned upcoming sample code to illustrate how to write ISAPI code designed to work well both synchronous and asynchronously (for example, VectorSend falls into this category).
  • On IIS6, you want to use ISAPI to get access to functions like TransmitFile or VectorSend to optimize user/kernel transitions and take advantage of the kernel response cache. CGI simply cannot do these things.

About Provisioning and Application Pools

As for how you want to provision all of the websites... here is how to think about it all.

  • The number "20,000" refers to the number of dedicated IIS Site Bindings (i.e. the traditional way of defining/configuring websites) that will "comfortably" start for IIS6 within a minute. IIS6 can actually push past 50,000 for such "rich" IIS Site Bindings, but it is clearly too rich for mass shared hosting.
  • Both Apache and IIS have similar limitations based on configuration and the solutions are remarkably similar - instead of dedicated bindings for each website, extend the server core with code that programmatically re-routes from Host headers (which would require dedicated bindings) to virtual directories (which would not require dedicated bindings). The results of such URL rewrites clearly has to stay "in-process" to ensure that a website's reliability depends entirely on the w3wp.exe itself and not on where the request is routed from (i.e. suppose w3wp.exe[1] is rewriting URLs for hosts that cross application pool boundaries and IIS routed it to w3wp.exe[2]. Suppose that w3wp.exe[1] is running a bad website that causes it to 503 and go offline - it is now unable to rewrite URLs and the sites on w3wp.exe[2] which depend on the re-routing are also effectively 503).

However, it is very possible to utilize Application Pools to partition the URL namespace - you just have to configure things a little differently due to how the shared hosting ISAPI filter works.

  • Ultimately, HTTP.SYS routes requests to different Application Pools based on the dedicated IIS Site Bindings, and since you cannot use the ISAPI Filter to rewrite URLs to cross processes, you must make sure that you have dedicated IIS Site Bindings for these "virtual" websites. But the cost here is not that big since you are still aggregating many "virtual" websites using the ISAPI Filter into a single dedicated IIS Site Binding.
  • Make sure to run the ISAPI Filter globally so that the Host->Vdir conversion happens for all application pools

Logically, this is how things are configured, both before and after:

 Before (rich bindings)
W3SVC/1/ServerBinding="www.site1.com"
W3SVC/1/ROOT/Path="C:\websites\www.site1.com"
W3SVC/1/ROOT/AppPoolId="AppPool1"

W3SVC/2/ServerBinding="www.anothersite.com"
W3SVC/2/ROOT/Path="C:\websites\www.anothersite.com"
W3SVC/2/ROOT/AppPoolId="AnotherAppPool"

W3SVC/3/ServerBinding="www.site3.com"
W3SVC/3/ROOT/Path="C:\websites\www.site3.com"
W3SVC/3/ROOT/AppPoolId="AppPool1"

* Notice how each Host header website needs its own Website ID, AppPoolId, and ROOT/Path.
* This is what is "rich" about the configuration


After (mass hosting)
W3SVC/Filters/WSHA/FilterPath="C:\filters\SharedHostingISAPIFilter.dll"
W3SVC/Filters/FilterLoadOrder="WSHA"

W3SVC/1/ServerBinding="www.site1.com" "www.site3.com"
W3SVC/1/ROOT/www_site1_com/Path="C:\websites\www.site1.com"
W3SVC/1/ROOT/www_site3_com/Path="C:\websites\www.site3.com"
W3SVC/1/ROOT/AppPoolId="AppPool1"

W3SVC/2/ServerBinding="www.anothersite.com"
W3SVC/2/ROOT/www_anothersite_com/Path="C:\websites\www.anothersite.com"
W3SVC/2/ROOT/AppPoolId="AnotherAppPool"

* Notice how I just aggregated two host header websites onto the same Website ID
* HTTP.SYS will still route the websites to the same Application Pools as before

Mass shared hosting is all about how to pack in websites under W3SVC/1/ROOT using the ISAPI Filter while provisioning the websites between Application Pools for isolation as shown by W3SVC/2/ROOT/AppPoolId. You essentially only need as many dedicated IIS Site Bindings as you wish to partition with Application Pools. So before, you would have needed 20,000 Site IDs isolated by 10 application pools (and IIS6 would scale like 20,000 websites)... while after, you only need 10 Site IDs each corresponding to a different application pool, and the ISAPI Filter routes the websites from HostHeader to vdir (and IIS6 runs like 10 websites).

With clever server configuration and ISAPI Filter code, you retain all Application Pool isolation benefits at low cost from the point of view of a rich IIS Site Binding.

If you have any questions, feel free to tack on Comments for the benefit of all readers.

//David