How it works
One queue engine, five protocols, the official client libraries unmodified, and a tenant per random identifier.
What PowerQueue is
PowerQueue is a message broker written in Rust. It has one queue engine, with its own storage, and five protocol front ends on top of it:
- AMQP 0-9-1, the protocol of RabbitMQ and its client libraries;
- Amazon SQS, over the AWS JSON and Query protocols that the AWS SDKs and CLI use;
- Google Cloud Pub/Sub, over the gRPC API the Google Cloud client libraries use;
- Azure Queue Storage, over its REST API;
- Azure Service Bus, over AMQP 1.0 with its management operations, plus its administration REST API.
Each front end answers the way the real service answers, including its error codes, so the official SDKs work unmodified: you change only the endpoint and the credentials. Under every protocol the messages go into the same engine's queues; a Pub/Sub subscription, an SQS queue and a Service Bus queue are all queues of that engine.
Within a tenant the protocols share one set of queue names: a queue called orders is the same queue whether you reach it over AMQP 0-9-1, SQS, Azure Queue Storage or Service Bus, and a Pub/Sub subscription called orders is that queue too. Give each protocol its own names: in particular, a publish to a Pub/Sub topic X that does not exist yet is refused with FAILED_PRECONDITION when another protocol already has a queue called X, because the topic's subscription X could not be created; create the topic and a subscription explicitly, or use another name.
This site is a free public deployment of PowerQueue, run in its public mode: many users, none with an account, share one broker, each in a tenant of their own, within caps that keep any one client from using more than a fair share.
Tenants and the identifier
A tenant is a private space of queues, topics and subscriptions. It is named by a tenant identifier: exactly 24 characters, a lowercase letter followed by 23 lowercase letters or digits. That format is valid in the strictest field of every protocol at once (an Azure Storage account name allows only 3 to 24 lowercase letters and digits; a Google Cloud project id must start with a letter). A random identifier has about 2123 possible values, so nobody can guess yours.
You do not send the identifier in anything new. Each protocol already has a credential field its SDK requires, and the identifier goes there:
| Protocol | The identifier goes in | The secret part |
|---|---|---|
| AMQP 0-9-1 | the virtual host (the URL path) | any user name and password |
| Amazon SQS | the access key id | any secret access key, any region |
| Google Cloud Pub/Sub | the project id | no credentials |
| Azure Queue Storage | the storage account name (the first segment of the URL path) | any base64 account key |
| Azure Service Bus | the shared access key name in the connection string | any shared access key |
Signatures and passwords are not checked: there are no keys to check them against. The identifier itself is the only secret, so treat it like a password. The same identifier names the same tenant in every protocol.
Tenants are created on first use. There is no signup: the first request that names a well-formed identifier creates its tenant. A request whose field holds anything else, including placeholders from SDK samples such as guest, test, test-project or devstoreaccount1, is refused with the protocol's own authentication or validation error, whose message says what a valid identifier looks like. It is never sent to a shared default tenant. Each client address may create up to 20 new tenants an hour, and the service holds at most 10,000 tenants.
Isolation between tenants
Tenants never see each other. The same queue name in two tenants is two queues, listings show only your own entities, and every reference that could point into another tenant is refused rather than resolved:
- SQS: another tenant's queue URL answers
QueueDoesNotExist; a redrive policy or message move task naming another tenant's ARN isInvalidParameterValue. - Pub/Sub: a subscription, dead-letter topic, snapshot or schema in another project is
INVALID_ARGUMENT. - Azure Queue Storage: a Shared Key header naming an account other than the one in the URL is
403 AuthenticationFailed. - Service Bus:
ForwardTonaming another namespace is400, and a connection keeps the tenant of its first token.
Isolation is by name, not by encryption: everyone's messages are on the same machine, in the same broker. Do not send anything confidential.
Queues and topics need not be created
In the public service, using a name creates the entity, so the shortest program that sends and receives works without any setup:
- AMQP 0-9-1: publishing to a queue name through the default exchange, consuming,
basic.getand a passive declare create the queue. - SQS:
GetQueueUrl,SendMessage,SendMessageBatchandReceiveMessageon an unknown name create a standard queue; a name ending in.fifocreates a FIFO queue with SQS's defaults.CreateQueueworks as usual. - Azure Queue Storage: Put Message, Get Messages, Peek Messages and Clear Messages create the queue. A later Create Queue answers
201 Createdand keeps the metadata you give. - Azure Service Bus: sending to or receiving from an unknown entity creates a queue.
- Google Pub/Sub: publishing to an unknown topic
Xcreates topicXtogether with a subscription also calledX, so publish-then-pull on one name loses nothing. Pulling a subscription namedX~namecreates that subscription on topicX(and topicXwith its subscriptionXif needed); it receives what is published from then on. Pulling any other unknown subscription isNOT_FOUND, because a pull names only the subscription and the topic cannot be known.CreateSubscriptionworks as usual for any name.
Code written for the real services still works: if it creates its entities first, it finds them or is told it created them. The difference to know: a mistyped name silently creates a new entity, and code that relies on a not-found answer for a missing queue or topic does not get one.
Using one queue from several interfaces
Within a tenant every interface shares one set of queue names, so a message sent through one interface can be read through any other: send with SQS, read with AMQP 0-9-1; publish to a Pub/Sub topic, read its subscription with Service Bus. Each interface can express things the others cannot, so a message is delivered in the reading interface's natural form, marked where that interface can carry a mark, and never silently damaged. The interface that sent a message always reads it back exactly as sent. Nothing is refused for being hard to represent elsewhere.
Message bodies
| Read through | A text body | A binary body (not valid text) | A typed Service Bus body (an AMQP value or sequence) |
|---|---|---|---|
| AMQP 0-9-1, Pub/Sub, Service Bus | the bytes as sent | the exact bytes | Service Bus: the value as sent. AMQP 0-9-1 and Pub/Sub: JSON text, with x-pq-body-encoding = json (a header, an attribute) |
| SQS | the text | base64, with the message attribute x-pq-body-encoding = base64 | JSON text, with x-pq-body-encoding = json |
| Azure Queue Storage | the text | base64, unmarked: Azure messages have no attributes to carry a mark | JSON text, unmarked |
To send binary through SQS, send the base64 of the bytes with the attribute x-pq-body-encoding = base64 (type String): the bytes are stored, and AMQP 0-9-1, Pub/Sub and Service Bus readers receive them. Text sent through Azure Queue Storage is stored as that text, so an Azure SDK that base64-encodes its messages (a message encoding policy) delivers base64 text to the other interfaces. If a queue may carry binary and you read it through Azure Queue Storage, decode base64 yourself.
A Service Bus body that is an AMQP string or binary value crosses as its plain bytes. Any other AMQP value (a number, a map, a list, a timestamp) or an AMQP sequence is rendered as JSON for the other interfaces: null, booleans and numbers as JSON values; strings and symbols as strings; binary as a base64 string; timestamps as RFC 3339 strings; uuids as their text; decimals as strings such as 125E-2; lists and sequences as arrays; maps as objects. For example a Service Bus message whose body is the map {"a": 1} reads as the text {"a":1} through SQS. Its stored size is the JSON and the AMQP encoding together.
Attributes, headers and properties
| Read through | What it receives |
|---|---|
| AMQP 0-9-1 headers, SQS message attributes, Service Bus application properties | Typed values: text as String, numbers as Number (keeping an SQS type label such as Number.float), byte arrays as Binary. |
| Pub/Sub attributes | Strings: numbers as their decimal text (42), booleans as true or false, binary values base64-encoded and named, comma-separated, in the attribute x-pq-base64-attributes. |
| Azure Queue Storage | Nothing: Azure messages have no attributes. They stay on the message for every other reader. |
AMQP 0-9-1 header types no other interface has are delivered to SQS, Pub/Sub and Service Bus as text: a nested table or an array as JSON (SQS data type String.json), a decimal as its decimal text (SQS type Number), a void value as an empty string (a null property through Service Bus). For example the header meta = table {k: "v"} reads through SQS as the attribute meta of type String.json with value {"k":"v"}.
Names starting x-pq- are the service's own markers and metadata. Clients cannot set them, except x-pq-body-encoding = base64 on an SQS send as above. AMQP 0-9-1 readers see every header, these included.
Idle deletion
An entity that came into being implicitly is deleted once it has been empty and unused for an hour. An entity you create explicitly (with CreateQueue, queue.declare, Create Queue, CreateTopic and so on) stays until you delete it or its tenant is deleted, as does an implicit one you later create explicitly. This keeps the per-tenant entity cap from filling up with names used once.
The limits, and why
The service is shared and free, so each limit stops one client from taking what everyone else needs. Every refusal is the protocol's own error, so your SDK reports it the way it would against the real service. The documentation lists the exact error for each limit in each protocol.
| Limit | Value | Why |
|---|---|---|
| Message size, body plus attributes | 256 KiB | Keeps any one message from taking a large share of memory and bandwidth. Protocols whose own limit is lower keep theirs: Azure Queue Storage allows 64 KiB. |
| Queues, topics and subscriptions per tenant | 100 | Every queue costs memory and storage bookkeeping. |
| Messages per queue | 1,000 | Bounds the storage one queue can take. Further sends are refused; nothing already stored is dropped. |
| Message bytes per tenant | 16 MiB | All the messages a tenant holds at once, across its queues, including messages received but not yet acknowledged. Each message counts with its attributes, properties and headers. Storage is shared: this keeps any one user from filling it for everyone. Further sends are refused until something is consumed. |
| Deduplication IDs per tenant | 1 MiB | The deduplication IDs a tenant's queues remember at once (SQS FIFO, Service Bus duplicate detection), each counted as its length plus 192 bytes, for as long as its deduplication window lasts. Past it, sends carrying a new ID are refused until older IDs expire. |
| Message lifetime: retention, time to live, delay | 1 hour | A queue is for passing messages on, not for keeping them, and the service has no backups; a short lifetime also limits how long anyone can hold storage. Longer values are refused, never silently shortened. |
| Idle time before an implicit entity is deleted | 1 hour | Keeps names used once from accumulating. |
| Idle time before a whole tenant is deleted | 7 days | Identifiers nobody uses any more should not hold storage for ever. |
| Connections per client address | 50 | Bounds sockets per client on the AMQP ports (5671, 5673), which clients reach directly. |
| Requests per client address | 100 a second, bursts of 200 | Shares the machine fairly across SQS, Pub/Sub, Azure Queue Storage and Service Bus requests. |
| New connections per client address | 5 a second, bursts of 20 | Stops a client opening and closing AMQP connections (each a TLS handshake) as fast as it can. |
| Temporary bans | 100 refusals within a minute | An address that keeps exceeding the limits is refused outright for a minute, then 10 minutes, an hour and a day, before any work is done for it. |
| New tenants per client address | 20 an hour | Stops one client from creating tenants in bulk. |
| Tenants in total | 10,000 | Bounds the whole service. |
| AMQP 0-9-1 channels per connection | 64 | Bounds the work one connection can hold open. |
| Service Bus sessions and links per connection | 16 and 128 | The same, for AMQP 1.0. |
| Pub/Sub calls in progress per client address | 100 | The same, for gRPC: an open streaming pull counts until it closes. |
The broker also stops accepting new messages when its disk runs short, so one busy tenant cannot fill it for everyone.
Endpoints and TLS
Every public endpoint uses TLS with a Let's Encrypt certificate, so clients need no extra trust settings:
| Endpoint | Port | Protocol |
|---|---|---|
amqp.powerqueue.link | 5673 | AMQP 0-9-1 over TLS (amqps) |
amqp.powerqueue.link | 5671 | Azure Service Bus, AMQP 1.0 over TLS |
amqp.powerqueue.link | 443 | Azure Service Bus administration API (HTTPS) |
sqs.powerqueue.link | 443 | Amazon SQS (HTTPS) |
pubsub.powerqueue.link | 443 | Google Cloud Pub/Sub (gRPC over HTTPS) |
azure.powerqueue.link | 443 | Azure Queue Storage (HTTPS, account in the path) |
The HTTPS and gRPC endpoints go through a reverse proxy (Caddy) that terminates TLS and passes each client's address on to the broker, so the per-address limits apply to the real client. The two AMQP ports are the broker's own TLS listeners, using the same certificate. AMQP 0-9-1 uses port 5673 rather than its usual 5671 because the Service Bus SDKs always connect to 5671 and offer no way to choose another port. There are no plain-text endpoints.
What is kept, and for how long
- Across maintenance. The service is down from time to time for maintenance, without notice. Tenants, queues, topics, subscriptions and messages are stored on disk and normally survive those restarts.
- Messages live at most 1 hour, less if you ask for a shorter retention or time to live. A tenant holds at most 16 MiB of messages at once.
- Idle tenants are deleted automatically, with everything in them, after 7 days without activity. Any request or connection naming the tenant, and any message sent to or received from its queues, counts as activity; a tenant with a connection open is never deleted. Use the identifier again after that and you get a new, empty tenant.
- No guarantee. Nothing is backed up, and data may be lost at any time, for example when a storage format changes in an upgrade.
When the live statistics on the front page have not been updated for a few minutes, the service is probably down. Write your code to cope: create (or simply use) your queues at start-up, retry connections, and do not rely on anything being there.
What is deliberately not offered
- Accounts, keys and access control. Anything that needs a signup is out of scope; the identifier is the whole credential. IAM policies, SQS permissions and Azure ACLs are stored and echoed back, but grant nothing.
- Durability and backups. Data normally persists, but nothing is backed up or guaranteed.
- Pub/Sub push subscriptions. They would make the service send requests to any address on the internet; use pull or streaming pull.
- The broker's management HTTP API. It is off in public mode.
- Plain-text endpoints. Only TLS is offered.
- Support, notice of maintenance, or any service level.