Some services need to route RPCs to a specific pod that owns in-memory state (e.g., a streaming session, an audio bridge). VoIPbin handles this with per-pod queues alongside the standard shared per-service queue.
<service>.request — shared queue, any consumer wins
<service>.request.<host_id> — per-pod queue, owned by exactly one pod
Example from bin-pipecat-manager:
bin-manager.pipecat-manager.request— call creation, lookup, etc.bin-manager.pipecat-manager.request.<POD_IP>— message-send, terminate, ping
Per-pod queues MUST be declared as volatile (auto-delete when the last consumer disconnects). The volatility is what guarantees the queue disappears when its owning pod dies, so a publisher can detect death by RPC timeout.
sockHandler.QueueCreate(queue, "volatile")Shared queues use "normal" (durable) declaration.
HostID is set at pod startup from the K8s Downward API — typically POD_IP:
env:
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIPThen in cmd/<service>/main.go:
listenIP := os.Getenv("POD_IP")
if listenIP == "" {
return fmt.Errorf("could not get the listen ip address")
}
listenQueue := fmt.Sprintf("%s.%s", commonoutline.QueueName<Service>Request, listenIP)Persist this HostID on the resource (e.g., pipecatcall.HostID) so the consumer service can route follow-up RPCs to the right pod.
- Calico POD_IP recycle. Calico CNI reassigns released pod IPs within minutes. A stored
HostIDmay resolve to a different pod after a restart. Pair this pattern with per-pod-liveness-preflight.md to detect dead pods quickly, but be aware that recycle gives a matching IP and bypasses the simple echo check. v2 candidate: storePOD_UID(immutable across IP recycle) instead of or alongsidePOD_IP. - No load balancing. A per-pod queue routes to one pod by definition; if that pod is busy or backlogged, the request waits. Use the shared queue for stateless calls and reserve per-pod for genuine session affinity.
- No persistence. Volatile queues lose messages if the pod dies before consuming them. Acceptable for the use case (the session is also gone), but don't use this pattern for important non-session work.
Use per-pod queues when all of these are true:
- Some operation must reach a specific pod that holds in-memory state.
- That state cannot be reconstructed cheaply on another pod.
- You have a way to handle pod death (typically the liveness preflight pattern + caller error surfacing).
If the state is reconstructable from DB or shared cache, use the shared queue and let any pod handle the request.
bin-pipecat-manager/cmd/pipecat-manager/main.go:115-118(POD_IP read intolistenIP) and:167-168(per-pod queue construction<QueueNamePipecatRequest>.<host_id>)bin-pipecat-manager/pkg/listenhandler/main.go(volatile per-pod queue declaration)bin-common-handler/pkg/requesthandler/pipecat_message.go(per-pod RPC routing)