chunkin

Some time ago I had the requirement to limit the number of requests per IP to a CouchDB-instance. I couldn’t find an option for CouchDB to achieve that, but as communication with CouchDB is based on HTTP-requests I thought that it should be possible to use nginx as reverse proxy with rate-limiting capabilities for CouchDB. The CouchDB-wiki lists some basic steps how to use nginx as reverse proxy, but rate limiting isn’t mentioned there.

Beside the actual rate limiting I noticed that nginx won’t work, because it doesn’t support chunked request-bodies out of the box, which CouchDB seems to rely onto. But to the rescue there is a nginx module called ngx_chunkin, which adds chunkin support to nginx. To get that module in Debian you need to install nginx-extras from the repository (available since squeeze-backports). The configuration of that module is straight forward as described on the wiki page. All you have to do is to put the following code snippet into the server-context of your nginx configuration:

        chunkin on;

        error_page 411 = @my_411_error;
        location @my_411_error {
                chunkin_resume;
        }

Having solved that issue, doing the actual rate limiting was the next step. Therefore nginx ships the module ngx_http_limit_req_module. All I had to do was to configure a zone for the limited requests in the http-context of the configuration:

limit_req_zone $binary_remote_addr zone=couchdb_write:10m rate=10r/s;

and the logic what to rate limit (writing requests in my case) into the server-context:

        location / {
                # POST, PUT, DELETE ... requests will get rate limiting
                if ($request_method !~* GET) {
                        rewrite ^(.*)$ /throttled$1 last;
                }

                proxy_pass       http://127.0.0.1:5985;
                proxy_buffering  off;
                proxy_set_header Host $host;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        location /throttled {
                internal;
                limit_req        zone=couchdb_write burst=10000;
                rewrite /throttled(.*) $1 break;
                proxy_pass       http://127.0.0.1:5985;
                proxy_buffering  off;
                proxy_set_header Host $host;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

So if you put together the chunkin support and the rate limiting, you’ll get the following piece of configuration for a server which does rate limiting of writing CouchDB-requests using nginx:

limit_req_zone $binary_remote_addr zone=couchdb_write:10m rate=10r/s;

server {
        listen 5984;

        chunkin on;

        error_page 411 = @my_411_error;
        location @my_411_error {
                chunkin_resume;
        }

        location / {
                # POST, PUT, DELETE ... requests will get rate limiting
                if ($request_method !~* GET) {
                        rewrite ^(.*)$ /throttled$1 last;
                }

                proxy_pass       http://127.0.0.1:5985;
                proxy_buffering  off;
                proxy_set_header Host $host;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        location /throttled {
                internal;
                limit_req        zone=couchdb_write burst=10000;
                rewrite /throttled(.*) $1 break;
                proxy_pass       http://127.0.0.1:5985;
                proxy_buffering  off;
                proxy_set_header Host $host;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
}

I really enjoyed figuring that out, because it was a lot of fun to be able to use a great tool (nginx) to extend another great tool (CouchDB), because they’re using the same protocol (HTTP).