Quality of Service (QoS)
QoS is an MQTT delivery level that you can set per message or per connection. In kraken it only applies to one hop of a message's journey, and on the default broker it has no effect at all. This page says exactly what it does and does not cover.
What QoS Governs
A message travels from the publisher's WebSocket connection into kraken, through kraken's broker backend, and out over each subscriber's WebSocket connection. The QoS level you set (0, 1 or 2, default 1) is validated (anything else is treated as 1) and handed to the broker backend:
- Built-in
synbroker (the default): QoS is accepted and ignored. Delivery is in-memory fan-out to the subscribers connected at that moment, at most once, whatever level you choose. BROKER_BACKEND=mqtt: kraken publishes and subscribes on the external MQTT broker (EMQX, Mosquitto and so on) at the level you set, so QoS governs the hop between kraken and that broker. See Scaling.
The WebSocket legs have their own, simpler contract:
- A publish can be acknowledged. On protocol version 2 (current JavaScript, Python and Go SDKs), when you pass a callback the SDK attaches a reference and kraken answers with a
publishedframe once it has accepted the message. That is what the emit callback resolves on. - Nothing is resent. If the acknowledgement does not arrive within 10 seconds the callback receives an error, and it is up to you to publish again.
- There is no exactly-once guarantee end to end. A publisher that retries produces two messages, so use an idempotency key in your payload for anything that must not be applied twice.
What kraken Does Not Do
Plan around these; none of them is configurable in the current release:
- No offline queue on the default broker. A subscriber that is disconnected when a message is published never receives it, and a load-balanced group with no connected member drops the message.
- No history or catch-up. kraken does not resend missed messages after a reconnect. If clients need state they missed, load it from your own store, for example with a hydration webhook or a retained "current value" message (see Topics).
- No redelivery of unacknowledged messages. See Delivery acknowledgements below.
With BROKER_BACKEND=mqtt, an actor whose auth marks its session persistent (persistent_session: true in an HTTP auth response) keeps an MQTT session on the external broker across disconnects, and that broker queues QoS 1 and 2 messages for it under its own rules. The static auth file never requests a persistent session.
QoS Levels
| Level | Name | syn broker | mqtt broker hop | Use Case |
|---|---|---|---|---|
0 | At-most-once | Ignored | Forwarded once, no acknowledgement | Telemetry, cursors, typing |
1 | At-least-once | Ignored | Acknowledged; may be forwarded more than once | Notifications, chat messages (default) |
2 | Exactly-once on the hop | Ignored | MQTT four-step handshake on that hop only | Where a duplicate is costly and you also dedupe on the client |
Even on the MQTT hop, QoS 2 does not extend to the WebSocket legs. Treat it as "fewer duplicates", not "no duplicates", and keep an idempotency check.
Setting QoS
Set a default for the connection and override it per message:
import { NoLag } from "@nolag/js-sdk";
// Default QoS for every publish and subscription on this connection
const client = NoLag(token, { url: "ws://localhost:8080/ws", qos: 1 });
await client.connect();
const room = client.setApp("factory").setRoom("floor-1");
// Override per message
room.emit("temperature", { temperature: 72.5 }, { qos: 0 });
room.emit("orders", { orderId: "12345", status: "completed" }, { qos: 2 });Subscriptions take a qos option too, which applies to the subscription on the MQTT broker hop.
Confirming a Publish
Pass a callback to emit to learn whether kraken accepted the message. The callback resolves on kraken's published acknowledgement, or receives the error if kraken refused it (unknown room, no permission, rate limit, size). If neither arrives within 10 seconds it receives a timeout error. The SDK never resends on its own.
room.emit("orders", { orderId: "12345", status: "completed" }, { qos: 1 }, (err) => {
if (err) {
// Refused or not acknowledged. Decide whether to publish again; the SDK will not.
console.error("publish failed:", err.message);
return;
}
console.log("accepted by kraken");
});"Accepted" means kraken checked the publish and handed it to its broker backend. It does not mean any subscriber received it.
Delivery Acknowledgements
When kraken records messages (RECORD_MESSAGES, on by default in the Docker image), each delivered message carries a msgId and a flag asking the client to acknowledge it. The JavaScript SDK sends these acknowledgements automatically (batched if you set ackBatchInterval), and kraken marks the delivery as acknowledged in its message store. The Python and Go SDKs do not send them.
That record is all an acknowledgement does in the current release. kraken does not resend a message that was never acknowledged, so treat msgId as a convenient unique id for deduplication rather than as a delivery guarantee.
Choosing a QoS
- On the default
synbroker: the level makes no difference. Leave the default. - On the
mqttbroker: use0for high-rate data where the next update supersedes this one,1(the default) for most application traffic, and2only where the broker hop must not duplicate and you also dedupe on the client.
Handling Duplicates
Whatever the level, make consumers idempotent. Key on an identifier from your own payload, which survives a publisher retry as well as a broker duplicate:
const processed = new Set<string>();
room.on("orders", (data: { orderId: string }) => {
if (processed.has(data.orderId)) return;
processOrder(data);
processed.add(data.orderId);
// Keep the set bounded
if (processed.size > 10000) {
Array.from(processed).slice(0, 5000).forEach((id) => processed.delete(id));
}
});