The C10K problem

The C10K problem

[Help save the best Linux news source on the web -- subscribe to Linux Weekly News!]

It's time for web servers to handle ten thousand clients simultaneously,don't you think? After all, the web is a big place now.

And computers are big, too. You can buy a 1000MHz machinewith 2 gigabytes of RAM and an 1000Mbit/sec Ethernet card for $1200 or so. Let's see - at 20000 clients, that's50KHz, 100Kbytes, and 50Kbits/sec per client. It shouldn't take any more horsepower than that to take four kilobytes from the disk and send them to the network once a second for eachof twenty thousand clients.(That works out to $0.08 per client, by the way. Those$100/client licensing fees some operating systems charge are starting to look a little heavy!) So hardware is no longer the bottleneck.

In 1999 one of the busiest ftp sites, cdrom.com, actually handled 10000 clients simultaneouslythrough a Gigabit Ethernet pipe.As of 2001, that same speed is now being offered by several ISPs,who expect it to become increasingly popular with large business customers.

And the thin client model of computing appears to be coming back instyle -- this time with the server out on the Internet, servingthousands of clients.

With that in mind, here are a few notes on how to configure operating systems and write code to support thousands of clients. The discussioncenters around Unix-like operating systems, as that's my personal areaof interest, but Windows is also covered a bit.

Contents

Related Sites

In October 2003, Felix von Leitner put together an excellent web pageand presentation about network scalability,complete with benchmarks comparing various networking system calls and operating systems.One of his observations is that the 2.6 Linux kernel really does beat the 2.4 kernel,but there are many, many good graphs that will give the OS developers food for thought for some time.(See also the Slashdotcomments; it'll be interesting to see whether anyone does followup benchmarks improving on Felix's results.)

Book to Read First

If you haven't read it already, go out and get a copy ofUnix Network Programming : Networking Apis: Sockets and Xti (Volume 1)by the late W. Richard Stevens. It describes many of the I/Ostrategies and pitfalls related to writing high-performance servers.It even talks about the 'thundering herd' problem.And while you're at it, go read Jeff Darcy's notes on high-performance server design.

(Another book which might be more helpful for thosewho are *using* rather than *writing* a web server isBuilding Scalable Web Sites by Cal Henderson.)

I/O frameworks

Prepackaged libraries are available that abstract some of the techniques presented below, insulating your code from the operating system and making it more portable.

  • ACE, a heavyweight C++ I/O framework,contains object-oriented implementations of some of these I/O strategiesand many other useful things.In particular, his Reactor is an OO way of doing nonblocking I/O, andProactor is an OO way of doing asynchronous I/O.
  • ASIO is an C++ I/O frameworkwhich is becoming part of the Boost library. It's like ACE updated for the STL era.
  • libevent is a lightweight CI/O framework by Niels Provos. It supports kqueue and select,and soon will support poll and epoll. It's level-triggered only, I think,which has both good and bad sides. Niels has a nice graph of time to handle one event as a function of the number of connections. It shows kqueue and sys_epollas clear winners.
  • My own attempts at lightweight frameworks (sadly, not kept up to date):
    • Poller is a lightweight C++ I/O framework that implements a level-triggered readiness API using whatever underlying readiness API you want (poll, select, /dev/poll, kqueue, or sigio).It's useful for benchmarks that comparethe performance of the various APIs. This document links to Poller subclasses below to illustrate how each of the readiness APIscan be used.
    • rn is a lightweight C I/O framework that was my second tryafter Poller. It's lgpl (so it's easier to use in commercial apps) andC (so it's easier to use in non-C++ apps). It was used in some commercialproducts.
  • Matt Welsh wrote a paperin April 2000 about how to balance the use of worker thread andevent-driven techniques when building scalable servers. The paper describes part of his Sandstorm I/O framework.
  • Cory Nelson's Scale! library - an async socket, file, and pipe I/O library for Windows

I/O Strategies

Designers of networking software have many options. Here are a few:
  • Whether and how to issue multiple I/O calls from a single thread
    • Don't; use blocking/synchronous calls throughout, and possibly use multiple threads or processes to achieve concurrency
    • Use nonblocking calls (e.g. write() on a socket set to O_NONBLOCK) to start I/O,and readiness notification (e.g. poll() or /dev/poll) to know when it's OK to start the next I/O on that channel.Generally only usable with network I/O, not disk I/O.
    • Use asynchronous calls (e.g. aio_write()) to start I/O, and completion notification (e.g. signals or completion ports)to know when the I/O finishes. Good for both network and disk I/O.
  • How to control the code servicing each client
    • one process for each client (classic Unix approach, used since 1980 or so)
    • one OS-level thread handles many clients; each client is controlled by:
      • a user-level thread (e.g. GNU state threads, classic Java with green threads)
      • a state machine (a bit esoteric, but popular in some circles; my favorite)
      • a continuation (a bit esoteric, but popular in some circles)
    • one OS-level thread for each client (e.g. classic Java with native threads)
    • one OS-level thread for each active client (e.g. Tomcat with apache front end; NT completion ports; thread pools)
  • Whether to use standard O/S services, or put some code into the kernel (e.g. in a custom driver, kernel module, or VxD)

The following five combinations seem to be popular:

  1. Serve many clients with each thread, and use nonblocking I/O and level-triggered readiness notification
  2. Serve many clients with each thread, and use nonblocking I/O and readiness change notification
  3. Serve many clients with each server thread, and use asynchronous I/O
  4. serve one client with each server thread, and use blocking I/O
  5. Build the server code into the kernel

1. Serve many clients with each thread, and use nonblocking I/O and level-triggered readiness notification

... set nonblocking mode on all network handles, and useselect() or poll() to tell which network handle has data waiting.This is the traditional favorite.With this scheme, the kernel tells you whether a file descriptor is ready,whether or not you've done anything with that file descriptor since the last timethe kernel told you about it. (The name 'level triggered' comes from computer hardwaredesign; it's the opposite of 'edge triggered'.Jonathon Lemon introduced the terms in his BSDCON 2000 paper on kqueue().)

