views:

290

answers:

1

I'm tuning a server and need some guidance. This server provides the following features:

  1. an ASPX page with a DB call that is downloaded approximately every 3 minutes by several thousand machines
  2. a simple ASP.NET admin web site used by a tiny number of people but that must be highly available
  3. a WCF service that provides file synchronization to another several thousand clients using BasicHttpBinding and Streamed messages.

When an update is available to the file-sync clients (#3), they swarm the server clamoring for their new data. Without aggressive throttling they eat up all available ASP.NET worker threads and cause #1 and #2 to hang. This, as the kids say, sux0rs.

I've reproduced this problem on a 4-proc server. With a test client creating 100 simultaneous downloads, I see that 48 downloads are running simultaneously; in perfmon I see the remainder queueing up in ASP.NET->Requests In Application Queue. This jibes with the "12 concurrent threads per CPU" default setting described in this article. CPU and memory look fine; it's really just shuttling bytes with no processing.

As I see it, there are a couple of things I could do, possibly in concert.

  1. Up the maxIOThreads and maxWorkerThreads in machine.config
  2. Set up a web garden
  3. Redo the client so downloads are handled directly instead of via WCF (eg link directly to the file instead of streaming it in a message).

My questions:

  • Aside from trial and error, what are good guidelines for increasing the number of threads given that each is going to be in use for a pretty long time with low CPU/memory usage? If I go this route, how do I ensure the task switching for the increased number of threads doesn't overwhelm the system and cause more problems?
  • Would a web garden help in this situation?
  • Will I see much better performance if I get the download out of WCF and let IIS handle it entirely?

Best, roufamatic

+1  A: 

Done right, WCF is the best tool for the job. If you're sending a static file, I'd suggest looking into an async service impl (or an async HttpHandler if you want to do it in ASP.NET without WCF) that's built on a FileStream with the Asynchronous flag set, and do the client writes async as well. This will allow you to make much more efficient use of the worker threads and offload the work to the OS. Downloading 1000 copies of the same file concurrently shouldn't cause your server to bat an eye if the impl is done right.

nitzmahone
OK, I'm stumped. If all these clients are coming to the server asking for their files, how does it matter if the request is sync or async? Ultimately the server has to connect to the client and send the data, and when the files are large (which they are) it will take a long time to send and eat a thread in the process. If the server were doing something long-running I could understand this, but really it's the client that's the slowpoke.
roufamatic
Exactly. In sync mode, you have one thread per client blocked on buffer reads/writes, causing the scheduler to frequently walk around and wake them, only to usually waste the timeslice hoping the buffer write will complete. Once you've got about 20-30 of those happening, it's spending more time doing thread state restores than real work. When you offload the reads/writes using kernel async primitives, IOCP uses a very lightweight scheduler and h/w interrupts to service the buffers and only assign and poke a worker thread when a buffer is ready. Many fewer threads == much more work done.
nitzmahone
The important part is to use the async primitives on your reads and writes (read from FileStream opened w/ async FileOption set and implement the service as Begin/End with AsyncPattern=true), and use the Begin/EndRead and Begin/EndWrite with callbacks. Otherwise, you ARE just moving the problem around. The client can continue to call the method the "looks synchronous" way. This pattern can be kinda complex if you've never implemented it before (it's kind of a "ping-ponging callbacks" thing), but it WILL vastly increase your throughput for this kind of thing.
nitzmahone
Thanks for the responses. Right now the synchronous call is returning a MessageContract with some data specified as headers and the file data as a Stream in the body (as described here: http://msdn.microsoft.com/en-us/library/ms733742.aspx). Can this pattern be adapted to use what you've described, or do I need to create a new Operation that returns a FileStream?
roufamatic
Yeah, that works fine (I do it the same way). If you're not mutating the file, you can just hand off the FileStream opened with FileOptions.Asynchronous to your response object's Stream member- that alone might do the trick (eg, might not need the AsyncPattern decoration on your OperationContract, since the actual consumption of the stream is done after your operation has returned). I believe it's all async under the covers- sync operations use a blocking wrapper that you don't see. I also don't know if the ASP.NET hosting changes any of this, because I always self-host. Good luck!
nitzmahone
It looks like I won't get any benefit from going asynchronous because of IIS6: http://blogs.msdn.com/wenlong/archive/2008/04/21/wcf-request-throttling-and-server-scalability.aspx . Grrrrr.
roufamatic
Actually you can if you have 3.5 SP1: http://blogs.msdn.com/wenlong/archive/2008/08/13/orcas-sp1-improvement-asynchronous-wcf-http-module-handler-for-iis7-for-better-server-scalability.aspx. Contrary to the title, it DOES work on IIS6, but it introduces some new DOS risk.
nitzmahone
As I understand it, the DOS could come from my own swarm of clients. I can't take that risk. I'm going to look into self-hosting for the next version. Thank you for your response and comments!
roufamatic