Skip to main content

Store and forward in LoT connectors

Store and forward means holding messages while the next hop is slow or unavailable, then passing them on when it catches up. In Coreflux, every LoT connector sits behind a shared, bounded, in-memory queue between the broker and the connector’s handlers. ADD ROUTE_CONFIG decides what each connector does when that queue is full: wait (backpressure) or drop the newest message.
Like a ticket window with a limited line. People already in line are always served, in order. When the line is full you can wait for a spot, wait a short time then turn the newest arrival away, or turn them away immediately.

The principles, and how LoT handles them

What it does not do

This is not a complete store-and-forward system. The connector queue:
  • does not write to disk, so queued messages don’t survive a broker restart or crash,
  • does not replay messages after a destination comes back. Once a connector has taken a message from the queue, retrying a failed write is up to that connector type, and many simply log the failure,
  • does not acknowledge end to end: a publisher’s QoS 1/2 acknowledgement means the broker accepted the message, not that the destination stored it.

Filling the gaps

  • Upstream, use MQTT itself. Publish with QoS 1 or 2 from clients with persistent sessions (cleanSession=false, or a session expiry in MQTT 5), so the broker keeps their messages while they are offline. The broker’s durability settings decide what survives a broker restart.
  • Store first, forward later. Write each message to a database connector (or a model with STORE IN), then forward from that table on a schedule. The table is the durable buffer and can be replayed.
  • Let the destination buffer. Kafka, and a remote MQTT broker with persistent sessions, keep their own durable queues once a message reaches them.
  • Watch for drops. The broker logs when a connector starts dropping. Treat that as an alert.

When to use it

  • Leave the default (balanced) on most connectors.
  • Use lossless when a path must not lose messages (audit, compliance) and you accept that the publisher may wait.
  • Use realtime when high-rate telemetry must never stall the client that published it.
  • Set OVERLOAD_CAPACITY to cap one noisy connector so it cannot crowd out others.
ADD ROUTE_CONFIG is the only LoT block that works on every connector type. Place it next to that type’s own config (SQL_CONFIG, SOURCE_CONFIG, MODBUS_CONFIG, and so on). Order among ADD blocks does not matter.
Let’s look at how to set it, starting with the three common contracts.

Quick Start

Use lossless on a forwarding path that must keep every message. The publisher waits until this connector has room — it never drops.

Settings

All three settings are optional. An absent ADD ROUTE_CONFIG block inherits the broker default entirely.
A message already accepted for a connector is never evicted. Only the arriving message can be dropped. That keeps sequences such as Sparkplug birth then data self-consistent under load.
Do not set OVERLOAD_CAPACITY together with OVERLOAD_POLICY "lossless". A capacity cap drops overflow; lossless never drops. The broker logs an error naming the connector, ignores the capacity, and runs the connector as lossless. The same conflict applies if the connector inherits lossless from the broker default and then declares a capacity.

Defaults

Each setting resolves on its own:
  1. No ADD ROUTE_CONFIG block — the connector inherits the broker default for policy, timeout, and capacity.
  2. A block is present but a setting is omitted — that setting does not copy the broker’s timeout or capacity. OVERLOAD_POLICY inherits the broker default. OVERLOAD_TIMEOUT and OVERLOAD_CAPACITY use that policy’s own default (50 ms for balanced, unbounded capacity).
  3. An invalid value (unknown policy name, negative timeout, non-positive capacity) does not stop the connector loading. The broker logs an error and uses that field’s default.
Broker-wide defaults are environment variables, read once at process start. Change them and restart the broker. A connector’s own ADD ROUTE_CONFIG always overrides these for that connector. OVERLOAD_CAPACITY on a connector is a private budget for that connector. The shared ceiling is separate: one connector can hit its own cap and start dropping while other connectors still have room. See Broker Configuration for how these sit alongside other broker limits.

What happens under load

When a connector is waiting, that wait can delay the publishing client’s own publish — only that client, and only up to the wait budget. Other connected clients are not stalled by this connector. The broker logs two different saturation conditions (each at most once every five seconds):
  • The shared connector queue is full — every connector waiting behind it is affected.
  • A single connector is over the OVERLOAD_CAPACITY it declared for itself — the shared queue may still have room.
If two connectors could match the same topic (for example one exact mapping and another sensors/#), the broker applies the stricter wait-or-drop choice among those candidates. A lossless connector on an overlapping pattern can make a realtime connector wait, even if only the realtime connector would have handled the message. The broker never drops a message because a more lenient connector asked to drop it. If mixed policies on overlapping wildcards wait or drop more than you expect, check the patterns first — that behavior is by design.

Troubleshooting

That connector’s own OVERLOAD_CAPACITY is exhausted. Look for the broker warning that a connector is over its in-flight budget, not the warning that the shared queue is full.
Working as designed — lossless waits until there is room. Switch to balanced with a timeout if you would rather drop than stall, or cap a noisy connector with OVERLOAD_CAPACITY so it drops its own overflow instead.
It is declared alongside OVERLOAD_POLICY "lossless" (or the broker default is lossless). Check the broker log for the lossless/capacity conflict — capacity is ignored, not applied.
Another connector’s overlapping wildcard pattern resolved a stricter policy for that topic. See the overlapping-pattern warning above.
The resolved policy is realtime. That is expected — realtime drops without waiting.

Best practices

  • Start with the default balanced policy. Change a connector only when you have a reason (must not lose, must not stall, or must not crowd others).
  • Use lossless sparingly. It protects delivery at the cost of delaying the publisher that caused the backlog.
  • Put OVERLOAD_CAPACITY on the noisy connector, not on the critical one — the critical connector can stay unbounded while the noisy connector drops its own overflow.
  • Prefer exact topic mappings when two connectors would otherwise share a broad + / # pattern with different policies.
  • Invalid settings never fail the connector silently: read the broker log after deploy if behavior does not match the block you wrote.

Next Steps

Connectors Overview

Define connectors, mappings, and status topics.

Broker Configuration

Set broker-wide queue defaults and other limits.
Last modified on October 21, 2026