Note: it's particularly important to remember that readiness notification from thekernel is only a hint; the file descriptor might not be ready anymore when you tryto read from it. That's why it's important to use nonblocking mode when usingreadiness notification.

An important bottleneck in this method is that read() or sendfile() from disk blocks if the page is not in core at the moment;setting nonblocking mode on a disk file handle has no effect.Same thing goes for memory-mapped disk files.The first time a server needs disk I/O, its process blocks,all clients must wait, and that raw nonthreaded performance goes to waste.
This is what asynchronous I/O is for, but on systems that lack AIO,worker threads or processes that do the disk I/O can also get around thisbottleneck. One approach is to use memory-mapped files,and if mincore() indicates I/O is needed, ask a worker to do the I/O,and continue handling network traffic. Jef Poskanzer mentions thatPai, Druschel, and Zwaenepoel's 1999 Flash web server uses this trick; they gave a talk at Usenix '99 on it.It looks like mincore() is available in BSD-derived Unixes like FreeBSDand Solaris, but is not partof the Single Unix Specification.It's available as part of Linux as of kernel 2.3.51, thanks to Chuck Lever.

But in November 2003 on the freebsd-hackers list, Vivek Pei et al reported very good results using system-wide profiling of their Flash web serverto attack bottlenecks. One bottleneck they found wasmincore (guess that wasn't such a good idea after all)Another was the fact that sendfile blocks on disk access;they improved performance by introducing a modified sendfile() that return something like EWOULDBLOCKwhen the disk page it's fetching is not yet in core.(Not sure how you tell the user the page is now resident...seems to me what's really needed here is aio_sendfile().)The end result of their optimizations is a SpecWeb99 score of about 800on a 1GHZ/1GB FreeBSD box, which is better than anything onfile at spec.org.

There are several ways for a single thread to tell which of a set of nonblocking sockets are ready for I/O:

  • The traditional select()
    Unfortunately, select() is limited to FD_SETSIZE handles. This limit is compiled in to the standard library and user programs.(Some versions of the C library let you raise this limit at user app compile time.)

    See Poller_select(cc, h)for an example of how to use select() interchangeably with other readiness notification schemes.

  • The traditional poll()
    There is no hardcoded limit to the number of file descriptors poll() can handle,but it does get slow about a few thousand, since most of the file descriptorsare idle at any one time, and scanning through thousands of file descriptorstakes time.

    Some OS's (e.g. Solaris 8) speed up poll() et al by use of techniques like poll hinting,which wasimplemented and benchmarked by Niels Provos for Linux in 1999.

    See Poller_poll(cc, h,benchmarks)for an example of how to use poll() interchangeably with other readiness notification schemes.

  • /dev/poll
    This is the recommended poll replacement for Solaris.

    The idea behind /dev/poll is to take advantage of the fact that oftenpoll() is called many times with the same arguments.With /dev/poll, you get an open handle to /dev/poll, andtell the OS just once what files you're interested in by writing to that handle;from then on, you just read the set of currently ready file descriptors from that handle.

    It appeared quietly in Solaris 7(see patchid 106541)but its first public appearance was in Solaris 8; according to Sun,at 750 clients, this has 10% of the overhead of poll().

    Various implementations of /dev/poll were tried on Linux, but none of them perform as well as epoll, and were never really completed./dev/poll use on Linux is not recommended.

    See Poller_devpoll(cc, hbenchmarks)for an example of how to use /dev/poll interchangeably with many other readiness notification schemes. (Caution - the example is for Linux /dev/poll, might not work right on Solaris.)

  • kqueue()
    This is the recommended poll replacement for FreeBSD (and, soon, NetBSD).

    See below. kqueue() can specify either edge triggering or level triggering.

2. Serve many clients with each thread, and use nonblocking I/O and readiness change notification

Readiness change notification (or edge-triggered readiness notification)means you give the kernel a file descriptor, and later, when that descriptor transitions from not ready to ready, the kernel notifies you somehow. It then assumes youknow the file descriptor is ready, and will not send any more readinessnotifications of that type for that file descriptor until you do somethingthat causes the file descriptor to no longer be ready (e.g. until you receive theEWOULDBLOCK error on a send, recv, or accept call, or a send or recv transfersless than the requested number of bytes).

When you use readiness change notification, you must be prepared for spuriousevents, since one common implementation is to signal readiness whenever anypackets are received, regardless of whether the file descriptor was already ready.

This is the opposite of "level-triggered" readiness notification.It's a bit less forgiving of programming mistakes, sinceif you miss just one event, the connection that event was for gets stuck forever.Nevertheless, I have found that edge-triggered readiness notificationmade programming nonblocking clients with OpenSSL easier, so it's worth trying.

[Banga, Mogul, Drusha '99]described this kind of scheme in 1999.

There are several APIs which let the application retrieve 'file descriptor became ready' notifications:

3. Serve many clients with each server thread, and use asynchronous I/O

This has not yet become popular in Unix,probably because few operating systems support asynchronous I/O,also possibly because it (like nonblocking I/O) requires rethinking your application.Under standard Unix, asynchronous I/O is provided by the aio_ interface(scroll down from that link to "Asynchronous input and output"), which associates a signal and value with each I/O operation.Signals and their values are queued and delivered efficiently to the user process.This is from the POSIX 1003.1b realtime extensions, and is also in the Single Unix Specification,version 2.

AIO is normally used with edge-triggered completion notification, i.e. asignal is queued when the operation is complete. (It can also be usedwith level triggered completion notification by callingaio_suspend(),though I suspect few people do this.)

glibc 2.1 and later provide a generic implementationwritten for standards compliance rather than performance.

Ben LaHaise's implementation for Linux AIO was merged into themain Linux kernel as of 2.5.32. It doesn't use kernel threads, and has a very efficient underlying api, but (as of 2.6.0-test2) doesn't yet support sockets. (There is also an AIO patch for the 2.4 kernels,but the 2.5/2.6 implementation is somewhat different.) More info:

Suparna also suggests having a look at the the DAFS API's approach to AIO.

Red Hat ASand Suse SLES both provide a high-performance implementation on the 2.4 kernel;it is related to, but not completely identical to, the 2.6 kernel implementation.

In February 2006, a new attempt is being made to provide network AIO; see the note above about Evgeniy Polyakov's kevent-based AIO.

In 1999, SGI implemented high-speed AIO for Linux. As of version 1.1, it's said to work well with bothdisk I/O and sockets. It seems to use kernel threads.It is still useful for people who can't wait for Ben's AIO to support sockets.

The O'Reilly book POSIX.4: Programming for the Real Worldis said to include a good introduction to aio.

A tutorial for the earlier, nonstandard, aio implementation on Solarisis online at Sunsite.It's probably worth a look, but keep in mind you'll need to mentallyconvert "aioread" to "aio_read", etc.

Note that AIO doesn't provide a way to open files without blocking for disk I/O;if you care about the sleep caused by opening a disk file, Linus suggests you should simply do the open() in a different thread ratherthan wishing for an aio_open() system call.

Under Windows, asynchronous I/O is associated with the terms"Overlapped I/O" and IOCP or "I/O Completion Port". Microsoft's IOCP combines techniques from theprior art like asynchronous I/O (like aio_write) and queued completionnotification (like when using the aio_sigevent field with aio_write)with a new idea of holding back some requests to try to keep the numberof running threads associated with a single IOCP constant.For more information, see Inside I/O Completion Portsby Mark Russinovich at sysinternals.com, Jeffrey Richter'sbook "Programming Server-Side Applications for Microsoft Windows 2000"(Amazon,MSPress),U.S. patent #06223207, or MSDN.

4. Serve one client with each server thread

... and let read() and write() block. Has the disadvantage of using a whole stackframe for each client, which costs memory. Many OS's also have trouble handling morethan a few hundred threads. If each thread gets a 2MB stack (not an uncommondefault value), you run out of *virtual memory* at (2^30 / 2^21) = 512 threads on a 32 bit machine with 1GB user-accessible VM (like, say, Linux as normally shipped on x86).You can work around this by giving each thread a smaller stack,but since most thread libraries don't allow growing thread stacksonce created, doing this means designing your program to minimizestack use. You can also work around this by moving to a 64 bit processor.

The thread support in Linux, FreeBSD, and Solaris is improving,and 64 bit processors are just around the corner even for mainstream users.Perhaps in the not-too-distant future, those who prefer usingone thread per client will be able to use that paradigm evenfor 10000 clients.Nevertheless, at the current time, if you actually want to support that many clients,you're probably better off using some other paradigm.

For an unabashedly pro-thread viewpoint, see Why Events Are A Bad Idea (for High-concurrency Servers) by von Behren, Condit, and Brewer, UCB, presented at HotOS IX. Anyone from the anti-thread camp care to point out a paper that rebuts this one? :-)

LinuxThreads
LinuxTheads is thename for the standard Linux thread library. It is integrated into glibc sinceglibc2.0, and is mostly Posix-compliant, but with less than stellar performanceand signal support.
NGPT: Next Generation Posix Threads for Linux
NGPT is a projectstarted by IBM to bring good Posix-compliant thread support to Linux. It'sat stable version 2.2 now, and works well... but the NGPT team has announcedthat they are putting the NGPT codebase into support-only mode because they feel it's "the best way to support the communityfor the long term". The NGPT team will continue working to improveLinux thread support, but now focused on improving NPTL.(Kudos to the NGPT team for their good work and the graceful way theyconceded to NPTL.)
NPTL: Native Posix Thread Library for Linux
NPTL is a project by Ulrich Drepper(the benevolent dict^H^H^H^Hmaintainer of glibc) and Ingo Molnarto bring world-class Posix threading support to Linux.

As of 5 October 2003, NPTL is now merged into the glibc cvs tree as an add-ondirectory (just like linuxthreads), so it will almost certainly be releasedalong with the next release of glibc.

The first major distribution to include an early snapshot of NPTL was Red Hat 9.(This was a bit inconvenient for some users, but somebody had to break the ice...)

NPTL links:

Here's my try at describing the history of NPTL(see also Jerry Cooperstein's article):

In March 2002,Bill Abt of the NGPT team, the glibc maintainer Ulrich Drepper, and others met to figure out what to do about LinuxThreads.One idea that came out of the meeting was to improve mutex performance;Rusty Russell et al subsequently implementedfast userspace mutexes (futexes)),which are now used by both NGPT and NPTL.Most of the attendees figured NGPT should be merged into glibc.

