> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreflux.org/llms.txt
> Use this file to discover all available pages before exploring further.

# MQTT State Durability

> Persist retained messages, sessions, and queued QoS 1/2 across broker restarts with the durability write-ahead log

## Keep MQTT state across restarts

By default, retained messages, persistent sessions, and queued QoS 1/2 traffic live in memory. A broker restart clears them. **Durability** writes that state to a local write-ahead log (WAL) so after a restart, clients can reconnect and pick up where they left off — their subscriptions, retained values, and any messages that were still waiting to be delivered.

> [!TIP]
>
> **Like a notebook next to a whiteboard.** The whiteboard is fast working memory; the notebook is what you still have after the room goes dark. Durability is the notebook for MQTT state that must survive a restart.

### When to enable durability

Enable durability when you need:

* retained values to survive a broker restart;
* persistent sessions and their subscriptions restored after reboot; or
* queued QoS 1/2 messages re-offered to clients that reconnect with a persistent session.

Leave it off on constrained edge devices (slow flash, tight write budgets) or when restart persistence is not required.

> [!NOTE]
>
> Durability is a **broker-level** setting. It works on a standalone broker and on every cluster node. [High-availability clustering](/v2.14/mqtt-broker/clustering) builds on it for session failover — enable durability when you need subscriptions to follow a client that reconnects to another node.

***

Configure the `Durability` section (or `COREFLUX_DURABILITY_*` environment variables), restart the broker, and verify retained or session state after a controlled restart.

## Quick Start

Add a `Durability` block to the broker configuration, or set environment variables (env vars override the file). Restart the broker for changes to take effect.

**Enable (typical)**

Opt in with the default flush policy (group commit every 50 ms):

```json wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "Durability": {
    "Enabled": true,
    "FsyncPolicy": "interval",
    "FsyncIntervalMs": 50,
    "Codec": "json"
  }
}
```

**Strongest fsync**

Flush every append when you need the smallest power-loss window on a single node:

```json wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
{
  "Durability": {
    "Enabled": true,
    "FsyncPolicy": "always",
    "Codec": "json"
  }
}
```

**Environment variables**

Tune without editing the config file (useful in containers):

```bash wrap theme={"theme":"css-variables","languages":{"custom":["/languages/lot.json"]}}
export COREFLUX_DURABILITY_ENABLED=true
export COREFLUX_DURABILITY_FSYNC=interval
export COREFLUX_DURABILITY_FSYNC_INTERVAL_MS=50
export COREFLUX_DURABILITY_CODEC=json
# optional: export COREFLUX_DURABILITY_DATA_DIR=/var/lib/coreflux/mqttwal
```

***

## What is persisted

| State | Survives restart? | Notes |
| - | - | - |
| **Retained messages** | Yes | Reloaded on boot on that node |
| **Persistent sessions + subscriptions** | Yes | A client that reconnects with a persistent session gets its subscriptions back |
| **Queued QoS 1/2 (unacked)** | Yes | Re-offered on session restore (**at-least-once** — a message acked at crash time may be redelivered) |

QoS 0 traffic is not made durable by this layer — MQTT treats it as best-effort.

***

## Configuration reference

| Config field | Env var | Default | Description |
| - | - | - | - |
| `Enabled` | `COREFLUX_DURABILITY_ENABLED` | `false` | Master switch — opt in for restart durability |
| `FsyncPolicy` | `COREFLUX_DURABILITY_FSYNC` | `interval` | `interval` (group commit) or `always` (flush every append) |
| `FsyncIntervalMs` | `COREFLUX_DURABILITY_FSYNC_INTERVAL_MS` | `50` | Flush interval in ms when policy is `interval` |
| `Codec` | `COREFLUX_DURABILITY_CODEC` | `json` | Log encoding: `json` (readable) or `binary` (compact) |
| `DataDir` | `COREFLUX_DURABILITY_DATA_DIR` | `{data path}/mqttwal` | Root directory for durable MQTT state |
| `MaxSegmentBytes` | — | `8388608` (8 MiB) | Segment size that triggers rotation |
| `CheckpointEveryAppends` | — | `4096` | Appends between checkpoints that truncate older segments |

Accept `1` / `true` (case-insensitive) for `COREFLUX_DURABILITY_ENABLED`.

### Fsync policy

| Policy | Behavior | Trade-off |
| - | - | - |
| `interval` (default) | Appends buffer and flush together on a timer | Bounded loss window on **power failure** only; process crashes still keep what the OS already had |
| `always` | Flush to disk on every append | Strongest single-node guarantee; highest I/O cost (costly on eMMC/SD edge devices) |

### Codec

* **`json` (default)** — human-readable records; easier to inspect during debugging.
* **`binary`** — compact encoding; smaller on disk and typically faster to encode/decode on busy brokers. Existing JSON segments still recover; new records append in binary. Switching back from binary to `json` for inspection of already-written binary segments is not supported — stay on `json` if you need text tooling on the log.

In a cluster, the same codec setting also applies to the replication logs used for quorum durability.

***

## Durability and clustering

Durability and clustering solve different loss modes:

| Concern | What protects you |
| - | - |
| **Single-node restart** | Durability WAL on that broker |
| **Minority node crash after an acknowledged QoS ≥ 1 publish** | Cluster [quorum replication](/v2.14/mqtt-broker/clustering#delivery-guarantees) |
| **Client reconnects to another node** | Session takeover — requires **`Durability.Enabled`** so subscriptions can hydrate on the new node |

> [!WARNING]
>
> **Offline queues stay on the node that hosted them.** Subscriptions can follow a client after failover (with durability enabled), but queued QoS 1/2 messages for offline clients do not migrate. See [clustering limitations](/v2.14/mqtt-broker/clustering#limitations).

***

## Observability

Prometheus `/metrics` exposes durability and WAL series, including:

| Metric family | What it shows |
| - | - |
| `coreflux_durability_enabled` | Whether durability is on |
| `coreflux_wal_*` | Appends, bytes written, fsyncs, last fsync latency |
| `coreflux_durable_*` | Counts for retained messages, sessions, in-flight messages, and in-flight drops |

Use these alongside your usual broker health checks after enabling durability or changing fsync policy.

***

## Limitations

> [!WARNING]
>
> **Durability is opt-in.** The default is `Enabled: false`. Without it, retained messages, sessions, and queued QoS 1/2 are lost on restart, and cluster session failover cannot restore subscriptions on another node.

> [!WARNING]
>
> **Enabling durability reduces QoS 1 throughput by about 20%.** The write-ahead log adds per-message work on the publish path. QoS 0 is unaffected. Leave durability off when you need maximum QoS 1 throughput and do not need restart persistence or cluster session failover.

> [!WARNING]
>
> **At-least-once after crash.** A QoS 1/2 message acknowledged at the exact moment of failure may be redelivered after restore. Design consumers to tolerate duplicates.

> [!WARNING]
>
> **Retained messages remain node-local.** Durability restores retains on the same node after restart; it does not create a cluster-wide retained store. See [clustering limitations](/v2.14/mqtt-broker/clustering#limitations).

***

## Next Steps

> **[High-Availability Clustering](/v2.14/mqtt-broker/clustering)**
>
> Run multiple brokers with quorum replication and session failover on top of durability.

> **[Broker Configuration](/v2.14/mqtt-broker/configuration)**
>
> Ports, TLS, listeners, and other broker settings.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.