Driver Dispatch Under Timeouts
A search problem with a clock on it — expanding rings, batched offers, and a retry loop for when the answer is 'not yet' rather than 'no'.
Rider App → Dispatch API → MySQL
The request opens a stream
A ride request is created and acknowledged immediately, but the response isn't closed. Dispatch can legitimately take minutes, so the connection stays open and the app is told what actually happens — accepted, queued, or exhausted. The alternative is a single reply and a client left polling an operation whose duration nobody can predict in advance.
Why not the alternatives?
- Broadcast to every driver in the city
- Fastest possible assignment and a notification storm for a ride one person can take. It also destroys proximity ordering — a driver twenty minutes away competes with one around the corner, and riders wait longer for worse pickups.
- Offer to one driver at a time
- No wasted notifications, and an unassigned rider watching a spinner while the system works through unresponsive drivers one acceptance window at a time. The batch is the compromise: bounded waste, bounded wait.
- Query once, then work through the list
- Cheaper in database terms and increasingly wrong. A sweep lasts long enough for drivers to move, go offline, or take other rides, so dispatching from a snapshot means dispatching to where drivers used to be.
- Treat an exhausted sweep as failure
- Simple, and it discards the distinction the system exists to preserve. Nothing about a failed sweep says the next one fails too, and availability turns over on a timescale shorter than a rider's patience.
- Let the queue's automatic retry handle re-attempts
- Automatic retry replays a job that errored. A dispatch re-attempt isn't an error — the previous sweep completed correctly and found nobody — and it must re-validate that the request still exists before acting. Conflating the two means retrying rides that were already cancelled.
Lessons
- 'Not found' and 'not yet' are different answers. Most of the value in this pipeline came from refusing to collapse them into one.
- Freshness beats efficiency when the underlying data is in motion — re-querying every ring looked wasteful and was the difference between dispatching to drivers and dispatching to their last known positions.
- An operation that legitimately takes minutes isn't a slow request; it's a process with progress. Give it a channel to say so, or the client invents polling and guesswork.
- The failure path holds the money. Releasing funds correctly across every exit is harder, and matters more, than the assignment logic everyone focuses on.
- Logic that runs in two execution contexts will drift apart. Shared behaviour has to be shared code — a copy with a comment explaining that it's a copy is a bug with a delay on it.