Ulrich Drepper, though, didn't like NGPT, and figured he could do better.(For those who have ever tried to contribute a patch to glibc,this may not come as a big surprise :-)Over the next few months, Ulrich Drepper, Ingo Molnar,and others contributed glibc and kernel changesthat make up something called the Native Posix Threads Library (NPTL).NPTL uses all the kernel enhancements designed for NGPT,and takes advantage of a few new ones.Ingo Molnar described the kernel enhancements as follows:

While NPTL uses the three kernel features introduced by NGPT: getpid() returns PID, CLONE_THREAD and futexes; NPTL also uses (and relies on) amuch wider set of new kernel features, developed as part of this project.

Some of the items NGPT introduced into the kernel around 2.5.8 gotmodified, cleaned up and extended, such as thread group handling(CLONE_THREAD). [the CLONE_THREAD changes which impacted NGPT'scompatibility got synced with the NGPT folks, to make sure NGPT does notbreak in any unacceptable way.]

The kernel features developed for and used by NPTL are described in thedesign whitepaper, http://people.redhat.com/drepper/nptl-design.pdf ...

A short list: TLS support, various clone extensions (CLONE_SETTLS,CLONE_SETTID, CLONE_CLEARTID), POSIX thread-signal handling, sys_exit()extension (release TID futex upon VM-release), the sys_exit_group()system-call, sys_execve() enhancements and support for detached threads.

There was also work put into extending the PID space - eg. procfs crasheddue to 64K PID assumptions, max_pid, and pid allocation scalability work.Plus a number of performance-only improvements were done as well.

In essence the new features are a no-compromises approach to 1:1 threading - the kernel now helps in everything where it can improve threading, andwe precisely do the minimally necessary set of context switches and kernelcalls for every basic threading primitive.

One big difference between the two is that NPTL is a 1:1 threading model,whereas NGPT is an M:N threading model (see below). In spite of this, Ulrich's initial benchmarksseem to show that NPTL is indeed much faster than NGPT. (The NGPT teamis looking forward to seeing Ulrich's benchmark code to verify the result.)
FreeBSD threading support
FreeBSD supports both LinuxThreads and a userspace threading library.Also, a M:N implementation called KSE was introduced in FreeBSD 5.0.For one overview, see www.unobvious.com/bsd/freebsd-threads.html.

