Writing the Worker Loop: Mastering the Goridge Protocol for High-Scale PHP
Welcome back, engineers! In our journey to architect a High-Scale PHP CMS, we've laid the groundwork with RoadRunner's supervisor configuration. Today, we dive into the very heart of our application: the PHP worker loop. This isn't just about writing code; it's about understanding the fundamental shift in PHP execution that unlocks true high-performance and enables us to handle those ambitious hundred-million requests per second.
Forget the old "bootload tax" of traditional PHP-FPM where every request meant re-initializing your entire framework. With RoadRunner, our PHP script becomes a long-running process, continuously serving requests. The magic that makes this persistent runtime possible, and incredibly efficient, is the Goridge protocol.
The Heartbeat: Why a Worker Loop?
In the traditional shared-nothing PHP model, each request spins up a fresh PHP process, loads all your framework's dependencies, connects to the database, processes the request, and then tears everything down. This "bootload tax" is acceptable for low-traffic sites but becomes an insurmountable bottleneck at scale.
Our RoadRunner setup flips this on its head. RoadRunner spawns a pool of PHP worker processes, but these processes don't die after one request. Instead, they enter a perpetual loop, waiting for RoadRunner to hand them a new request. This loop is the "worker loop," and it's where your application logic lives.
Core Concepts: Beyond the Request-Response
Persistent State and Reduced Bootload: This is the game-changer. Imagine your framework takes 50ms to bootstrap. With FPM, that's 50ms per request. With RoadRunner, it's 50ms once when the worker starts. Subsequent requests immediately hit your application logic, drastically reducing latency and increasing throughput. Database connections, caches, DI containers – all can be initialized once and reused. This isn't just an optimization; it's a paradigm shift for PHP.
Inter-Process Communication (IPC) via Goridge: RoadRunner and your PHP worker are separate processes. They need a fast, efficient way to talk. Goridge is that language. It's a binary RPC (Remote Procedure Call) protocol designed specifically for high-performance communication between Go (RoadRunner) and other languages (like PHP). It's much faster and has less overhead than traditional HTTP or even plain TCP for internal process communication because it's optimized for structured data exchange, minimizing serialization/deserialization costs.
The Supervisor Model (RoadRunner's Role): RoadRunner isn't just a proxy; it's a smart supervisor. It manages the lifecycle of your PHP workers: starting them, monitoring them, restarting them if they crash, and load-balancing requests across the worker pool. This provides resilience and ensures your application remains available even if individual workers encounter issues.
Component Architecture & Control Flow
Let's visualize how this fits together.
Client Request: A user's browser sends a request.
Load Balancer/Reverse Proxy: Distributes the request to one of our RoadRunner instances.
RoadRunner:
Receives the HTTP request.
Picks an available PHP worker from its pool.
Serializes the request data (headers, body, method, path) into a Goridge payload.
Sends this Goridge payload to the chosen PHP worker over a Unix socket or TCP connection.
PHP Worker (Our Focus Today):
Enters its infinite loop.
Waits for a Goridge message from RoadRunner.
Deserializes the Goridge payload back into a standard PHP request object.
Executes your application logic (e.g., routing, controller, database interaction).
Serializes the response data (status code, headers, body) into a Goridge payload.
Sends this Goridge payload back to RoadRunner.
RoadRunner:
Receives the Goridge response.
Deserializes it.
Constructs an HTTP response.
Sends the HTTP response back to the client.
This continuous dance, orchestrated by Goridge, is what allows a single PHP worker to serve thousands, even millions, of requests efficiently.
Hands-on: Building Our First Worker Loop
Our goal is to create a simple PHP script that understands the Goridge protocol, receives a request, processes it, and sends a response. This script will be the blueprint for all future complex CMS logic.
This simple script is powerful. It demonstrates:
The one-time setup: Anything before the
while(true)loop runs only once.The persistent loop: The
while(true)ensures the worker keeps serving requests.Goridge interaction:
Worker::fromGlobals()(part ofpsr7-server) transparently uses Goridge to read the request, andWorker::respond()sends the response.Error handling: Crucial for stability. A worker must always respond, even if with an error, or RoadRunner will think it's stalled.
Assignment: Build Your Goridge Worker
Your task is to implement the worker loop using the provided code skeleton.
Project Setup: Ensure you have the
cms-workerdirectory from Day 2.Dependencies: Install the necessary PHP packages:
spiral/goridge,nyholm/psr7,nyholm/psr7-server.Worker Script: Create
src/worker.phpwith the core worker loop logic provided above.RoadRunner Configuration: Update your
rr.yamlto point to this new worker script.Test and Verify: Start RoadRunner and send several HTTP requests to observe the worker's persistent behavior. Pay attention to the
X-Worker-IDheader and theerror_logoutput.
Success Criteria:
You can send multiple
curlrequests to your RoadRunner server.Each request is correctly processed by your
worker.phpscript.The
X-Worker-IDheader in the response remains consistent across multiple requests (indicating the same PHP process handled them).The
error_logmessages from the worker appear in your RoadRunner console.
This hands-on exercise will solidify your understanding of how Goridge powers a persistent PHP application, a cornerstone for our high-scale CMS.
Solution Hints:
composer.json:
Run composer install in your cms-worker directory.
rr.yaml(simplified for this lesson, assumingcms-workeris your project root):
Make sure this rr.yaml is in the root of your cms-worker project.
To run: Navigate to your
cms-workerdirectory and execute./rr serve -c .rr.yaml.To test: Open another terminal and run
curl http://localhost:8080/hello?name=worldmultiple times. Observe the output, especially theX-Worker-IDheader.
By completing this, you've taken a massive leap from traditional PHP to a truly high-performance, persistent application architecture. This Goridge-powered worker loop is the foundation upon which we'll build our entire high-scale CMS.