[philiptellis] /bb|[^b]{2}/
Never stop Grokking


Monday, November 14, 2011

Analysing network characteristics using JavaScript and the DOM, Part I

As Web developers, we have an affinity for developing with JavaScript. Whatever the language used in the back end, JavaScript and the browser are the primary language-platform combination available at the user’s end. It has many uses, ranging from silly to experience-enhancing.

In this post, we’ll look at some methods of manipulating JavaScript to determine various network characteristics from within the browser — characteristics that were previously available only to applications that directly interface with the operating system. Much of this was discovered while building the Boomerang project to measure real user performance.

What’s In A Network Anyway?

The network has many layers, but the Web developers among us care most about HTTP, which runs over TCP and IP (otherwise known jointly as the Internet protocol suite). Several layers are below that, but for the most part, whether it runs on copper, fiber or homing pigeons does not affect the layers or the characteristics that we care about.

Network Latency

Network latency is typically the time it takes to send a signal across the network and get a response. It’s also often called roundtrip time or ping time because it’s the time reported by the ping command. While this is interesting to network engineers who are diagnosing network problems, Web developers care more about the time it takes to make an HTTP request and get a response. Therefore, we’ll define HTTP latency as the time it takes to make the smallest HTTP request possible, and to get a response with insignificant server-processing time (i.e. the only thing the server does is send a response).

Cool tip: Light and electricity travel through fiber and copper at 66% the speed of light in a vacuum, or 20 × 108 metres per second. A good approximation of network latency between points A and B is four times the time it takes light or electricity to travel the distance. Greg’s Cable Map is a good resource to find out the length and bandwidth of undersea network cables. I’ll leave it to you to put these pieces together.

Network Throughput

Network throughput tells us how well a network is being utilized. We may have a 3-megabit network connection but are effectively using only 2 megabits because the network has a lot of idle time.

DNS

DNS is a little different from everything else we care about. It works over UDP and typically happens at a layer that is transparent to JavaScript. We’ll see how best to ascertain the time it takes to do a DNS lookup.

There is, of course, much more to the network, but determining these characteristics through JavaScript in the browser gets increasingly harder.

Measuring Network Latency With JavaScript

HTTP Get Request

My first instinct was that measuring latency simply entailed sending one packet each way and timing it. It’s fairly easy to do this in JavaScript:

var ts, rtt, img = new Image;
img.onload=function() { rtt=(+new Date - ts) };
ts = +new Date;
img.src="/1x1.gif";

We start a timer, then load a 1 × 1 pixel GIF and measure when its onload event fires. The GIF itself is 35 bytes in size and so fits in a single TCP packet even with HTTP headers added in.

This kinda sorta works, but has inconsistent results. In particular, the first time you load an image, it will take a little longer than subsequent loads — even if we make sure the image isn’t cached. Looking at the TCP packets that go across the network explains what’s happening, as we’ll see in the following section.

TCP Handshake and HTTP Keep-Alive

TCP handshake: SYN-ACK/SYN-ACK

When loading a Web page or image or any other Web resource, a browser opens a TCP connection to the specified Web server, and then makes an HTTP GET request over this connection. The details of the TCP connection and HTTP request are hidden from users and from Web developers as well. They are important, though, if we need to analyze the network’s characteristics.

The first time a TCP connection is opened between two hosts (the browser and the server, in our case), they need to “handshake” This takes place by sending three packets between the two hosts. The host that initiates the connection (the browser in our case) first sends a SYN packet, which kind of means, “Let’s SYNc up. I’d like to talk to you. Are you ready to talk to me?” If the other host (the server in our case) is ready, it responds with an ACK, which means, “I ACKnowledge your SYN.” And it also sends a SYN of its own, which means, “I’d like to SYNc up, too. Are you ready?” The Web browser then completes the handshake with its own ACK, and the connection is established. The connection could fail, but the process behind a connection failure is beyond the scope of this article.

Once the connection is established, it remains open until both ends decide to close it, by going through a similar handshake.

When we throw HTTP over TCP, we now have an HTTP client (typically a browser) that initiates the TCP connection and sends the first data packet (a GET request, for example). If we’re using HTTP/1.1 (which almost everyone does today), then the default will be to use HTTP keep-alive (Connection: keep-alive). This means that several HTTP requests may take place over the same TCP connection. This is good, because it means that we reduce the overhead of the handshake (three extra packets).