On 25 Mar 2003, Jeff Roberson posted on freebsd-arch:

... Thanks to the foundation provided by Julian, David Xu, Mini, Dan Eischen,and everyone else who has participated with KSE and libpthread developmentMini and I have developed a 1:1 threading implementation. This code worksin parallel with KSE and does not break it in any way. It actually helpsbring M:N threading closer by testing out shared bits. ...
And in July 2006, Robert Watson proposed that the 1:1 threadingimplementation become the default in FreeBsd 7.x:
I know this has been discussed in the past, but I figured with 7.x trundling forward, it was time to think about it again. In benchmarks for many common applications and scenarios, libthr demonstrates significantly better performance over libpthread...libthr is also implemented across a larger number of our platforms, and is already libpthread on several. The first recommendation we make to MySQL and other heavy thread users is "Switch to libthr", which is suggestive, also! ...So the strawman proposal is: make libthr the default threading library on 7.x.
NetBSD threading support
According to a note from Noriyuki Soda:
Kernel supported M:N thread library based on the SchedulerActivations model is merged into NetBSD-current on Jan 18 2003.
For details, see An Implementation of Scheduler Activations on the NetBSD Operating System by Nathan J. Williams, Wasabi Systems, Inc., presented at FREENIX '02.
Solaris threading support
The thread support in Solaris is evolving... from Solaris 2 to Solaris 8, the defaultthreading library used an M:N model, but Solaris 9 defaults to 1:1 model thread support.See Sun's multithreaded programming guideand Sun's note about Java and Solaris threading.
Java threading support in JDK 1.3.x and earlier
As is well known, Java up to JDK1.3.x did not support any method of handling network connections other than one thread per client. Volanomark is a good microbenchmark which measures throughput in messsages per second at variousnumbers of simultaneous connections. As of May 2003, JDK 1.3implementations from various vendors are in fact able to handleten thousand simultaneous connections -- albeit with significantperformance degradation. See Table 4for an idea of which JVMs can handle 10000 connections, and howperformance suffers as the number of connections increases.
Note: 1:1 threading vs. M:N threading
There is a choice when implementing a threading library: you can eitherput all the threading support in the kernel (this is called the 1:1 threadingmodel), or you can move a fair bit of it into userspace (this is called the M:Nthreading model). At one point, M:N was thought to be higher performance,but it's so complex that it's hard to get right, and most people are moving away from it.

