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:

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:

ProtocolThe identifier goes inThe secret part
AMQP 0-9-1the virtual host (the URL path)any user name and password
Amazon SQSthe access key idany secret access key, any region
Google Cloud Pub/Subthe project idno credentials
Azure Queue Storagethe storage account name (the first segment of the URL path)any base64 account key
Azure Service Busthe shared access key name in the connection stringany 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:

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:

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 throughA text bodyA binary body (not valid text)A typed Service Bus body (an AMQP value or sequence)
AMQP 0-9-1, Pub/Sub, Service Busthe bytes as sentthe exact bytesService Bus: the value as sent. AMQP 0-9-1 and Pub/Sub: JSON text, with x-pq-body-encoding = json (a header, an attribute)
SQSthe textbase64, with the message attribute x-pq-body-encoding = base64JSON text, with x-pq-body-encoding = json
Azure Queue Storagethe textbase64, unmarked: Azure messages have no attributes to carry a markJSON 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 throughWhat it receives
AMQP 0-9-1 headers, SQS message attributes, Service Bus application propertiesTyped values: text as String, numbers as Number (keeping an SQS type label such as Number.float), byte arrays as Binary.
Pub/Sub attributesStrings: 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 StorageNothing: 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.

LimitValueWhy
Message size, body plus attributes256 KiBKeeps 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 tenant100Every queue costs memory and storage bookkeeping.
Messages per queue1,000Bounds the storage one queue can take. Further sends are refused; nothing already stored is dropped.
Message bytes per tenant16 MiBAll 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 tenant1 MiBThe 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, delay1 hourA 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 deleted1 hourKeeps names used once from accumulating.
Idle time before a whole tenant is deleted7 daysIdentifiers nobody uses any more should not hold storage for ever.
Connections per client address50Bounds sockets per client on the AMQP ports (5671, 5673), which clients reach directly.
Requests per client address100 a second, bursts of 200Shares the machine fairly across SQS, Pub/Sub, Azure Queue Storage and Service Bus requests.
New connections per client address5 a second, bursts of 20Stops a client opening and closing AMQP connections (each a TLS handshake) as fast as it can.
Temporary bans100 refusals within a minuteAn 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 address20 an hourStops one client from creating tenants in bulk.
Tenants in total10,000Bounds the whole service.
AMQP 0-9-1 channels per connection64Bounds the work one connection can hold open.
Service Bus sessions and links per connection16 and 128The same, for AMQP 1.0.
Pub/Sub calls in progress per client address100The 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:

EndpointPortProtocol
amqp.powerqueue.link5673AMQP 0-9-1 over TLS (amqps)
amqp.powerqueue.link5671Azure Service Bus, AMQP 1.0 over TLS
amqp.powerqueue.link443Azure Service Bus administration API (HTTPS)
sqs.powerqueue.link443Amazon SQS (HTTPS)
pubsub.powerqueue.link443Google Cloud Pub/Sub (gRPC over HTTPS)
azure.powerqueue.link443Azure 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

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