> ## 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.
</Tip>

### 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](/latest/mqtt-broker/clustering) builds on it for session failover — enable durability when you need subscriptions to follow a client that reconnects to another node.
</Note>

***

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.

<Tabs>
  <Tab title="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"
      }
    }
    ```
  </Tab>

  <Tab title="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"
      }
    }
    ```
  </Tab>

  <Tab title="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
    ```
  </Tab>
</Tabs>

***

## 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](/latest/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](/latest/mqtt-broker/clustering#limitations).
</Warning>

***

## 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>

<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>

<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>

<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](/latest/mqtt-broker/clustering#limitations).
</Warning>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="High-Availability Clustering" icon="server" href="/latest/mqtt-broker/clustering">
    Run multiple brokers with quorum replication and session failover on top of durability.
  </Card>

  <Card title="Broker Configuration" icon="gear" href="/latest/mqtt-broker/configuration">
    Ports, TLS, listeners, and other broker settings.
  </Card>
</CardGroup>
