Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

queue_relay

A relay service between incoming HTTP requests and a backend.

Accepts a request → enqueues it in RabbitMQ → forwards it to the backend under a rate limit → on failure, retries with increasing backoff (retry ladder) → once attempts are exhausted, moves the task to a dead queue.

HTTP client → POST /queue/tasks → tasks.main (RabbitMQ)
                                        │
                                        ▼
                              rate limit (10 / 3 sec)
                                        │
                                        ▼
                                    backend
                            success │        │ failure
                                    │        ▼
                                    │   retry ladder
                                    │   (instant → 3s → 15s → 60s → 120s → 240s)
                                    │        │
                                    ▼        ▼ (attempts exhausted)
                                  done   tasks.failed

How it works

  1. Ingest. POST /queue/tasks accepts JSON, publishes it to the tasks.main queue, and immediately responds 202 Accepted — without waiting for the task to actually reach the backend.
  2. Processing. The consumer reads from tasks.main, waits for a free slot in the rate limiter, and sends the request to the backend (backendBaseUrl + backendPath).
  3. Failure. Any error (network issue, timeout, non-2xx response) triggers a retry. The task is republished with an incremented attempt counter, either straight back to tasks.main or into one of the delay queues.
  4. Delay without a scheduler. Delay queues have no consumers — a message just sits there for x-message-ttl milliseconds, after which RabbitMQ itself ("dead letters") moves it back to tasks.main. No cron job or external scheduler is needed — it's a built-in broker mechanism.
  5. Final failure. Once all attempts are exhausted, the task moves to tasks.failed and is no longer touched automatically.

Retry ladder

Level Attempts Delay before attempt Queue
1 2 instant tasks.main
2 5 3 sec tasks.retry.3s
3 5 15 sec tasks.retry.15s
4 5 60 sec tasks.retry.60s
5 5 120 sec tasks.retry.120s
6 5 240 sec tasks.retry.240s

27 attempts total per task, after which it moves to tasks.failed.

The ladder is declared as data in one place, so levels are easy to add or adjust:

final RetryStage[] retryStages = [
    {attempts: 2, delaySec: 0,   queueName: ()},
    {attempts: 5, delaySec: 3,   queueName: "tasks.retry.3s"},
    {attempts: 5, delaySec: 15,  queueName: "tasks.retry.15s"},
    {attempts: 5, delaySec: 60,  queueName: "tasks.retry.60s"},
    {attempts: 5, delaySec: 120, queueName: "tasks.retry.120s"},
    {attempts: 5, delaySec: 240, queueName: "tasks.retry.240s"}
];

Rate limit

No more than 10 requests to the backend per 3 seconds (fixed window) — protects the backend from being overwhelmed, especially right after it recovers from downtime, when a backlog of delayed retries can hit it at once.

  • Implemented in the consumer's code (RateLimiter), not on the broker side — RabbitMQ has no built-in time-based rate limiting, only prefetch (a cap on how many unacknowledged messages a consumer can hold).
  • prefetchCount (default 20) — how many messages the consumer holds at once; acts as a buffer above the rate limit so it doesn't sit idle waiting for new messages from the broker.
  • While no slot is free, the consumer doesn't acknowledge (ack) the message — RabbitMQ won't hand it more messages beyond prefetchCount, so the backlog piles up in the broker instead of hammering the backend.

⚠️ The limiter lives in process memory and is only accurate with a single replica of the service. When scaling to multiple replicas, either enable x-single-active-consumer (already set on tasks.main) or move the rate limiter to shared external storage (e.g. Redis) to keep instances in sync.

Queues

Queue Consumed by Purpose
tasks.main this service working queue, actual processing happens here
tasks.retry.3s nobody (TTL only) delays a task by 3 sec
tasks.retry.15s nobody (TTL only) delays a task by 15 sec
tasks.retry.60s nobody (TTL only) delays a task by 60 sec
tasks.retry.120s nobody (TTL only) delays a task by 120 sec
tasks.retry.240s nobody (TTL only) delays a task by 240 sec
tasks.failed nobody (yet) tasks that exhausted all attempts

The entire topology is created automatically on startup (setupTopology()) — nothing needs to be set up manually in RabbitMQ.

Configuration

All parameters are configurable and can be overridden at runtime:

Parameter Default Description
inboundPort 8080 inbound HTTP port
rabbitHost localhost RabbitMQ host
rabbitPort 5672 RabbitMQ port
rabbitUser guest RabbitMQ username
rabbitPassword guest RabbitMQ password
backendBaseUrl http://localhost:8090 backend base URL
backendPath /process backend endpoint path
backendTimeout 5 backend request timeout, sec
rateLimitMaxRequests 10 requests allowed per window
rateLimitWindowSec 3 rate limit window size, sec
prefetchCount 20 messages buffered per consumer

Run

bal run

Endpoint

POST /queue/tasks
Content-Type: application/json

{ ...arbitrary JSON... }

Responds with 202 Accepted right after the task is queued — not after it's actually processed by the backend.

Notes for later

  • Scaling: when adding a second replica, keep in mind x-single-active-consumer (already enabled on tasks.main) and that the rate limiter isn't distributed yet — see the warning above.
  • RabbitMQ cluster: queue arguments (TTL, DLX) are immutable after creation — changing them requires deleting the old queue in the broker before redeploying, otherwise queueDeclare fails with PRECONDITION_FAILED.

About

RabbitGate is a resilient proxy service built with Ballerina and RabbitMQ that instantly acknowledges HTTP requests, protects backends with rate limiting, and handles failures via a built-in TTL retry ladder.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages