Solid walk through of upstream pooling. The ops failure mode we still hit is treating the pool as forever-healthy once it accepts TCP: without max_fails and fail_timeout matched to real probe latency, a peer that hangs past proxy_read_timeout keeps collecting traffic while others sit idle. Prefer least_conn once replica sizes are equal, keep proxy_next_upstream limited to error/timeout on safe methods only, and pin proxy_http_version 1.1 with Connection cleared so keepalive actually reuses sockets into the pool.
After a rolling restart, log $upstream_addr and $upstream_status together and confirm a draining peer leaves within one fail_timeout window instead of sprinkling intermittent 502s. If you add weight, re-check under equal load — unequal weights plus keepalive can pin sticky-looking latency that looks like app slowness. Hit each member directly on the private network, then through the upstream name, before trusting the pool in production.