Time-to-start per queue, as a metric

Queue depth without throughput says nothing: a hundred jobs is fine at fifty a second and an incident at one a minute.

// the age of the oldest waiting job, per queue
foreach (['interactive', 'bulk'] as $queue) {
    $gauge->set(
        $this->redis->oldestPendingAge($queue),
        ['queue' => $queue]
    );
}

// alert on THIS, not on depth:
//   interactive > 30s  → page
//   bulk        > 30m  → ticket

Oldest-waiting-age is a direct measure of the promise being broken and needs no interpretation, where depth needs dividing by a throughput nobody has to hand. Different thresholds per queue is the point of having split them. A heartbeat job enqueued on a timer gives the same signal without instrumenting the queue internals, and works with any broker.