High-Level Architecture
Carrier is built on a modular architecture that cleanly separates message receiving from message transmission. This separation of concerns enables:- Independent scaling of receivers and transmitters
- Easy addition of new message queue types
- Flexible output destinations
- Concurrent processing for high throughput
Core Components
Receivers
Read messages from message queues (currently SQS)
Transmitters
Send messages to destinations (currently HTTP webhooks)
Message Flow
The following diagram illustrates how messages flow through Carrier:Processing Steps
- Receive: Receivers poll message queues using long polling (20s wait time)
- Distribute: Messages are sent to a pool of concurrent workers
- Transmit: Each worker transmits the message via the configured transmitter
- Acknowledge: Successfully transmitted messages are deleted from the queue
- Retry: Failed messages with retryable errors have their visibility timeout updated
Concurrency Model
Carrier uses a multi-level concurrency architecture for high throughput:Receiver Level
SQS_RECEIVERS.
Worker Level
Each receiver maintains its own pool of workers:SQS_RECEIVER_WORKERS.
Batch Processing
Receivers fetch messages in batches from SQS:SQS_BATCH_SIZE.
Performance Tuning: Total concurrent message processing =
SQS_RECEIVERS × SQS_RECEIVER_WORKERS × SQS_BATCH_SIZESeparation of Concerns
Receivers
Receivers are responsible for:- Polling message queues
- Managing message visibility
- Handling acknowledgments and deletions
- Retry logic for failed transmissions
Transmitter interface - they don’t care about the destination.
Transmitters
Transmitters are responsible for:- Delivering messages to destinations
- Setting appropriate headers/metadata
- TLS/security configuration
- Handling destination-specific errors
receiver/sqs/sqs.go:60-62
Extensibility
Adding New Receivers
To add support for a new message queue (e.g., RabbitMQ, Kafka):- Implement the receiver logic in a new package (e.g.,
receiver/rabbitmq) - Use the same
Transmitterinterface - Follow the same pattern: poll → distribute → transmit → acknowledge
Adding New Transmitters
To add support for new destinations (e.g., gRPC, SNS):- Create a new transmitter package (e.g.,
transmitter/grpc) - Implement the
Tx(io.Reader, transmitter.TransmitAttributes) errormethod - Configure it in
main.goinstead of the webhook transmitter
Error Handling
Carrier distinguishes between retryable and non-retryable errors:Retryable Errors
Non-Retryable Errors
Non-retryable errors are logged but the message remains in the queue until it expires or reaches the dead-letter queue.Health Monitoring
Carrier includes optional webhook health checking:Resource Management
Carrier uses theprobe/pool library for goroutine management:
