Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ergon-md-dpdk (experimental)

Not recommended for production use.

This crate is a proof of concept. I wrote it to get a better understanding of how a kernel-bypass transport for Aeron can be written. Of all of ergon’s modules, it is the one where I relied on AI the most, and I could not verify that what it does is correct, unlike the other modules, whose domains I understand well.

If you need kernel bypass for Aeron in production, use Aeron Premium, which is supported. No kernel-bypass product was compared on this hardware, so no speed claim is made either way.

On one host it gains nothing: host-local channels run on ordinary kernel sockets in this driver too (md-dpdk/native/dpdk/rusteron_dpdk_kernel.c), so there is no bypass to gain (below).

Glossary

Terms this chapter uses. Host, Kubernetes and Java terms: deployment glossary. Figures: md-dpdk/BENCHMARKS.md.

termwhat it iswhen it matters
media driverthe part of Aeron that moves messages between the network and a host’s applications; its own process, or embedded in yoursits transport can change; Aeron clients do not
channelan Aeron address: a UDP host and port, or IPCa UDP channel is one flow
stream, publication, subscriptionstream: the messages on a channel under one stream id. Publication: its sending end. Subscription: its receiving end
frame, MTUframe: Aeron’s unit on the wire, a header and data. MTU: the largest packet a link carries; AERON_MTU_LENGTH caps a frame1,408 by default here (RUSTERON_DPDK_MAX_AERON_MTU), so a frame fits one packet buffer
Aeron IPCAeron over shared memory, between processes on one hostboth ends on one host: use it or kernel UDP
Aeron Premiumthe commercial Aeron (aeron.io), with its own DPDK driverthe supported route to bypass in production; this crate is a proof of concept
conductor, sender, receiverthe media driver’s three agents (threads of work): control, sending, receivingunder DPDK the network threads must busy-spin on CPUs of their own: the install refuses otherwise
threading modehow the agents share threads: DEDICATED, SHARED_NETWORK, SHARED or INVOKERmeasured cost of SHARED_NETWORK
idle strategy, parkwhat an Aeron thread does with no work: spin and noop never sleep; backoff spins, yields, then parks (sleeps) ever longerspinning needs a CPU of its own
log buffer, term bufferAeron’s memory-mapped file of one stream’s messages, in three term bufferson hugepages, not sparse
receiver window, status messagewindow: the bytes a sender may have in flight. Status message: the receiver’s “send more”on MANA, sized to what the 256-entry ring holds (not measured here)
heartbeat, keep-aliveheartbeat: Aeron’s frame on a quiet stream. Keep-alive: your own small messagewhy a keep-alive; its effect is not measured here
kernel UDPthe normal path: NIC interrupt, the kernel’s network stack, a system callno setup, every tool works (tuning)
kernel bypassone process takes a NIC from the kernel and reads and writes its queues itselflower median and max in these runs; costs cores, NICs, root setup and upkeep
DPDKthe Data Plane Development Kit: the kernel-bypass library this crate uses
EALDPDK’s start-up layer (Environment Abstraction Layer): it maps the NICs and hugepages and starts DPDK’s threadsextra options: RUSTERON_DPDK_EAL_ARGS
NIC, PCI address, MACnetwork interface card (virtual on a cloud VM); its slot on the host’s bus; its hardware addressthe crate picks NICs by PCI address, on Azure by MAC
ENAAWS’s virtual NIC (Elastic Network Adapter)a reset restores its interrupt placement and adaptive moderation (md-dpdk/src/checks.rs): re-apply them (hosts)
MANA, accelerated networkingMANA: Azure’s virtual NIC (Microsoft Azure Network Adapter). Accelerated networking gives the VM a slice of it (a VF, virtual function)rings of at most 256 entries: set RUSTERON_DPDK_TX_DESCRIPTORS=256
low-latency queue, BAR, write-combiningENA’s fast send mode: the CPU writes each send into the NIC’s memory window (the BAR). Write-combining batches those writesstart fails on an ENA port whose BAR is not mapped write-combined (md-dpdk/tests/dpdk_ena_llq.rs)
igb_uio, uio_hv_generic, vfio-pcikernel modules that hand a NIC to a user-space processnode-level, as root: igb_uio on AWS, uio_hv_generic on Azure. The kernel’s tools then lose that NIC
interrupta NIC’s signal that packets are waiting; the CPU stops its work to handle itDPDK polls instead
interrupt moderationthe NIC waits to batch several packets into one interrupt. Adaptive: the driver re-tunes the wait itselfoff on Graviton in the best kernel path measured; its cost on x86 is not measured here
busy polling (kernel)a socket read spins on the NIC’s queue instead of sleeping (net.core.busy_read, busy_poll)measured, never isolated: no setting recommended (below). A node-wide sysctl (kernel docs)
busy-spina thread checks for work in a loop and never sleepsno wake-up delay; a whole core at 100%, idle or not
pin, CPU affinitypin: tie a thread to one CPU. Affinity: the CPUs a thread may run on (AERON_*_CPU_AFFINITY)the crate pins the agents (threads and cores)
housekeeping corethe core for interrupts, the conductor and every thread that does not spinkeeps that work off the busy cores
isolated CPUa CPU the kernel keeps its own work off: scheduler, timer tick, interruptsnode-level (kernel command line)
ring, descriptora NIC queue: RX receives, TX sends. Each descriptor in it points at one packet bufferRX default: min(4,096, the NIC’s maximum): 4,096 on ENA, 256 on MANA
packet buffer (mbuf)DPDK’s buffer for one packet, from a pool per NIC on hugepages16,384 a NIC by default
burstmany messages sent back to backthe RX ring must hold it while the reader catches up
jumbo framespackets above Ethernet’s usual 1,500 Bone run only
hugepageslarge memory pages (2 MiB here), reserved up frontDPDK needs them; about 37 MiB a NIC at the defaults. Node-level reservation
hugetlbfsa filesystem of hugepages; Aeron’s directory can live on onemount it with size=, or Aeron refuses every log buffer
transparent hugepagesthe kernel building large pages by itself, in the background (khugepaged)not separated in one run; node-level
page faultthe first touch of a memory page: the kernel stops the thread to map ithugepages and pre-touching move it off the hot path
flowpackets between one source and one destination address and portan Aeron channel is one flow; one flow topped out at 7.75–7.77 Gbit/s of Aeron frames on c7i.4xlarge
placement grouphosts placed close together: a cluster placement group (AWS) or a proximity placement group (Azure)two c7i.4xlarge pairs still differed by about 7 µs at the median
spot instancespare AWS capacity, sold cheaper; AWS can take it backa c9g.2xlarge pair: $0.306 spot against $0.696 on demand an hour (cost)
x86, Gravitonx86: Intel and AMD CPUs. Graviton: AWS’s Arm CPUsnever compared in one run: pairs, days and builds differ (BENCHMARKS.md)
SMT, hyperthread, sibling, vCPUSMT runs two hyperthreads (siblings) on one core. A vCPU is a hyperthread on x86, a whole core on Graviton (no SMT)on x86, leave each busy core’s sibling idle
bare metalan instance with no hypervisorc7i.metal-24xl, round 4: DPDK max 103 µs at 1 M msgs/s, 845 µs idle; 6 times a c7i.4xlarge pair’s price
round trip (ping-pong)one message out and its reply backevery latency here is a round trip
repone repeat of a runp50 and p99.99 are the median of 5 reps
NAK, retransmit, drop, short sendthe receiver asking for a resend; a frame sent again; a packet the NIC threw away; a send that put out less than askedany one makes the rep lossy
lossy rep, loss-free maximumlossy rep: a rep that lost anything. Loss-free maximum: the highest load with no lossy repa rate with a lossy rep is a ceiling, not an operating point
p50, p99.99, tailpercentiles: p50 is the median; at p99.99, 1 round trip in 10,000 is slower. The high ones are the tail
maxthe largest single round trip in any rep, unless labelled “median of rep maxima”one stall sets it
intended send timeunder load, latency counts from when a message should have been senta stall then counts against every message behind it
CPU countera register in each core that counts at a fixed rate (the TSC on x86)the transport reads it inline (clock)
chronythe daemon that keeps the host’s wall clock on a reference: NTP, or a hardware clock such as the NIC’s (PTP)NIC timestamps need it (time sync)
NIC receive timestampthe time the NIC stamps on each packet it receivesAWS ENA only (configuration)
media receive timestampthe driver’s stamp of when a frame arrived, kept in the frame (media-rcv-ts-offset=reserved)one run

What it is

  • Aeron’s media driver carries Aeron’s messages between the network and the applications on a host, over UDP.
  • Kernel UDP is the normal path. The NIC raises an interrupt, the kernel’s network stack handles the packet, and the reading thread gets it with a system call.
  • Kernel bypass takes a NIC away from the kernel and gives it to one process, which reads and writes the NIC’s queues itself.
  • DPDK is the library that does this. A thread polls the NIC in a loop: no interrupt, no system call, no copy into a socket.
  • This crate plugs DPDK into Aeron’s C media driver (through rusteron). The driver sends and receives through a table of functions (aeron_udp_channel_transport_bindings_t); the crate fills that table with DPDK versions. Aeron clients do not change.
kernel UDP:  client ─ media driver ─ system call ─ kernel UDP/IP ─ interrupt ─ NIC
DPDK:        client ─ media driver ─ this crate, polling ─────────────────────── NIC

It runs on AWS’s ENA and Azure’s MANA virtual NICs, on Linux x86-64 and aarch64 (Graviton). The sender and the receiver each get a NIC of their own; the host’s primary NIC stays with the kernel.

Tuning kernel UDP first

All four are node-level settings (how to set them).

settingwhat it doesevidence
interrupt moderation offthe NIC interrupts for each packet instead of waiting to batch severalGraviton G1–G3: part of the best kernel path; no lossy rep to 1 M msgs/s in G1 and G3. x86: not measured here
NIC interrupts on a core of their ownthe kernel’s receive work runs on a core with nothing else to doGraviton G1, one A/B: on the housekeeping core 1 M msgs/s lost in 5 of 5 reps; on the receiver’s core it collapsed from 500 k msgs/s. x86: not measured here
busy pollinga socket read spins on the NIC’s queue instead of sleeping until an interruptnot recommended: never isolated. x86 round 7: the best kernel arm tried, with a 4,096 ring (two settings differ). Graviton G1: at 1 M msgs/s of 288 B, p50 4,973 µs, max 9,175 (8,544–102,826) µs (four settings differ)
4,096-entry receive ringthe NIC’s receive queue holds a burst while the reader catches upused with busy polling in the x86 rows below; not A/B’d alone here