Now, unless we have HTTP pipelining turned on (and most browsers and servers turn it off), these requests will happen serially.

HTTP keep-alive

We can now modify our code a bit to take the time of the TCP handshake into account, and measure latency accordingly.

var t=[], n=2, tcp, rtt;
var ld = function() {
   t.push(+new Date);
   if(t.length > n) 
     done();
   else {
     var img = new Image;
     img.onload = ld;
     img.src="/1x1.gif?" + Math.random()
                         + '=' + new Date;
   }
};
var done = function() {
  rtt=t[2]-t[1];
  tcp=t[1]-t[0]-rtt;
};
ld();

With this code, we can measure both latency and the TCP handshake time. There is a chance that a TCP connection was already active and that the first request went through on that connection. In this case, the two times will be very close to each other. In all other cases, rtt, which requires two packets, should be approximately 66% of tcp, which requires three packets. Note that I say “approximately,” because network jitter and different routes at the IP layer can make two packets in the same TCP connection take different lengths of time to get through.

You’ll notice here that we’ve ignored the fact that the first image might have also required a DNS lookup. We’ll look at that in part 2.

Measuring Network Throughput With JavaScript

Again, our first instinct with this test was just to download a large image and measure how long it takes. Then size/time should tell us the throughput.

For the purpose of this code, let’s assume we have a global object called image, with details of the image’s URL and size in bits.

// Assume global object
// image={ url: "", size: "" }
var ts, rtt, bw, img = new Image;
img.onload=function() {
   rtt=(+new Date - ts);
   bw = image.size*1000/rtt;    // rtt is in ms
};
ts = +new Date;
img.src=image.url;

Once this code has completed executing, we should have the network throughput in kilobits per second stored in bw.

Unfortunately, it isn’t that simple, because of something called TCP slow-start.

Slow-Start

In order to avoid network congestion, both ends of a TCP connection will start sending data slowly and wait for an acknowledgement (an ACK packet). Remember than an ACK packet means, “I ACKnowledge what you just sent me.” Every time it receives an ACK without timing out, it assumes that the other end can operate faster and will send out more packets before waiting for the next ACK. If an ACK doesn’t come through in the expected timeframe, it assumes that the other end cannot operate fast enough and so backs off.

TCP window sizes for slow-start

This means that our throughput test above would have been fine as long as our image is small enough to fit within the current TCP window, which at the start is set to 2. While this is fine for slow networks, a fast network really wouldn’t be taxed by so small an image.

Instead, we’ll try by sending across images of increasing size and measuring the time each takes to download.

For the purpose of the code, the global image object is now an array with the following structure:

var image = [
	{url: ..., size: ... }
];

An array makes it easy to iterate over the list of images, and we can easily add large images to the end of the array to test faster network connections.

var i=0;
var ld = function() {
   if(i > 0)
      image[i-1].end = +new Date;
   if(i >= image.length)
      done();
   else {
      var img = new Image;
      img.onload = ld;
      image[i].start = +new Date;
      img.src=image[i].url;
   }
   i++;
};

Unfortunately, this breaks down when a very slow connection hits one of the bigger images; so, instead, we add a timeout value for each image, designed so that we hit upon common network connection speeds quickly. Details of the image sizes and timeout values are listed in this spreadsheet.

Our code now looks like this:

var i=0;
var ld = function() {
   if(i > 0) {
      image[i-1].end = +new Date;
      clearTimeout(image[i-1].timer);
   }
   if(i >= image.length ||
         (i > 0 && image[i-1].expired))
      done();
   else {
      var img = new Image;
      img.onload = ld;
      image[i].start = +new Date;
      image[i].timer =
            setTimeout(function() {
                       image[i].expired=true
                    },
                    image[i].timeout);
      img.src=image[i].url;
   }
   i++;
};

This looks much better — and works much better, too. But we’d see much variance between multiple runs. The only way to reduce the error in measurement is to run the test multiple times and take a summary value, such as the median. It’s a tradeoff between how accurate you need to be and how long you want the user to wait before the test completes. Getting network throughput to an order of magnitude is often as close as you need to be. Knowing whether the user’s connection is around 64 Kbps or 2 Mbps is useful, but determining whether it’s exactly 2048 or 2500 Kbps is much less useful.

