Hi Luca,

some fragmentary answer:

Am 01.02.2016 um 10:17 schrieb Luca Toscano:

...

- AsyncRequestWorkerFactor is used to regulate the amount of requests
that a single process/threads block can handle, calculating the value
periodically using the idle threads/workers available. In case of
workers maxed out, the keep alive sockets/connections are closed to free
some space.

...

If my understanding is correct (I doubt it but let's assume this) then I
have the following questions:

- Would it be worth to add more info in the "how it works" section? A
first read may bring the user to think that the listening thread is the
one doing the actual work, rather than the workers, being a bit puzzled
when reading the AsyncRequestWorkerFactor section.

...

- The summary talks about "supporting threads" and given the fact that
AsyncRequestWorkerFactor is added to ThreadsPerChild, it raises the
question about how many of them are created at startup. Conversely, is
it a way to say: the number of threads for each process are
ThreadsPerChild but since they now perform also small bursts of work
(like keep alive house keeping and flushing data to clients) the total
amount of connections allowed should be more to make room for all these
connection/socket states?

The number of worker threads per process is constant during the lifetime from the process creation to its end. It is equals to ThreadsPerChild and the sole purpose of this configuration item.

AsyncRequestWorkerFactor comes into the image, because in contrast to the traditional MPMs prefork, worker and winnt, event does not have a 1:1 relation between worker threads and client connections. Event is designed to scale better than those in terms of connections. It should handle a lot more connections with the same number of threads. How can this work? It works by freeing the worker threads from the connection handling in certain situations where the thread would actually simply wait until it can write back the next part of the response or until the next request on the same connection arrives. But this design results in some over commitment. What happens if by bad luck over time we accepted lots of connections, which were mostly idle and then suddenly many of them start activity which needs a worker thread for each of them. Then it might happen, that we do not have enough worker threads in the process that accepted the connections. We don't have a way to move such connections to another child process. Once the connection is accepted it stays with the same process.

To reduce the likeliness of such shortage in free worker threads, the number of connections we accept per process is limited. The limit is not a fixed number or some fixed multiple of ThreadsPerChild, but instead it is calculated based on the number of idle worker threads. If that number gets small during run time, the number of additional connections we accept decreases until at some point that process won't accept any new connections. This situation can be monitored via mod_status.

Let's have a look at the formula: The docs say:

ThreadsPerChild + (AsyncRequestWorkerFactor * number of idle workers)

is the number of new connections a process will still accept (not counting connections in closing state). Ignoring for a moment the "closing state" stuff, we can write:

max_connections = ThreadsPerChild + (AsyncRequestWorkerFactor * idle_workers)

Let's replace ThreadsPerChild by (idle_workers + busy_workers):

max_connections = (idle_workers + busy_workers) + (AsyncRequestWorkerFactor * idle_workers)
   = busy_workers + (AsyncRequestWorkerFactor + 1) * idle_workers

Now splitting connections in busy and idle ones and defining a busy connection as one needing a worker thread so that the number of busy connections equals the number of busy workers, we get:

max_idle_connections + busy_workers = busy_workers + (AsyncRequestWorkerFactor + 1) * idle_workers

and thus

max_idle_connections = (AsyncRequestWorkerFactor + 1) * idle_workers

So although we only have idle_workers left, we accept up to (AsyncRequestWorkerFactor + 1) * idle_workers as the number of idle connections. Since AsyncRequestWorkerFactor by default has the value 2, it means by default we accept 3 times as many connections, as we have idle workers. That's a handy formulation for our type of over commitment.

Of course the formula is not always true. If many connections turn over into busy ones, than we might suddenly have more (old) idle connections than allowed by this formula. E.g. if for a moment all workers are idle, we would allow 3*ThreadsPerChild (idle) connections. Then assume ThreadsPerChild of them start to be busy, then we would allow no idle connections because no idle workers are left, but we would still have 2*ThreadsPerChild old idle connections. The formula only tells us how many we are willing to handle unless we are already over the limit because to many turned into busy ones.

I have not done the math if we also add "connections in closing state" into the game.

There's no easy way to tune AsyncRequestWorkerFactor since it somehow depends on the activity pattern of connections, which is hard to estimate. It is best to monitor the status via mod_status to get an idea, if one can save more threads with a higher value (more over commitment), or if one needs to stay on the conservative side. Sometimes it is better to work with higher ThreadsPerChild, so that the activity patterns on more connections per process gives a better average behavior.

HTH a bit w.r.t. AsyncRequestWorkerFactor (and also hoping that I didn't make mistakes in explaining).

Regards,

Rainer

Reply via email to