hckr.fyi // thoughts

IISNode Performance Debugging and Optimization

by Michael Szul on

When Node.JS first launched, it was a *nix-based platform, and Windows was nowhere to be found, but at the time, there was a small shift going on at Microsoft that saw the company experimenting with HTML/CSS/JavaScript Windows Store applications, along with a renewed interested in JavaScript as a fundamental programming language. Microsoft was somewhat concerned that they would miss the boat on Node.JS. The last time this happened was with Ruby on Rails, which saw a significant amount of developers abandon (or never pick up) Windows machines in favor of Mac's, and OSX's Unix-based terminal and file structure that lent itself perfectly to Ruby on Rails (and other *nix-based) development.

As a result, they went all-in on JavaScript, which is one of the reasons Node.JS development started as a first-class citizen in Visual Studio Code. Eventually, Node.JS began shipping with a Windows installer, and a project called IISNode was launched that allowed for deploying Node.JS applications to Windows machines under the IIS web server.

Unfortunately, information is scarce on IISNode deployment tactics, and the original developer is no longer with Microsoft (or the project); however, the project is still being maintained by the Azure team since IISNode runs Azure's Node.JS web application instances.

IISNode can be slow and ill-performant at times, but there are some key tweaks you can make to your configuration (beyond standard Node.JS performance tweaks) that will speed things up.

Configuration

The first thing we need to look at is the IISNode configuration itself. IISNode relies on configuration elements within the web.config file in the root of your application, but it can be overridden with an issnode.yml file--what you use depends on how you like to configure things.

Here's an example web.config entry optimized for performance (and debugging):

<iisnode nodeProcessCommandLine="C:\Program Files\nodejs\node.exe --no-deprecation --no-warnings"
        promoteServerVars="HTTP_UID,HTTP_PUBCOOKIE_USER,LOGON_USER,HTTP_SHIBSESSIONID"
        node_env="production"
        nodeProcessCountPerApplication="8"
        debugHeaderEnabled="true"
        devErrorsEnabled="true" />
    

I have two debug and development values set that you can ignore. The configuration items that directly affect performance are the node_env attribute and the nodeProcessCountPerApplication attribute. Most will be familiar with node_env, and will likely already have that set to production. For nodeProcessCountPerApplication, this indicates the number of cores used. Since Node.JS is a single event loop, it only uses a single processor. IISNode allows you to spawn multiple processes in order to load balance between cores. Setting this value to "0" (zero) will spawn one process per core, but sometimes with virtual cores, it's been known to spawn one extra.

You can also adjust the maxConcurrentRequestsPerProcess. The default is 1024. Out of the box, a deployed IISNode application will default to a single core with max concurrent requests set at 1024. That's not a lot of concurrent requests if you have a bad configuration. Be sure to adjust appropriately.

The other thing to pay attention to is that if you're deploying a standard Express JavaScript application, you might be serving statics files with Node. If you are, this is adding to the strain on your maxConcurrentRequestsPerProcess, and is a lot slower than just having IIS handle static files. You can fix this by using the built-in static file module along with a rewrite rule.

First the rewrite rule:

<rule name="StaticContent" stopProcessing="true">
          <match url="([\S]+[.](jpg|jpeg|gif|css|png|js|ts|cscc|less|ico|html|map|svg))" />
          <action type="None" />
    </rule>
    

Now some static file handling:

<add name="StaticFilesCss" path="*.css" verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
    <add name="StaticFilesPng" path="*.png" verb="*" modules="StaticFileModule" resourceType="File" requireAccess="Read" />
    

The above would be added to the <handlers/> node in the web.config, and you'll want to put them before the IISNode handler. Also, the above example is just rewriting CSS and PNG files. You'll want to adjust your static file serving to meet your needs.

Another thing you can do is alter the client content cache directives by putting the following code in your <system.webServer/> node:

<staticContent>
          <clientCache cacheControlMode="UseMaxAge" />
    </staticContent>
    

This will default the client cache to the max age, allowing the client's browser to do some heavy lifting.

Database

The configuration options above should improve your performance exponentially, but if you are still having problems, check your event loop blocking calls. In particular, check your database connections.

If you're deploying to IIS on a Windows machine, are you connecting to SQL Server? If so, you're likely using the "node-msssql" package that sits on top of Tedious.

One of the confusing aspects of "node-mssql" is that documentation is up-in-the-air about the best way to handle connections and connection pools. One thing is for certain: Don't close your connection pools yourself. Let the module (and SQL Server) handle that for you. Manually closing the connection pools can decrease your performance significantly.

In fact, while working on performance tuning an application, I noticed the connection pools were being closed after the database queries were being executed. Just by removing the calls to pool.close() I was able to move from 400 requests in 10 seconds to 4,000 requests in 10 seconds.

These are just a handful of quick tips, and there are other ways you can improve performance as well, such as introducing application caches (whether "node-cache" or Redis), but first always tune IISNode.