Summary And References

That’s it for part 1 of this series. We’ve looked at how the packets that make up a Web request get through between browser and server, how this changes over time, and how we can use JavaScript and a little knowledge of statistics to make educated guesses at the characteristics of the network that we’re working with.

In the next part, we’ll look at DNS and the difference between IPv6 and IPv4 and the WebTiming API. We’d love to know what you think of this article and what you’d like to see in part 2, so let us know in a comment.

Until then, check out the references.

References

Tuesday, September 06, 2011

The Limits of Network Load Testing – Ephemeral Ports

I've been trying to run load tests against a node.js web app that I'm working on. I've hacked up my app to log (every 5 seconds) the number of requests per second that it handles and the amount of memory that it uses. I saw some strange numbers. I should also mention that I was using ab to throw load at the server. Perhaps not the best load tester around, but it served my purpose for this particular test.

My app started out well, handling about 1500 requests per second, but then all of a sudden it would stop serving requests. With repeated runs, the point where it would stop was always around 16K connections... in fact, it always hovered around 16384, mostly staying below, but sometimes going above it a bit (eg: 16400).

Ephemeral Ports

To those of you familiar with TCP networking, you'll recognise that value as the IANA specified number of Ephemeral Ports (49152 to 65535).

I started to log the state of all TCP connections every second using the following shell script (split onto multiple lines for readability):

while true; do
   echo -n "$( date +%s ) ";
   netstat | \
      awk '$4~/\.http-alt/ { conn[$6]++ }
           END {
              for(c in conn) {
                 printf("%s: %d\t", c, conn[c]);
              } 
              printf("\n");
           }
      ';
   sleep 1;
done
This is the output it returned:
1315341300 TIME_WAIT: 209	ESTABLISHED: 100	
1315341301 FIN_WAIT_1: 4	FIN_WAIT_2: 4	TIME_WAIT: 1892	ESTABLISHED: 92	
1315341302 FIN_WAIT_1: 1	FIN_WAIT_2: 2	TIME_WAIT: 3725	ESTABLISHED: 97	
1315341303 FIN_WAIT_2: 2	TIME_WAIT: 5426	ESTABLISHED: 97	
1315341304 FIN_WAIT_1: 1	FIN_WAIT_2: 5	TIME_WAIT: 7017	ESTABLISHED: 94	
1315341305 TIME_WAIT: 8722	ESTABLISHED: 100	
1315341306 FIN_WAIT_1: 2	TIME_WAIT: 10459	ESTABLISHED: 98	
1315341308 FIN_WAIT_1: 3	FIN_WAIT_2: 3	TIME_WAIT: 12246	ESTABLISHED: 94	
1315341309 FIN_WAIT_1: 7	TIME_WAIT: 14031	ESTABLISHED: 93	
1315341310 FIN_WAIT_1: 3	TIME_WAIT: 15937	ESTABLISHED: 97	
1315341311 TIME_WAIT: 16363	
1315341312 TIME_WAIT: 16363	
1315341314 TIME_WAIT: 16363	
1315341315 TIME_WAIT: 16363	
1315341316 TIME_WAIT: 16363	
1315341317 TIME_WAIT: 16363	
1315341318 TIME_WAIT: 16363	
1315341319 TIME_WAIT: 16363	
1315341321 TIME_WAIT: 16363	
1315341322 TIME_WAIT: 16363	
1315341323 TIME_WAIT: 16363	
1315341324 TIME_WAIT: 16363	
1315341325 TIME_WAIT: 16363	
1315341326 TIME_WAIT: 16363	
1315341328 TIME_WAIT: 16363	
1315341329 TIME_WAIT: 16363	
1315341330 TIME_WAIT: 16363	
1315341331 TIME_WAIT: 14321	
1315341332 TIME_WAIT: 12641	
1315341333 TIME_WAIT: 11024	
1315341334 TIME_WAIT: 9621	
1315341336 TIME_WAIT: 7516	
1315341337 TIME_WAIT: 5920	
1315341338 TIME_WAIT: 4227	
1315341339 TIME_WAIT: 2693	
1315341340 TIME_WAIT: 1108	
1315341341 TIME_WAIT: 23
There are a few things to note here.
  1. I ran ab with concurrency set to 100, so the number of connections in the ESTABLISHED, and FIN_WAIT_{1,2} states should be about 100 for the most part
  2. I was polling netstat once per second + the amount of time it took netstat to return, so you might notice missing seconds in between
  3. Requests were being handled at over 1000 per second, so many of the connections would have gone from the SYN_SENT state to TIME_WAIT in less than 1 second.

