Queue Topology
Queue Topology
Section titled “Queue Topology”Mantis uses a structured RabbitMQ topology for command dispatch and AI log analysis. Mandible declares the entire topology on startup; Thorax and the analysis workers are pure consumers.
Overview
Section titled “Overview”Exchanges
Section titled “Exchanges”| Exchange | Type | Purpose |
|---|---|---|
mantis.commands | Direct | Route commands to Thorax by mode and priority |
mantis.analysis | Direct | Route AI log-analysis jobs to analysis workers |
mantis.dlx | Fanout | Dead-letter exchange for failed messages |
Command Exchange
Section titled “Command Exchange”The mantis.commands exchange routes commands based on target mode and priority:
mantis.commands (direct) — EVERY routing key binds to mantis.commands.thorax (theprimary consumer queue); priority keys ALSO bind to their priority queue:├── command.thorax -> mantis.commands.thorax├── command.poll.high -> mantis.commands.thorax + mantis.commands.priority.high├── command.poll.normal -> mantis.commands.thorax + mantis.commands.priority.normal├── command.poll.low -> mantis.commands.thorax + mantis.commands.priority.low├── command.listen.high -> mantis.commands.thorax + mantis.commands.priority.high├── command.listen.normal -> mantis.commands.thorax + mantis.commands.priority.normal└── command.listen.low -> mantis.commands.thorax + mantis.commands.priority.lowAnalysis Exchange
Section titled “Analysis Exchange”The mantis.analysis exchange routes AI log-analysis jobs to the analysis
worker queue (mantis.analysis).
Results (gRPC, not a queue)
Section titled “Results (gRPC, not a queue)”Execution results are not published to RabbitMQ. Thorax reports each
command’s outcome to Mandible directly over gRPC (store_command_result), so
there is no mantis.results exchange or result queue to monitor.
Events (SSE, not a queue)
Section titled “Events (SSE, not a queue)”Deployment lifecycle events (deployment.*, step.*, target.*) are streamed
to clients over Server-Sent Events via Mandible’s /sse endpoints, not a
mantis.events exchange.
Queues
Section titled “Queues”Command Queues
Section titled “Command Queues”| Queue | Purpose | Consumers |
|---|---|---|
mantis.commands.thorax | All commands for processing | Thorax instances |
mantis.commands.priority.high | High-priority commands | Unused (reserved for future priority-based routing) |
mantis.commands.priority.normal | Normal-priority commands | Unused (reserved for future priority-based routing) |
mantis.commands.priority.low | Low-priority commands | Unused (reserved for future priority-based routing) |
All current Thorax consumption happens via mantis.commands.thorax. The three
priority queues receive bindings from the same routing keys but have no active
consumers — they are reserved for future priority-based consumption.
Analysis Queue
Section titled “Analysis Queue”| Queue | Purpose | Consumers |
|---|---|---|
mantis.analysis | AI log-analysis jobs | Analysis workers |
Dead-Letter Queue
Section titled “Dead-Letter Queue”| Queue | Purpose | Consumers |
|---|---|---|
mantis.dlq | Failed/expired messages | Manual review |
Routing Keys
Section titled “Routing Keys”Command Routing
Section titled “Command Routing”| Routing Key | Target Queue | Description |
|---|---|---|
command.thorax | mantis.commands.thorax | All commands |
command.poll.high | thorax + priority.high | High-priority poll commands |
command.poll.normal | thorax + priority.normal | Normal poll commands |
command.poll.low | thorax + priority.low | Low-priority poll commands |
command.listen.high | thorax + priority.high | High-priority listen commands |
command.listen.normal | thorax + priority.normal | Normal listen commands |
command.listen.low | thorax + priority.low | Low-priority listen commands |
Queue Configuration
Section titled “Queue Configuration”Queue Arguments
Section titled “Queue Arguments”Default queue configuration:
# Queue durabilitydurable = trueauto_delete = falseexclusive = false
# Dead-letteringx-dead-letter-exchange = mantis.dlx
# Message TTL — set unconditionally by Mandible, and it varies per queue:# priority.high: 1,800,000 (30 min) commands.thorax / priority.normal: 3,600,000 (1 hr)# priority.low: 7,200,000 (2 hr) analysis: 14,400,000 (4 hr) mantis.dlq: 604,800,000 (7 days)x-message-ttl = 3600000 # 1 hour (commands.thorax / priority.normal)Priority Queues
Section titled “Priority Queues”Priority queues use max-priority argument:
# Priority queue argumentsx-max-priority = 10Quorum Queues
Section titled “Quorum Queues”For high availability, use quorum queues:
# Quorum queue argumentsx-queue-type = quorumx-quorum-initial-group-size = 3Message Flow
Section titled “Message Flow”Command Flow
Section titled “Command Flow”Event Flow
Section titled “Event Flow”Deployment lifecycle events are delivered to clients over Server-Sent Events, not RabbitMQ:
Dead-Letter Handling
Section titled “Dead-Letter Handling”Dead-Letter Reasons
Section titled “Dead-Letter Reasons”Messages are dead-lettered when:
| Reason | Description |
|---|---|
rejected | Message explicitly rejected |
expired | Message TTL exceeded |
maxlen | Queue length limit reached |
Retry Configuration
Section titled “Retry Configuration”Mantis implements retry with exponential backoff:
1. On arrival the x-death count is checked first; if it already meets max_retries the message is NACKed straight to the DLQ (no processing attempt).2. Otherwise the message is processed.3. First processing failure (not redelivered): NACK requeue=true — it re-enters the same queue immediately.4. Second failure (redelivered): NACK requeue=false — RabbitMQ routes it through the dead-letter exchange back to the main queue, incrementing x-death.5. The cycle repeats until x-death reaches max_retries, then the message goes to the DLQ.DLQ Management
Section titled “DLQ Management”Monitor dead-letter queue:
# List messages in DLQsudo rabbitmqctl list_queues name messages \ | grep mantis.dlq
# Inspect DLQ messagessudo rabbitmqadmin get queue=mantis.dlq count=10
# Purge DLQ (use with caution)sudo rabbitmqctl purge_queue mantis.dlqReprocessing Failed Messages
Section titled “Reprocessing Failed Messages”Requeue messages from DLQ:
# Move messages back to original queue# Using shovel pluginsudo rabbitmqctl set_parameter shovel reprocess \ '{"src-uri": "amqp://", "src-queue": "mantis.dlq", \ "dest-uri": "amqp://", "dest-queue": "mantis.commands.thorax", \ "delete-after": "queue-length"}'Topology Declaration
Section titled “Topology Declaration”Automatic Setup
Section titled “Automatic Setup”Mandible declares the full topology on startup. Configure the RabbitMQ
connection in mandible.toml:
[queue]enabled = truehost = "localhost"port = 5671username = "mantis"password = "password"Topology declaration is idempotent — Mandible re-declares exchanges, queues, and bindings on every startup, so no manual setup is needed. Thorax is a pure consumer and assumes the topology already exists (it retries its consumer connection until the queues appear).
Manual Setup
Section titled “Manual Setup”Declare topology manually:
# Create exchangesrabbitmqadmin declare exchange name=mantis.commands type=direct durable=truerabbitmqadmin declare exchange name=mantis.analysis type=direct durable=truerabbitmqadmin declare exchange name=mantis.dlx type=fanout durable=true
# Create queuesrabbitmqadmin declare queue name=mantis.commands.thorax durable=true \ arguments='{"x-dead-letter-exchange": "mantis.dlx"}'
rabbitmqadmin declare queue name=mantis.commands.priority.high durable=true \ arguments='{"x-dead-letter-exchange": "mantis.dlx", "x-max-priority": 10}'
rabbitmqadmin declare queue name=mantis.commands.priority.normal durable=true \ arguments='{"x-dead-letter-exchange": "mantis.dlx", "x-max-priority": 10}'
rabbitmqadmin declare queue name=mantis.commands.priority.low durable=true \ arguments='{"x-dead-letter-exchange": "mantis.dlx", "x-max-priority": 10}'
rabbitmqadmin declare queue name=mantis.analysis durable=true \ arguments='{"x-dead-letter-exchange": "mantis.dlx"}'
rabbitmqadmin declare queue name=mantis.dlq durable=true
# Create bindingsrabbitmqadmin declare binding source=mantis.commands destination=mantis.commands.thorax \ routing_key=command.thorax
rabbitmqadmin declare binding source=mantis.commands destination=mantis.commands.priority.high \ routing_key=command.poll.high
rabbitmqadmin declare binding source=mantis.commands destination=mantis.commands.priority.high \ routing_key=command.listen.high
# ... additional bindingsConsumer Configuration
Section titled “Consumer Configuration”Prefetch Count
Section titled “Prefetch Count”Configure consumer prefetch:
[queue]prefetch_count = 10 # Messages to prefetch per consumerConsumer Tags
Section titled “Consumer Tags”Thorax generates unique consumer tags:
thorax-{instance_id}-{uuid}Acknowledgment
Section titled “Acknowledgment”Mantis uses manual acknowledgment:
| Action | When |
|---|---|
ack | Message processed successfully |
nack requeue=true | Temporary failure, retry immediately |
nack requeue=false | Permanent failure, route to DLX |
High Availability
Section titled “High Availability”Mirrored Queues
Section titled “Mirrored Queues”For classic mirrored queues:
# Set HA policy for all mantis queuessudo rabbitmqctl set_policy ha-mantis "^mantis\." \ '{"ha-mode":"all","ha-sync-mode":"automatic"}'Quorum Queues
Section titled “Quorum Queues”For quorum queues (recommended):
# Set policy for quorum queuessudo rabbitmqctl set_policy quorum-mantis "^mantis\." \ '{"queue-type":"quorum"}' --apply-to queuesConsumer Failover
Section titled “Consumer Failover”When a Thorax instance fails:
- Messages are redelivered to other consumers
- In-flight messages timeout and retry
- Health monitor switches to polling if needed
Scaling Considerations
Section titled “Scaling Considerations”Multiple Consumers
Section titled “Multiple Consumers”Scale horizontally with competing consumers:
Queue Sharding
Section titled “Queue Sharding”For very high throughput:
mantis.commands.thorax.{shard_id}Instance Affinity
Section titled “Instance Affinity”Listen-mode targets use instance affinity:
# Command message includes assigned instanceassigned_instance_id = "thorax-node-1"Troubleshooting
Section titled “Troubleshooting”Queue Not Receiving Messages
Section titled “Queue Not Receiving Messages”-
Check binding exists:
Terminal window rabbitmqadmin list bindings source=mantis.commands -
Verify routing key matches:
Terminal window # Publish test messagerabbitmqadmin publish exchange=mantis.commands \routing_key=command.thorax payload='test' -
Check queue is not paused:
Terminal window rabbitmqctl list_queues name state
Messages Stuck in DLQ
Section titled “Messages Stuck in DLQ”-
Inspect message headers:
Terminal window rabbitmqadmin get queue=mantis.dlq count=1 -
Check x-death header for failure reason
-
Review application logs for processing errors
Consumer Not Receiving
Section titled “Consumer Not Receiving”-
Check consumer is registered:
Terminal window rabbitmqctl list_consumers queue=mantis.commands.thorax -
Verify prefetch is not exhausted:
Terminal window rabbitmqctl list_channels name prefetch_count messages_unacknowledged
Next Steps
Section titled “Next Steps”- Monitoring - RabbitMQ monitoring
- TLS Configuration - Secure connections
- RabbitMQ Setup - Initial setup