Source: BENCHMARKS.md, recommended configuration.

Why bypass is faster, and what it costs

No interrupt, no system call, no scheduler wake-up on the data path. In every pair below DPDK’s median was lower: by 4.96–6.94 µs idle and by 8.64–25.47 µs at 1 M msgs/s of 288 B.

costwhat it means
coreseach network thread spins at 100% on a core of its own, idle or not, plus a core for housekeeping (layout)
NICstwo extra NICs, taken from the kernel: no ping, tcpdump, firewall or kernel counters on them
memory2 MiB hugepages for packet buffers, about 37 MiB a NIC at the defaults
setupnode-level, as root: each NIC bound to a user-space driver, hugepages reserved (configuration)
operationsyou own NIC resets, restarts, upgrades and the C in native/dpdk/
same hostno gain: host-local channels still use kernel sockets (below)

Which transport should I use?

situationuse
both ends on one hostkernel UDP, or Aeron IPC (shared memory, no network); DPDK gives no bypass there
between hosts, Gravitonkernel UDP, moderation off, the NIC’s interrupts on a core of their own: the best kernel path measured (no lossy rep at 1 M msgs/s in G1 and G3, one in G2)
between hosts, x86kernel UDP. No kernel setting is recommended on x86: busy polling with a 4,096-entry ring was the best kernel arm tried, but it changes two settings at once, and the Graviton settings are not measured on x86. On c7i.4xlarge at 1 M msgs/s that arm’s max was 6,664 µs, with 4 NAKs and 849 short sends over 5 reps
between hosts, the kernel’s median or tail is not good enoughkernel bypass with Aeron Premium
learning how a transport binding works, or reproducing these numbersthis crate, in a lab

Measured: between two hosts

Aeron 1.53.3 ping-pong between two hosts in one placement group, isolated CPUs, 5 reps. Idle: 32 B, one message in flight. Load: 288 B at 1 M msgs/s, each latency counted from when the message should have been sent. p50 is the median of the 5 reps; max is the largest single round trip in any rep. µs. Each platform’s two rows are one run on one pair: compare within a platform only.

platform, roundtransportidle: p50 / max1 M msgs/s: p50 / maxlossy reps at 1 M msgs/s
Graviton5 c9g.2xlarge, G3DPDK13.97 / 7919.36 / 1140 of 5
kernel UDP, moderation off, interrupts on their own core18.93 / 16844.83 / 2110 of 5
x86 c7i.4xlarge, round 7DPDK21.28 / 11630.38 / 1550 of 5
kernel UDP, busy polling, 4,096 ring28.22 / 4,84643.74 / 6,6644 NAKs over 5 reps; lossy reps not in the table
x86 c7i.metal-24xl, round 4DPDK28.13 / 84534.30 / 103not in the table
kernel UDP, busy polling33.70 / 30742.94 / 1,150not in the table

Source: BENCHMARKS.md, DPDK against the best kernel arm (its max X (a–b) cells: the max here is b). Azure: no run output is committed, so no Azure figure is kept.

  • DPDK’s max was lower in every pair except bare-metal idle (845 against 307 µs).
  • Two c7i.4xlarge pairs differed by about 7 µs at the median, so the platforms are not compared here.

Localhost: no gain

A DPDK NIC cannot reach its own host. Channels on loopback or the host’s kernel interfaces run on kernel UDP sockets inside this driver, as Aeron’s kernel transport would carry them (rusteron_dpdk_kernel.c). So on one host the DPDK driver and Aeron’s C driver take the same kernel path. No committed run compares the two drivers on one host.

Next