It's pretty clear here that everything just stops once the number of connections reaches 16363, and my app's logs confirm that. ab ends up timing out somewhere near the end of this report (which should probably be the subject of a different blog post).

Now if we look closely, we notice that it takes 30 seconds from the first result we see to when the number of connections in the TIME_WAIT state start dropping below 16363

TIME_WAIT

A few points should be noted about the TIME_WAIT state.
  1. Only the endpoint that sends the first FIN will end up in a TIME_WAIT state. I'll tell you later why this is important
  2. A connection remains in the TIME_WAIT state for twice the connection's MSL (See RFC793)

So we now have a whole load of TCP connections in the TIME_WAIT state and the client (the load tester) has run out of ephemeral ports to keep running the test. This is independent of the load tester used, and how it handles its sockets (poll, select, multiple threads, multiple processes, whatever). This is a limit imposed by the Operating System in order to comply with the TCP spec, and in general make sure the internet doesn't break.

Now there are various ways to get around this limit. The simplest is to just use multiple boxes to run your performance test all simultaneously hitting your server. The more boxes you use, the more likely it is that your server will break down before your client hits the port limit. But what if you don't have access to so many boxes (in a world without EC2 for example), or it's too expensive, or you're not utilising the other resources (CPU/RAM/Network Interface) on your client boxes and would like to get a little more out of them?

You could program your load tester to use SO_REUSEADDR, which would allow it to reuse sockets that are in the TIME_WAIT state, but that could backfire if there's still data on the network.

MSL

Now remember that TIME_WAIT is twice the MSL. RFC793 recommends setting the MSL to 2 minutes, which would leave TIME_WAIT at 4 minutes. Luckily no current implementation uses this value. Linux has its TIME_WAIT length hardcoded to 60 seconds (implying a 30 second MSL), while BSD has set to 15 seconds (sysctl net.inet.tcp.msl) and tunable at run time.

To verify, I reran the test after setting the MSL to 5 seconds:

sudo sysctl -w net.inet.tcp.msl=5000

And these were the results:

