---
title: MQTT Buffering and QoS
slug: reference/mqtt-buffering-and-qos
docTags: 
createdAt: 2026-08-18T13:52:08.000Z
---

Buffering means holding a message so it can be delivered later, after a broker restart, a network drop, or a client being offline. Two independent paths exist: publisher to broker, and broker to subscriber. Both depend on the same two mechanisms working together: QoS and persistent sessions.

Key point up front: QoS ≥1 and a persistent session are both required, together. QoS governs the handshake while two clients are connected. It does not, by itself, make anything survive a disconnect. A persistent session is what makes the broker (and client) hold messages across a disconnect. Neither alone is enough.

## What each setting is

### QoS (Quality of Service)

QoS is the delivery guarantee on a message. There are three levels:

| QoS | Guarantee     | Behavior                                                                                                                     | Buffered |
| --- | ------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------- |
| 0   | At most once  | Fire and forget. Sent once, never held or retried. Lost if the link is down.                                                 | No       |
| 1   | At least once | Kept and resent until the other side acknowledges it. May arrive more than once.                                             | Yes      |
| 2   | Exactly once  | Like QoS 1, with extra steps to avoid duplicates. Higher overhead, used for critical data where duplicates are unacceptable. | Yes      |

Buffering only exists at QoS ≥1, because only then is a message held until it is confirmed. The collector subscribes at the level set by the `QOS` collector setting, which defaults to `1`. See [MQTT collector](docId\:GgnQIJHlxIFoimBt-yP2C) for the collector settings.

### Persistent session

A session is the state the broker remembers about a client: its subscriptions and its queued messages. A persistent session (clean session = false) means the broker keeps that state while the client is offline, instead of throwing it away on disconnect. This is what makes buffering work for the collector. Without it, the broker forgets the collector the moment it disconnects and queues nothing. On the Factry MQTT collector this is the `PersistentSession` setting, which is **off** by default since collector version 5.0.0. Nothing is queued for the collector until you enable it.

:::hint{type="info"}
The collector connects over MQTT 3.1.1. It does not use MQTT 5, so there is no Clean Start or Session Expiry Interval to configure on the collector side. `PersistentSession` is the only session setting.
:::

The session is tied to the client id. The broker uses the client id to recognize the returning collector and hand back its queue.&#x20;

:::hint{type="info"}
The client id of the Factry MQTT collector is `factry-mqtt-collector-<uuid>` and is fixed. For a collector in a high availability pair the uuid is the main collector's, so both members connect with the same client id and share one broker session, which is what lets the failover member pick up the queue.
:::

### Keep alive

Keep alive is how often the collector signals the broker that it is still there. On the Factry MQTT collector this is the `KeepAliveSecs` setting, which defaults to `30`. If the broker hears nothing for 1.5x the keep alive interval, it decides the collector is gone and starts queueing for it. A shorter keep alive means the broker notices a real drop sooner and starts holding messages sooner.

## Publisher to broker buffering

Path: publisher up, broker or network down. The publisher should hold the data produced during the outage and send it on reconnect.

**Requirements on the publisher:**

- Publish at QoS ≥1, so each message is held until the broker acknowledges it.
- clean session = false and a fixed client id, so an interrupted session resumes instead of being discarded.

Caveat: whether new samples produced while offline are actually buffered depends on the publisher client, not on the MQTT flags. Some clients persist a queue to disk, some hold it in memory, some drop it, and some only queue while they think they are connected. The flags set up the session; the client library decides if there is a real buffer. See Troubleshooting.

## Broker to subscriber buffering

Path: broker up, subscriber (collector) down. The broker holds the collector's messages and delivers them in order on reconnect.

**Requirements on the collector:**

- Persistent session on (`PersistentSession` true, which sets clean session = false). It is off by default.
- Fixed, unique client id.
- Subscription QoS ≥1 (`QOS` 1 normally).
- The collector must not be paused. Pausing clears the session on the broker and discards its queue, and resuming clears it again before reconnecting. Buffering survives a crash, a network drop or a restart, not a pause.

**Broker requirements (names vary by broker):**

For queued messages to survive a *broker* restart, the broker must:

- queue QoS ≥1 messages for offline persistent sessions,
- allow the per-client queue to be sized for your worst-case outage (message count, and byte cap if your broker separates them),
- persist sessions and queues to disk, so they are not lost on restart.

:::hint{type="info"}
**Example (Mosquitto):&#x20;**`persistent` true, `max_queued_messages`, `max_queued_bytes`. HiveMQ, EMQX and others expose the same three capabilities under different names. Check your broker's docs for the equivalents.
:::

On reconnect the collector receives the queued messages in the order the broker received them. It also reconciles its subscriptions inside the surviving session: it records the topics it subscribed to and, on the next connect, unsubscribes the ones that are no longer configured.

## What the collector depends on but does not control

- Publish QoS. What the collector receives is min(publish QoS, subscription QoS), so a source publishing at QoS 0 gets no broker-side queueing whatever the collector is set to. Check the publisher's QoS before assuming a collector problem. Sparkplug B is the common case here, see Sparkplug B and QoS below.
- Publisher-side buffering (source up, broker or network down) happens on the publishing device, not the collector. It belongs in the broker or publisher documentation.

## Troubleshooting

QoS is only as good as the publisher's implementation. The flags describe intent. Whether the publisher actually buffers during an outage depends on the client. Check its buffering behavior directly (outgoing queue, memory or persisted, what it does on a detected disconnect) rather than assuming QoS ≥1 means store and forward.

### node-red / mqtt.js publisher

The node-red MQTT node (mqtt.js) only queues outgoing messages while it believes it is connected. Once it detects the disconnect (after 1.5x keep alive, or immediately when the broker sends a disconnect on a clean stop) it stops buffering, so the first few points get queued and the rest are lost.

Workaround used: set keep alive to 0 so the client always considers itself connected and keeps buffering in memory.

Caveats:

- The buffer is in memory. Restarting the publisher loses it.
- With keep alive 0 the client will not detect a dead link via keep alive, it relies on TCP.
- Rough sizing, payload bytes only: a 30-metric Sparkplug message in mqtt.js is about 1.25 kB, so one message per 5 s is about 22 MB per day. mqtt.js holds each queued message as a JavaScript object in its outgoing store, with the topic string, packet metadata and buffer allocation on top of the payload, so real memory use is a multiple of that. Measure actual process memory growth for your payload and provision generous headroom rather than sizing on payload alone.

### Sparkplug B and QoS

Sparkplug B publishes NBIRTH, DBIRTH, NDATA and DDATA at QoS 0. The exceptions are the primary host STATE message and the NDEATH death certificate, which the spec requires at QoS 1 (NDEATH is registered as the MQTT Will, with Will QoS 1 and retain false). It does not use retain on data messages either. Instead it recovers state with a per-message sequence number and birth/death certificates, and a host can request a rebirth to resync. The consequence is that a spec-compliant Sparkplug B source gives no broker-side buffering: data produced during an outage is lost unless the device has its own store and forward.

Some Sparkplug-capable clients still expose a QoS setting and let you publish at QoS ≥1. That is outside the spec but does enable MQTT buffering. If you rely on it, confirm the device honours it end to end.
