A compiled sidecar for the work PHP holds a worker to do

A process holding thousands of idle connections is not what a request-per-process model is for, and a small binary beside it is a smaller change than a rewrite.

what moved:   the websocket fan-out. 4,000 idle
              connections, almost no CPU.
what stayed:  everything else.

the boundary: Redis pub/sub. PHP publishes, the sidecar
fans out. no shared code, no shared database, no build
coupling. one documented message format.

the cost:     a second language in the deployment, and a
              second thing to know at three in the morning.

Keeping the boundary at a message queue rather than a shared database is what makes the second language affordable — either side can be understood, deployed and replaced independently. The real cost is operational rather than technical, which is an argument for keeping such components small and boring enough that anybody can read them.