5. Build the server code into the kernel

Novell and Microsoft are both said to have done this at various times,at least one NFS implementation does this, khttpd does this for Linux and static web pages, and "TUX" (Threaded linUX webserver)is a blindingly fast and flexible kernel-space HTTP server by Ingo Molnar for Linux.Ingo's September 1, 2000 announcementsays an alpha version of TUX can be downloaded from ftp://ftp.redhat.com/pub/redhat/tux,and explains how to join a mailing list for more info.
The linux-kernel list has been discussing the pros and cons of thisapproach, and the consensus seems to be instead of moving web servers into the kernel, the kernel should have the smallest possible hooks addedto improve web server performance. That way, other kinds of serverscan benefit. See e.g. Zach Brown's remarksabout userland vs. kernel http servers. It appears that the 2.4 linux kernel provides sufficient power to user programs, asthe X15 server runs about as fast as Tux, but doesn't use anykernel modifications.

Comments

Richard Gooch has written a paper discussing I/O options.

In 2001, Tim Brecht and MMichal Ostrowski measured various strategies for simple select-based servers. Their data is worth a look.

In 2003, Tim Brecht posted source code for userver,a small web server put together from several servers written byAbhishek Chandra, David Mosberger, David Pariag, and Michal Ostrowski. It can use select(), poll(), epoll(), or sigio.

Back in March 1999, Dean Gaudet posted:

I keep getting asked "why don't you guys use a select/event based modellike Zeus? It's clearly the fastest." ...
His reasons boiled down to "it's really hard, and the payoff isn't clear".Within a few months, though, it became clear that people were willing to work on it.

Mark Russinovich wrote an editorial andan articlediscussing I/O strategy issues in the 2.2 Linux kernel. Worth reading, evenhe seems misinformed on some points. In particular, heseems to think that Linux 2.2's asynchronous I/O (see F_SETSIG above) doesn't notify the user process when data is ready, onlywhen new connections arrive. This seems like a bizarre misunderstanding.See also comments on an earlier draft,Ingo Molnar's rebuttal of 30 April 1999,Russinovich's comments of 2 May 1999,a rebuttal from Alan Cox,and various posts to linux-kernel. I suspect he was trying to say that Linux doesn't support asynchronous disk I/O,which used to be true, but now that SGI has implemented KAIO,it's not so true anymore.

See these pages at sysinternals.com and MSDN for informationon "completion ports", which he said were unique to NT; in a nutshell,win32's "overlapped I/O" turned out to be too low level to be convenient, anda "completion port" is a wrapper that provides a queue of completion events,plus scheduling magic that tries to keep the number of running threads constantby allowing more threads to pick up completion events if other threads thathad picked up completion events from this port are sleeping (perhaps doing blocking I/O).

See also OS/400's support for I/O completion ports.

There was an interesting discussion on linux-kernel in September 1999 titled"> 15,000 Simultaneous Connections"(and the second week of the thread).Highlights:

  • Ed Hall posted a few notes on his experiences; he's achieved >1000 connects/second on a UP P2/333 running Solaris. His codeused a small pool of threads (1 or 2 per CPU) each managing a large number of clients using "an event-based model".
  • Mike Jagdis posted an analysis of poll/select overhead, and said"The current select/poll implementation can be improved significantly,especially in the blocking case, but the overhead will still increasewith the number of descriptors because select/poll does not, andcannot, remember what descriptors are interesting. This would beeasy to fix with a new API. Suggestions are welcome..."
  • Mike posted about his work on improving select() and poll().
  • Mike posted a bit about a possible API to replace poll()/select(): "How about a 'device like' API where you write 'pollfd like' structs,the 'device' listens for events and delivers 'pollfd like' structsrepresenting them when you read it? ... "
  • Rogier Wolff suggestedusing "the API that the digital guys suggested", http://www.cs.rice.edu/~gaurav/papers/usenix99.ps
  • Joerg Pommnitz pointed out that any new API along these lines shouldbe able to wait for not just file descriptor events, but also signals and maybe SYSV-IPC. Our synchronization primitives should certainly be able todo what Win32's WaitForMultipleObjects can, at least.
  • Stephen Tweedie asserted that the combination of F_SETSIG, queued realtimesignals, and sigwaitinfo() was a superset of the API proposed in http://www.cs.rice.edu/~gaurav/papers/usenix99.ps. He also mentions thatyou keep the signal blocked at all times if you're interested in performance;instead of the signal being delivered asynchronously, the process grabs thenext one from the queue with sigwaitinfo().
  • Jayson Nordwick comparedcompletion ports with the F_SETSIG synchronous event model,and concluded they're pretty similar.
  • Alan Cox noted that an older rev of SCT's SIGIO patch is included in2.3.18ac.
  • Jordan Mendelson posted some example code showing how to use F_SETSIG.
  • Stephen C. Tweedie continued the comparison of completion ports and F_SETSIG,and noted: "With a signal dequeuing mechanism, your application is going to getsignals destined for various library components if libraries are usingthe same mechanism," but the library can set up its own signal handler,so this shouldn't affect the program (much).
  • Doug Royer noted that he'd gotten 100,000 connections on Solaris 2.6 while hewas working on the Sun calendar server.Others chimed in with estimates of how much RAM that would requireon Linux, and what bottlenecks would be hit.

