Timeouts, TCP and streaming operations
We got a bug report in the RavenDB mailing list that was interesting to figure out. The code in question was:
foreach(var product in GetAllProducts(session)) // GetAllProducts is implemented using streaming { ++i; if (i > 1000) { i = 0; Thread.Sleep(1000); } }
This code would cause a timeout error to occur after a while. The question is why? We can assume that this code is running in a console application, and it can take as long as it wants to process things.
And the server is not impacted from what the client is doing, so why do we have a timeout error here? The quick answer is that we are filling in the buffers.
GetAllProducts is using the RavenDB streaming API, which push the results of the query to the client as soon as we have anything. That lets us parallelize work on both server and client, and avoid having to hold everything in memory.
However, if the client isn’t processing things fast enough, we run into an interesting problem. The server is sending the data to the client over TCP. The client machine will get the results, buffer them and send them to the client. The client will read them from the TCP buffers, then do some work (in this case, just sleeping). Because the rate in which the client is processing items is much smaller than the rate in which we are sending them, the TCP buffers become full.
At this point, the client machine is going to start dropping TCP packets. It doesn’t have any more room to put the data in, and the server will send it again, anyway. And that is what the server is doing, assuming that we have a packet loss over the network. However, that will only hold up for a while, because if the client isn’t going to recover quickly, the server will decide that it is down, and close the TCP connection.
At this point, there isn’t any more data from the server, so the client will catch up with the buffered data, and then wait for the server to send more data. That isn’t going to happen, because the server already consider the connection lost. And eventually the client will time out with an error.
A streaming operation require us to process the results quickly enough to not jam the network.
RavenDB also have the notion of subscriptions. With those, we require explicit client confirmation from the client to send the next batch, so a a slow client isn’t going to cause issues.
Comments
A TCP connection stays open and stable even if the receiver does not pull a single byte for hours while the sender tries to send 1TB of data. This is not the problem. TCP flow control is made to handle this. No packets are dropped.
Probably, one of your read/write calls timed out. That's all.
So it's a wrong diagnosis with the right conclusion. Timeout caused by slow processing.
or are you streaming over UDP?
mark is right. The TCP receiver tells the sender how much room is left in the buffer ("TCP receive window"), and the sender will never send packets that don't fit into the buffer.
There shouldn't be any timeout involved here. TCP connections are supposed to stay open indefinitely (until one of the sides decides to close it). However, NATs have to terminate connections after a period of idle time. RFC 5382 recommends hosts to send keepalive packets every 2 hours, requires NATs to use an idle time of at least 2 hours 4 minutes. Unfortunately, in practice many NATs use idle times as short as 5 minutes, so idle connections will get incorrectly terminated.
The unfortunate truth about networking is that if you need reliable communication, use TLS over port TCP port 443, and do everything including keep-alives protected by encryption. Otherwise, you'll get eventually screwed over by a "smart network device".
Mark, Huh?! That is pretty obviously not true. Where is the data reside in the meantime? Let us assume that the server and receiver both have 1GB of memory. I'm writing to TCP connection 1TB of data. The receiver doesn't read, the data has to be _somewhere_.
Daniel, The receive window is just how much receiver can accept without sending a confirmation back. A server is free to send more, but need to keep in memory all data that the receiver didn't ack. If it actually send more than the receiver can accept, sender risks having to resend, of course. And I'm not aware of any real world network that can just keep alive a connection for 2 hours.
Comment preview