1315344661 TIME_WAIT: 1451	ESTABLISHED: 100	
1315344662 FIN_WAIT_2: 5	TIME_WAIT: 3272	ESTABLISHED: 95	
1315344664 FIN_WAIT_1: 1	TIME_WAIT: 5010	ESTABLISHED: 99	
1315344665 TIME_WAIT: 6574	ESTABLISHED: 100	
1315344666 FIN_WAIT_1: 11	FIN_WAIT_2: 2	TIME_WAIT: 7908	ESTABLISHED: 87	
1315344667 TIME_WAIT: 9689	ESTABLISHED: 100	
1315344668 TIME_WAIT: 11155	ESTABLISHED: 100	
1315344669 FIN_WAIT_1: 3	TIME_WAIT: 12522	ESTABLISHED: 97	
1315344671 FIN_WAIT_2: 2	TIME_WAIT: 13655	ESTABLISHED: 98	
1315344672 FIN_WAIT_1: 4	FIN_WAIT_2: 12	TIME_WAIT: 13847	ESTABLISHED: 84	
1315344673 FIN_WAIT_1: 2	TIME_WAIT: 13218	ESTABLISHED: 98	
1315344674 FIN_WAIT_1: 2	TIME_WAIT: 13723	ESTABLISHED: 97	
1315344675 FIN_WAIT_1: 2	TIME_WAIT: 14441	ESTABLISHED: 98	
1315344677 FIN_WAIT_1: 4	FIN_WAIT_2: 6	TIME_WAIT: 13946	ESTABLISHED: 90	
1315344678 FIN_WAIT_1: 3	FIN_WAIT_2: 15	TIME_WAIT: 14670	ESTABLISHED: 82	
1315344679 FIN_WAIT_1: 2	TIME_WAIT: 15164	ESTABLISHED: 98	
1315344680 FIN_WAIT_1: 2	TIME_WAIT: 15062	ESTABLISHED: 98	
1315344681 TIME_WAIT: 15822	ESTABLISHED: 100	
1315344683 FIN_WAIT_1: 4	TIME_WAIT: 15855	ESTABLISHED: 82	
1315344684 FIN_WAIT_1: 15	TIME_WAIT: 15506	ESTABLISHED: 84	
1315344685 FIN_WAIT_2: 9	TIME_WAIT: 15928	ESTABLISHED: 91	
1315344687 FIN_WAIT_1: 4	TIME_WAIT: 15356	ESTABLISHED: 96	
1315344688 FIN_WAIT_1: 2	FIN_WAIT_2: 8	TIME_WAIT: 15490	ESTABLISHED: 90	
1315344689 FIN_WAIT_1: 1	TIME_WAIT: 15449	ESTABLISHED: 99	
1315344690 FIN_WAIT_1: 3	TIME_WAIT: 15801	ESTABLISHED: 97	
1315344692 FIN_WAIT_1: 1	TIME_WAIT: 15882	ESTABLISHED: 70	
1315344693 FIN_WAIT_1: 3	TIME_WAIT: 16106	ESTABLISHED: 56	
1315344694 FIN_WAIT_1: 3	TIME_WAIT: 15637	ESTABLISHED: 2	
1315344695 TIME_WAIT: 14166	
1315344697 TIME_WAIT: 12588	
1315344698 TIME_WAIT: 10454	
1315344699 TIME_WAIT: 8917	
1315344700 TIME_WAIT: 7441	
1315344701 TIME_WAIT: 5735	
1315344702 TIME_WAIT: 4119	
1315344703 TIME_WAIT: 1815	
1315344704 TIME_WAIT: 88	

The test runs longer here because it actually gets to 50000 connections. We're doing about 1500 requests per second, so it would take just under 11 seconds to exhaust all 16384 ports, but since ports are reusable in 10 seconds, we would have reclaimed the first 3000 ports in this time and then just continue to use them.

Here's the catch though, you can only do this on BSD. Linux doesn't let you change the MSL. Also, DO NOT DO THIS IN PRODUCTION!

TIME_WAIT, FIN and HTTP keep-alive

Now I mentioned earlier that only the endpoint that sends the first FIN packet will enter the TIME_WAIT state. This is important because in production, you really do not want your server to end up with a lot of connections in the TIME_WAIT state. A malicious user could, for example, make a large number of HEAD requests to your server, and either wait for them to terminate, or specify Connection:close but leave the connection open for your server to close by sending the first FIN packet.

How do you deal with this?

Thursday, July 14, 2011

CouchDB drivers for NodeJS

I've been looking at writing some code in NodeJS, and wanted to use CouchDB as the data store (note, I don't have relational data). I've come across the following libraries, all coincidentally on github.
  • couch-client
  • cradle
  • noddycouch
  • node-couch
  • node-couchdb
  • node-couchdb-min
At this point I've only looked at the documentation, so I don't have a detailed idea of which I'd use. If you have your own favourites, please mention them in the comments.

couch-client

The documentation seems reasonably rich, and it has all the methods that I'd like to use. My only concern at this point is that it batches up writes. This may not be ideal for my particular use case, but it isn't a show stopper.

There are no installation/setup docs, so I assume I've got to clone the github repo to get it.

cradle

I've been impressed with cradle's docs so far, and it's also available through npm, which is a plus. It also distinguishes between creating a document with and without an id, which may be required in my use case.

Lastly, cradle mentions an SSL use case, which I think is important. The other libraries may also have SSL support, but they don't explicitly call it out.

noddycouch

No docs available, but example.js has code examples on how to use it.

node-couch

Again, no docs available, except to say that it was inspired by the jQuery module for NodeJS.

node-couchdb

Very good documentation and seems to be a very thorough API, but the owner says he isn't maintaining the package any more. That's a big downside.

node-couchdb-min

This appears to be a minimal version of node-couchdb.

...===...