Interesting reading!

Limits on open filehandles

  • Any Unix: the limits set by ulimit or setrlimit.
  • Solaris: see the Solaris FAQ,question 3.46 (or thereabouts; they renumber the questions periodically).
  • FreeBSD:

    Edit /boot/loader.conf, add the line
    set kern.maxfiles=XXXX
    where XXXX is the desired system limit on file descriptors,and reboot. Thanks to an anonymous reader, who wrote in tosay he'd achieved far more than 10000 connections on FreeBSD 4.3,and says
    "FWIW: You can't actually tune the maximum number of connectionsin FreeBSD trivially, via sysctl.... You have to do it in the /boot/loader.conf file.
    The reason for this is that the zalloci() calls for initializingthe sockets and tcpcb structures zones occurs very early insystem startup, in order that the zone be both type stable andthat it be swappable.
    You will also need to set the number of mbufs much higher, sinceyou will (on an unmodified kernel) chew up one mbuf per connectionfor tcptempl structures, which are used to implement keepalive."
    Another reader says
    "As of FreeBSD 4.4, the tcptempl structure is nolonger allocated; you no longer have to worry about one mbuf being chewedup per connection."
    See also:
  • OpenBSD:A reader says
    "In OpenBSD, an additional tweak is required to increase the number ofopen filehandles available per process: the openfiles-cur parameter in /etc/login.confneeds to be increased. You can change kern.maxfileseither with sysctl -w or in sysctl.conf but it has no effect. Thismatters because as shipped, the login.conf limits are a quite low 64for nonprivileged processes, 128 for privileged."
  • Linux: See Bodo Bauer's /proc documentation.On 2.4 kernels:
    echo 32768 > /proc/sys/fs/file-max
    
    increases the system limit on open files, and
    ulimit -n 32768
    increases the current process' limit.

    On 2.2.x kernels,

    echo 32768 > /proc/sys/fs/file-max
    echo 65536 > /proc/sys/fs/inode-max
    
    increases the system limit on open files, and
    ulimit -n 32768
    increases the current process' limit.

    I verified that a process on Red Hat 6.0 (2.2.5 or so plus patches) can open at least 31000 file descriptors this way.Another fellow has verified that a process on 2.2.12 can open at least 90000 file descriptors this way (with appropriate limits). The upper boundseems to be available memory.
    Stephen C. Tweedie postedabout how to set ulimit limits globally or per-user at boot time usinginitscript and pam_limit.
    In older 2.2 kernels, though, the number of open files per process is still limited to 1024, even with the above changes.
    See also Oskar's 1998 post,which talks about the per-process and system-wide limits on file descriptorsin the 2.0.36 kernel.

Limits on threads

On any architecture, you may need to reduce the amountof stack space allocated for each thread to avoid runningout of virtual memory. You can set this at runtime withpthread_attr_init() if you're using pthreads.

  • Solaris: it supports as many threads as will fit in memory, I hear.
  • Linux 2.6 kernels with NPTL: /proc/sys/vm/max_map_count may need to be increased to go above 32000 or so threads.(You'll need to use very small stack threads to get anywhere near that number of threads, though,unless you're on a 64 bit processor.) See the NPTL mailing list, e.g. the thread with subject "Cannot create more than 32K threads?",for more info.
  • Linux 2.4: /proc/sys/kernel/threads-max is the max number of threads;it defaults to 2047 on my Red Hat 8 system.You can set increase this as usual by echoing new values into that file,e.g. "echo 4000 > /proc/sys/kernel/threads-max"
  • Linux 2.2: Even the 2.2.13 kernel limits the number of threads,at least on Intel. I don't know what the limits are on other architectures.Mingo posted a patchfor 2.1.131 on Intel that removed this limit. It appears to be integrated into 2.3.20.

    See also Volano's detailedinstructions for raising file, thread, and FD_SET limits in the 2.2 kernel. Wow.This document steps you through a lot of stuff thatwould be hard to figure out yourself, but is somewhat dated.

  • Java: See Volano's detailed benchmark info,plus their info on how to tune various systemsto handle lots of threads.

Java issues

Up through JDK 1.3, Java's standard networking libraries mostly offered the one-thread-per-client model.There was a way to do nonblocking reads, but no way to do nonblocking writes.

