ASP.NET Performance – Part 5 – Nginx

The previous parts of this series focused on small pieces of code we could use to maximize real-world performance – largely based off of YSlow recommendations. In this final installment we’ll think outside the box (literally) by placing a reverse proxy infront of IIS to get the most bang possible. Our technology of choice is Nginx running on Linux (while Nginx runs on windows, it doesn’t seem to support shared memory – which is critical to a number of features).

Nginx (pronounced engine-x) is an event-driven asynchronous server. Such servers are specialized at handling extreme loads while consuming limited resources. On top of this foundations are a number of modules which enable various feature. Such features include load balancing, compression, reverse proxy (which is mostly what we’ll look at), fastcgi, memcached, url rewriting and so on. Nginx is used by a number of high profile sites, including WordPress, GitHub and SourceForge. Oh, and its completely free. (Another equally popular and equally free alternative is lighttpd).

Nginx is prettty easy to configure and the configuration file is pretty easy to read. However, if you prefer to learn by doing but don’t have linux setup, then you can use the windows version (that’s how I started). You’ll be able to get a feel for it and do most of the things we’ll be looking at.

For the most part we’ll focus on Nginx’s reverse proxy module. A reverse proxy [typically] sits infront of your web server, accepting all connections and deciding on a course of action. In its simplest form, our nginx.conf file will have the following reverse proxy settings:

user www-data;
worker_processes  5;

error_log  /var/log/nginx/error.log;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    server {
        listen       80;
        server_name  oursite.com www.oursite.com;
        location / {
            proxy_set_header  x-real-IP        $remote_addr;
            proxy_set_header  x-forwarded-for  $proxy_add_x_forwarded_for;
            proxy_set_header  host             $http_host;
    		proxy_pass        http://10.10.1.1/;
        }	
    }
}

We’ve configured Nginx to listen on port 80 for connections with a host header of oursite.com or www.oursite.com. When Nginx receives a request, it will look for a matching server, and a matching location (here we are matching all locations), and then issue a request to our imaginary windows machine at 10.10.1.1. Since Nginx is actually issueing the request to our web server, we are adding headers to the request so that the web server has all necessary informaton.

The above configuration gets us up and running, but doesn’t really do anything (except for add another point of failure). However, by adding just two more lines of code, things can really get awesome:

#define a shared memory named cache (don't think this will work in windows)
proxy_cache_path /usr/local/nginx/proxy_temp/ levels=1:2 keys_zone=cache:10m inactive=10m max_size=1000M;

server {
    listen       80;
    server_name  oursite.com www.oursite.com;
    location / {
        proxy_set_header  x-real-IP        $remote_addr;
        proxy_set_header  x-forwarded-for  $proxy_add_x_forwarded_for;
        proxy_set_header  host             $http_host;
        proxy_pass http://10.10.1.1/;
        proxy_cache cache #enables the cache
    }	
}

First we’ve defined a zone where cached data is to be stored (called cache), as well as how much room to allocate in memory (for cache lookup metadata) and on disk (for the actual cached data). Then, we’ve modified our location and simply told it to allow caching using the zone called cache. This means that once oursite.com/assets/images/logon.png?23188a4 has been requested, Nginx will keep a copy on disk and use its local copy instead of forwarding the request. We are still serving the same total number of requests, but the load for our static/plain files has been moved from our Windows machine which should focus on executing our code (and which is suceptible to the C10K problem) to a highly specialized, low-resource system. There are actually a lot of benchmarks (as well as real life case studies) which show Nginx’s destroying Apache (which is thread-based like IIS) when it comes to high concurrency, low resources and response time.

Note that we aren’t just limited to caching requests for images, css and javascript file – Nginx will cache any response that has the appropriate headers. You can take your homepage, add an OutputCache (directive or MVC attribute) with the location to Downstream and watch your your site fly.

We can even add a new location to our server (locations support regular expression matching too, incase you need it), and do something like:

location /favicon.ico {
    proxy_pass	http://10.10.1.1/favicon.ico;
    proxy_cache cache;	
    expires 30d;
}

This showcases Nginx’s expires directive which can be used to set an expiry header.

Moving on, we can also use Nginx’s Gzip module to compress responses. Again, this is something IIS can do, but there’s something reassuring about letting our application machine worry exclusively on running our code. With the following lines, applied globally (so that it relates to any server definition), nginx will compress all responses greater than 500 bytes for a number of different filetypes, providing the browser isn’t < IE 7:

gzip  on;
gzip_buffers 16 8k;
gzip_comp_level 6;
gzip_proxied any;
gzip_types text/css text/plain application/x-javascript image/x-icon;
gzip_min_length 500;
gzip_disable "MSIE [1-6]\."

All of this is just the tip of the iceberg of what’s possible. Other interesting modules protect against denial of service attacks, help harden the system, support load balancing (with sticky sessions), logging and memcached integration. We can also set headers – removing the need for some of the code we wrote in Part 2. Best of all, the Nginx documentation is quite good, and the community is thriving.

This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.

12 Responses to ASP.NET Performance – Part 5 – Nginx

  1. Matt says:

    Hi Karl thx for the good article. Curious about two things.

    First, do you have any thoughts or experience with HAProxy as an alternative to nginx for load balancing?

    Also, what’s the effect on analytics (external systems like google analytics or on web logs for example) of offloading objects like images to the Nginx cache?

  2. Matt says:

    Hi Karl thx for the good article. Curious about two things.

    First, do you have any thoughts or experience with HAProxy as an alternative to nginx for load balancing?

    Also, what’s the effect on analytics (external systems like google analytics or on web logs for example) of offloading objects like images to the Nginx cache?

  3. Rafiki says:

    So your argument for doing all this is “good feeling” and someone else’s comparison between Nginx and Apache, huh? :)

  4. scoopr says:

    If you are using nginx mostly only as a caching proxy (and not say, handling any file serving on the nginx box) have you considered Varnish? It’s more or less built for this use-case, and is awesome at it. Granted nginx is pretty awesome as well.

  5. Jomit says:

    Nice article series Karl . . .

  6. Awesome article. Love to see more of this sort of thing on .NET blogs.

  7. Edwin says:

    Forget the problem of an application crash or slow data access or response time for an overloaded SAN switch port with Traverse’s service container that can monitor application response time and correlate that with the underlying storage components which are relevant to that application using its Business Container technology.
    http://zyrion.com/solutions/server.php

  8. scottgu says:

    I’d recommend also looking at the IIS7 Application Request Routing extension: http://www.iis.net/expand/ApplicationRequestRouting

    This is a free, fully supported, extension for IIS that also provides front-end load-balancing and reverse proxy support. It comes with a rich set of features and is easy to setup and manage. Its performance is also screaming fast.

    Hope this helps,

    Scott

  9. karl says:

    GS:
    If you aren’t at the point where you need to scale beyond a single server, then I agree that the value doesn’t outweigh the complexity.

    However, if I was in that scenario, I wouldn’t completely write it off either. I’d definitely be interested in finding out whether CPU and Memory usage goes down when Nginx is added to the mix. In benchmarks against Apache, Nginx is able to serve more files, more quickly, and while using less resources. But again, that’s only really interesting if you are pushing your machine…and I wouldn’t run it on Windows anyways.

  10. GS says:

    How does Nginx needed in this scenario of single box? All those features (compression, caching, routing etc) already available in IIS 7. This just adds layer of complexity and perfomance hit in single server scenario.

  11. What about the IIS7 URL rewrite module for request routing? http://learn.iis.net/page.aspx/489/using-the-application-request-routing-module/

    Personally, I’m partial to the nginx route, just curious as to the performance comparison.

  12. José Filipe says:

    Just to say: really nice posts serie.

    My job has driven me to a intranet only profile so far, but those tips are unvaluable anyway..