C4 C 4 Punk Developers

CWIST guide · Intermediate · 8 min read

Choosing between the reactor and the classic pool

Two request paths, two failure shapes. How to tell which one your workload wants, and how to read the latency distribution honestly.

The decision in one paragraph

If the number of open connections is what grows, use the reactor. If the number of concurrent in-flight requests is bounded and you care about the median and p99, try the classic pool and measure. That is the whole heuristic; the rest of this guide is why it holds.

What the reactor does

The CWIST reactor is the default mode. It multiplexes many connections per event loop, so connection count is decoupled from thread count. A connection costs a reactor slot rather than a parked thread, which is what makes very high connection counts affordable in memory.

The cost is that requests share a loop. Work queued behind other work on the same loop waits, and that shows up in the middle of the distribution rather than at the edges.

What the classic pool does

The classic pool is thread-per-connection, enabled with CWIST_C1M_MODE=0. Every connection gets its own thread, so no request waits behind another in a batch. In our published runs it answers the median request in less than half the time of the async comparison row and stays ahead through p99.

The cost is per-connection memory and scheduler pressure. Thread count tracks connection count, so this mode stops being the right answer at the point where connections are cheap and numerous.

Reading the numbers we publish

Our benchmark tables report the corrected latency distribution, not only a mean, because an average hides which requests were slow. Read them like this:

  • Mean tells you almost nothing on its own.
  • P50 through P99 is where the classic pool leads in our runs.
  • The extreme tail is where the crossover happens and the async comparison is tighter. We say so in the same table rather than cropping the column.
  • Memory columns are end samples, not peaks. Compare a single-process server against PSS, not RSS.

Why you should not take our numbers

Published figures come from one commit, one load profile and one CI environment. The runner CPU model changes between runs and moves the numbers more than most code changes do. They establish a shape, not a guarantee, and they are not a causal explanation of scheduling behaviour.

Measuring your own

# warm up first, and discard the warmup
wrk -t4 -c100 -d10s http://localhost:8080/ > /dev/null
wrk -t4 -c100 -d10s --latency http://localhost:8080/

# then the other mode, same flags, same machine, same commit
CWIST_C1M_MODE=0 ./server

Keep the load generator off the machine under test if you can. Run each mode more than once. If the two runs of the same mode differ by more than the gap between modes, you have measured your environment rather than the server.

Summary

Neither mode is the better one in general. The reactor decouples connections from threads; the classic pool refuses to queue requests behind each other. Pick per workload, switch with one environment variable, and trust your own distribution over anyone's headline number.