In May 2001, JDK 1.4 introducedthe package java.nioto provide full support for nonblocking I/O (and some other goodies).See the release notes for some caveats.Try it out and give Sun feedback!

HP's java also includes a Thread Polling API.

In 2000, Matt Welsh implemented nonblocking sockets for Java; his performancebenchmarks show that they have advantages over blocking sockets in servershandling many (up to 10000) connections. His class library is calledjava-nbio;it's part of the Sandstorm project.Benchmarks showing performance with 10000 connections are available.

See also Dean Gaudet's essayon the subject of Java, network I/O, and threads, and the paper by Matt Welshon events vs. worker threads.

Before NIO, there were several proposals for improving Java's networking APIs:

  • Matt Welsh's Jaguar systemproposes preserialized objects, new Java bytecodes, and memory managementchanges to allow the use of asynchronous I/O with Java.
  • InterfacingJava to the Virtual Interface Architecture, by C-C. Chang andT. von Eicken, proposes memory management changes to allow the useof asynchronous I/O with Java.
  • JSR-51was the Sun project that came up with the java.nio package. Matt Welsh participated (who says Sun doesn't listen?).

Other tips

  • Zero-Copy
    Normally, data gets copied many times on its way from here to there.Any scheme that eliminates these copies to the bare physical minimum is called "zero-copy".
    • Thomas Ogrisegg's zero-copy send patch for mmaped files under Linux 2.4.17-2.4.20. Claims it's faster than sendfile().
    • IO-Lite is a proposal for a set of I/O primitives that gets rid of the need for many copies.
    • Alan Cox noted thatzero-copy is sometimes not worth the trouble back in 1999. (He did like sendfile(), though.)
    • Ingo implementeda form of zero-copy TCP in the 2.4 kernel for TUX 1.0 in July 2000, and says he'll make it available to userspace soon.
    • Drew Gallatin and Robert Picco have added some zero-copy features to FreeBSD; the idea seems to be thatif you call write() or read() on a socket, the pointer is page-aligned,and the amount of data transferred is at least a page, *and* youdon't immediately reuse the buffer, memory management tricks will beused to avoid copies. But see followups to this message on linux-kernelfor people's misgivings about the speed of those memory management tricks.

      According to a note from Noriyuki Soda:

      Sending side zero-copy is supported since NetBSD-1.6 release byspecifying "SOSEND_LOAN" kernel option. This option is now defaulton NetBSD-current (you can disable this feature by specifying"SOSEND_NO_LOAN" in the kernel option on NetBSD_current).With this feature, zero-copy is automatically enabled, if data morethan 4096 bytes are specified as data to be sent.
    • The sendfile() system call can implement zero-copy networking.
      The sendfile() function in Linux and FreeBSD lets you tell the kernel to send part or all of a file. This lets the OS do it as efficiently as possible.It can be used equally well in servers using threads or servers usingnonblocking I/O. (In Linux, it's poorly documented at the moment; use _syscall4 to call it. Andi Kleen is writing new man pages that cover this.See also Exploring The sendfile System Call by Jeff Tranter in Linux Gazette issue 91.) Rumor has it, ftp.cdrom.com benefitted noticeably from sendfile().

      A zero-copy implementation of sendfile() is on its way for the 2.4 kernel.See LWN Jan 25 2001.

      One developer using sendfile() with Freebsd reports that usingPOLLWRBAND instead of POLLOUT makes a big difference.

      Solaris 8 (as of the July 2001 update) has a new system call 'sendfilev'.A copy of the man page is here..The Solaris 8 7/01 release notesalso mention it. I suspect that this will be most useful when sendingto a socket in blocking mode; it'd be a bit of a pain to use with a nonblockingsocket.

  • Avoid small frames by using writev (or TCP_CORK)
    A new socket option under Linux, TCP_CORK, tells the kernel toavoid sending partial frames, which helps a bit e.g. when there arelots of little write() calls you can't bundle together for some reason.Unsetting the option flushes the buffer. Better to use writev(), though...

    See LWN Jan 25 2001 for a summaryof some very interesting discussions on linux-kernel about TCP_CORK and a possible alternative MSG_MORE.

  • Behave sensibly on overload.
    [Provos, Lever, and Tweedie 2000] notes that dropping incoming connections when the serveris overloaded improved the shape of the performance curve,and reduced the overall error rate. They used a smoothedversion of "number of clients with I/O ready" as a measureof overload. This technique should be easily applicable toservers written with select, poll, or any system call that returnsa count of readiness events per call (e.g. /dev/poll or sigtimedwait4()).
  • Some programs can benefit from using non-Posix threads.
    Not all threads are created equal. The clone() function in Linux(and its friends in other operating systems)lets you create a thread that has its own current working directory,for instance, which can be very helpful when implementing an ftp server.See Hoser FTPd for an example of the use of native threads rather than pthreads.
  • Caching your own data can sometimes be a win.
    "Re: fix for hybrid server problems" by Vivek Sadananda Pai (vivek@cs.rice.edu) on new-httpd, May 9th, states:

    "I've compared the raw performance of a select-based server with amultiple-process server on both FreeBSD and Solaris/x86. Onmicrobenchmarks, there's only a marginal difference in performancestemming from the software architecture. The big performance win forselect-based servers stems from doing application-level caching. Whilemultiple-process servers can do it at a higher cost, it's harder toget the same benefits on real workloads (vs microbenchmarks).I'll be presenting those measurements as part of a paper that'llappear at the next Usenix conference. If you've got postscript,the paper is available at http://www.cs.rice.edu/~vivek/flash99/"

Other limits

  • Old system libraries might use 16 bit variables to holdfile handles, which causes trouble above 32767 handles. glibc2.1 should be ok.
  • Many systems use 16 bit variables to hold process or thread id's.It would be interesting to port the Volano scalabilitybenchmark to C, and see what the upper limit on number of threads is for the various operating systems.
  • Too much thread-local memory is preallocated by some operating systems;if each thread gets 1MB, and total VM space is 2GB, that creates an upper limitof 2000 threads.
  • Look at the performance comparison graph at the bottom ofhttp://www.acme.com/software/thttpd/benchmarks.html.Notice how various servers have trouble above 128 connections, even on Solaris 2.6? Anyone who figures out why, let me know.
    Note: if the TCP stack has a bug that causes a short (200ms)delay at SYN or FIN time, as Linux 2.2.0-2.2.6 had, and the OS orhttp daemon has a hard limit on the number of connections open,you would expect exactly this behavior. There may be other causes.

Kernel Issues

For Linux, it looks like kernel bottlenecks are being fixed constantly.See Linux Weekly News,Kernel Traffic,the Linux-Kernel mailing list,and my Mindcraft Redux page.

In March 1999, Microsoft sponsored a benchmark comparing NT to Linuxat serving large numbers of http and smb clients, in which theyfailed to see good results from Linux. See also my article on Mindcraft's April 1999 Benchmarksfor more info.

See also The Linux Scalability Project.They're doing interesting work, including Niels Provos' hinting poll patch, and some work onthe thundering herd problem.

See also Mike Jagdis' work on improving select() and poll(); here's Mike's post about it.

Mohit Aron (aron@cs.rice.edu) writes that rate-based clocking in TCP can improve HTTP response time over 'slow' connections by 80%.

Measuring Server Performance

Two tests in particular are simple, interesting, and hard:

  1. raw connections per second (how many 512 byte files per second can youserve?)
  2. total transfer rate on large files with many slow clients(how many 28.8k modem clients can simultaneously downloadfrom your server before performance goes to pot?)

Jef Poskanzer has published benchmarks comparing many web servers.See http://www.acme.com/software/thttpd/benchmarks.html for his results.

I also have a few old notes about comparing thttpd to Apache that may be of interestto beginners.

Chuck Lever keeps reminding us about Banga and Druschel's paper on web server benchmarking. It's worth a read.

IBM has an excellent paper titled Java server benchmarks [Baylor et al, 2000]. It's worth a read.

Examples

Nginx is a web server that uses whateverhigh-efficiency network event mechanism is available on the target OS.It's getting popular; there are even two books about it.

Interesting select()-based servers

Interesting /dev/poll-based servers

  • N. Provos, C. Lever,"Scalable Network I/O in Linux,"May, 2000. [FREENIX track, Proc. USENIX 2000, San Diego, California (June, 2000).] Describes aversion of thttpd modified to support /dev/poll. Performance is comparedwith phhttpd.

Interesting kqueue()-based servers

Interesting realtime signal-based servers

  • Chromium's X15.This uses the 2.4 kernel's SIGIO feature together with sendfile() and TCP_CORK, and reportedly achieves higher speed than even TUX.The source is available undera community source (not open source) license. See the original announcementby Fabio Riccardi.
  • Zach Brown's phhttpd - "aquick web server that was written to showcase the sigio/siginfo eventmodel. consider this code highlyexperimental and yourself highly mental if you try and use it in a production environment."Uses the siginfo features of 2.3.21 or later, and includes the needed patchesfor earlier kernels.Rumored to be even faster than khttpd.See his post of 31 May 1999for some notes.

Interesting thread-based servers

Interesting in-kernel servers

Other interesting links

Translations

Belorussian translationprovided by Patric Conrad at Ucallweconn


Changelog

2011/07/21
Added nginx.org

$Log: c10k.html,v $
Revision 1.212  2006/09/02 14:52:13  dank
added asio

Revision 1.211  2006/07/27 10:28:58  dank
Link to Cal Henderson's book.

Revision 1.210  2006/07/27 10:18:58  dank
Listify polyakov links, add Drepper's new proposal, note that FreeBSD 7 might move to 1:1

Revision 1.209  2006/07/13 15:07:03  dank
link to Scale! library, updated Polyakov links

Revision 1.208  2006/07/13 14:50:29  dank
Link to Polyakov's patches

Revision 1.207  2003/11/03 08:09:39  dank
Link to Linus's message deprecating the idea of aio_open

Revision 1.206  2003/11/03 07:44:34  dank
link to userver

Revision 1.205  2003/11/03 06:55:26  dank
Link to Vivek Pei's new Flash paper, mention great specweb99 score


Copyright 1999-2011 Dan Kegel
dank@kegel.com
Last updated: 21 July 2011
[Return to www.kegel.com]


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值