Skip to content

Transport

Scope: the module overview already introduced Aeron Transport as "a very fast pipe — a reliable, ordered stream over UDP/IPC" and contrasted it with Cluster. This page goes one layer deeper on how Transport gets that speed and reliability without TCP.

TL;DR — in 30 seconds:

  • The Media Driver runs as a separate process from your application; the two talk through shared memory, not a socket call, so offering a message doesn't need a syscall on the hot path.
  • A channel (URI) + stream ID names a stream — pick IPC for same-machine delivery, since it skips the kernel network stack entirely, unlike loopback UDP.
  • Aeron gets reliability without TCP by having the subscriber NAK just the missing range from a term buffer; offer() never blocks — a full subscriber makes it return "not accepted" instead of stalling the publisher.

1. The media driver: a separate process, not a library call

Aeron splits into two halves that run as separate processes: your application, which holds Publication and Subscription handles, and the Media Driver, which does the actual sending and receiving. Your application never touches a socket directly.

The two halves talk to each other through shared memory (memory-mapped files), not a normal inter-process call. That matters because it means offering a message doesn't require a syscall or a kernel context switch on the hot path — the application thread writes into a shared buffer, and the Media Driver (running its own thread, polling that buffer) picks up the write and pushes it onto the wire. This is the same mechanical-sympathy trade-off as the memory-hierarchy page's locality argument, applied to inter-process communication instead of cache lines: avoid the expensive path (kernel transition) whenever a cheaper one (shared memory) will do.

flowchart LR
  subgraph APP["Publisher application"]
    P["Publication.offer(message)"]
  end
  subgraph MDP["Media Driver — publisher side"]
    MD1["reads shared memory,<br/>writes to term buffer,<br/>sends over the wire"]
  end
  subgraph MDS["Media Driver — subscriber side"]
    MD2["receives, writes to<br/>term buffer, exposes<br/>via shared memory"]
  end
  subgraph SUB["Subscriber application"]
    S["Subscription.poll(handler)"]
  end
  P -->|shared memory| MD1
  MD1 -->|UDP or IPC| MD2
  MD2 -->|shared memory| S

2. Publications and subscriptions: naming a stream

A Publication is the handle your code uses to offer messages onto a stream. A Subscription is the matching handle on the receiving side. The two are wired together by a channel (a URI describing the transport — a UDP endpoint, or aeron:ipc for same-machine delivery) plus a stream ID (an integer that lets several logical streams share one channel).

Channel media When you'd pick it
UDP unicast Publisher and subscriber(s) on different machines, point-to-point or fan-out
UDP multicast Many subscribers on the same network segment need the same stream, without per-subscriber copies
IPC (aeron:ipc) Publisher and subscriber are processes on the same machine — skips the network stack entirely and hands data across shared memory only

Choosing IPC over loopback UDP for same-host communication is itself a mechanical-sympathy decision: loopback still walks the kernel's network stack, while IPC does not.

3. Reliable delivery without TCP: term buffers and NAKs

Aeron gets reliability over UDP — a protocol with no built-in delivery guarantee — without TCP's approach. The core difference is what gets retransmitted when something is lost.

Publications write into fixed-size term buffers; each piece of data has a well-defined position within the stream. A subscriber tracks the positions it has received, so a gap (a piece of the stream that never arrived) is easy to detect. Where TCP has the sender wait on a moving acknowledgment window, Aeron flips the trigger: a gap makes the subscriber send a NAK (negative acknowledgment) naming the missing range, and the sender retransmits only that range from its term buffer.

Note: the practical effect is that loss on a healthy stream is cheap to recover from — one small retransmission for the missing piece — rather than resetting a whole congestion window the way TCP would after a loss. Full wire format: Aeron Transport Protocol Specification.

4. Backpressure: what happens when a subscriber can't keep up

offer() never blocks the publisher waiting for a slow subscriber. If a subscriber isn't consuming fast enough, offer() returns a status telling the caller it was not accepted this time — the application is expected to check that result and decide whether to retry, drop, or apply its own backoff. This is a deliberate design choice: a Publication that could silently block would let one slow subscriber stall a fast publisher, which defeats the reason you reached for Aeron Transport in the first place.

5. How this connects

  • Cluster builds on exactly this layer. The commit path the module overview describes — leader appends, replicates, quorum acks — is Raft consensus running on top of the Publications/Subscriptions and reliable delivery this page describes. Transport gives you a fast, reliable pipe; Cluster adds agreement about what state that pipe has delivered.
  • The shared-memory hand-off is mechanical sympathy in practice. The module recommends Mechanical Sympathy (90) before or alongside Aeron for exactly this reason — the media-driver split, the shared-memory buffers, and the avoid-a-syscall design all come from the same "know what the hardware and kernel actually cost you" instinct that module teaches in general.
  • Backpressure is a single-writer-adjacent idea. A term buffer has exactly one writer (the Media Driver on the publish side), which is why Aeron can track position and detect gaps cheaply — the same single-writer reasoning the concurrency page uses to avoid contention shows up here as a design choice in the transport itself.

Common gotchas

  • Assuming Aeron's application code talks to the network directly. Fix: it never does — the Media Driver is a separate process, and the application only writes into a shared-memory buffer.
  • Choosing loopback UDP for same-host communication. Fix: use IPC (aeron:ipc) instead — loopback still walks the kernel's network stack, while IPC skips it entirely via shared memory.
  • Assuming offer() blocks until a slow subscriber catches up. Fix: it never blocks — it returns a "not accepted" status, and the application decides whether to retry, drop, or back off.
  • Assuming Aeron's reliability works like TCP's. Fix: on loss, the subscriber NAKs only the missing range from the sender's term buffer — the sender doesn't reset a whole congestion window the way TCP would after a loss.
Check yourself — why can offering a message avoid a syscall on the hot path, even though the message eventually leaves the machine over UDP?

Because the application only writes into a shared-memory buffer — the Media Driver, running as its own process/thread, is the one that reads the buffer and performs the actual send, so the application thread itself never makes a syscall.

Check yourself — two processes on the same machine want to exchange messages. Why is IPC a mechanical-sympathy win over loopback UDP?

Loopback UDP still walks the kernel's network stack even though the data never leaves the machine; IPC hands the data across shared memory only, skipping that stack entirely.

Check yourself — a subscriber is falling behind a fast publisher. What does offer() do, and why is that the right design?

It returns a "not accepted" status rather than blocking — a Publication that could silently block would let one slow subscriber stall a fast publisher, defeating the reason you reached for Aeron Transport.

Check yourself — a subscriber detects a gap in the stream. What does it do, and how does that differ from TCP's approach?

It sends a NAK naming the missing range, and the sender retransmits only that range from its term buffer — cheaper than TCP, which resets its whole congestion window after a loss.

You can defend this when you can explain why the Media Driver runs as a separate process instead of being a library call inside your application; say what a channel + stream ID identifies and why IPC beats loopback UDP on the same host; explain how a NAK-based retransmission differs from TCP's approach to loss; and say why offer() returning a "not accepted" status instead of blocking is the right shape for a publisher that must never stall on a slow subscriber.