Introduction
What to use
One line per module: what it is for, and the call you make.
| module | what it does | how you use it |
|---|---|---|
ergo-sbe | schema XML → Rust SBE codecs | ergo_sbe::generate_to_out_dir(...) in build.rs; then plain include!, or ergo_sbe::sbe_mod!(name) in your crate, which also needs ergo-sbe in [dependencies] (chapter) |
ergon-tune | reads the host and names what will wreck your tail latency, and the fix | call at start-up; it changes nothing (chapter) |
ergon-io | WebSocket and HTTP/TLS on one busy-spinning thread | poll() from your loop; epoll or io_uring is configuration (chapter) |
ergo-aeron-cluster | Aeron Cluster client | only if you need cluster; experimental, not a production substitute (chapter) |
ergon-md-dpdk | DPDK transport for the Aeron media driver | Linux x86_64 and aarch64; proof of concept — use Aeron Premium for kernel bypass (chapter) |
Kubernetes, if you deploy there. Tuning a node and pod for HFT: Kubernetes HFT tuning — start there.
| module | what it does | how you use it |
|---|---|---|
k8s/nri-shared-cpus | gives a pod its exclusive CPUs and the node’s shared housekeeping CPUs, and starts every thread on the shared ones so the busy-spin loop keeps its core | helm install ergon-shared-cpus oci://ghcr.io/mimran1980/charts/ergon-shared-cpus --version 0.1.0. Image ghcr.io/mimran1980/ergon-shared-cpus:0.1.0. Prepare the node first. Sample: k8s/nri-shared-cpus/examples/try (chapter) |
k8s/nri-latency-pods | node tuning and the hot-pod admission policy (no exec probes, no always-restart sidecars). The grants live in the shared-cpus image. This directory is the YAML and the node units | apply admission/priority-class.yaml and admission/validating-admission-policy.yaml after the chart. Pod authors set annotations and the ergon.io/latency: hot label (chapter) |
Start with ergo-sbe. Add another crate only for a need you have:
ergon-io for exchange I/O, ergon-tune to check a latency host’s settings.
Which clock to read for timestamps and durations: Clocks.
ergo-sbe generates Rust codecs with no heap allocation on the encode and
decode hot paths (allocation_count_test) from
Simple Binary Encoding schemas.
Wire layout matches official SBE for the
published profile.
Tail order is a type error, not a runtime surprise.
Quick start
[build-dependencies]
ergo-sbe = "0.1"
[dependencies]
ergo-sbe = "0.1" # only for sbe_mod! / include_sbe!
#![allow(unused)]
fn main() {
let mut buf = [0u8; HeartbeatEncoder::compute_length_with_header()];
// Buffer is exact size from const compute_length_with_header — no bounds check needed.
let len = HeartbeatEncoder::try_wrap_and_apply_header(&mut buf, 0)?
.fixed(&HeartbeatFixedFields { sequence: 7, timestamp: 0 })
.encoded_length_with_header();
let dec = HeartbeatDecoder::try_from(&buf[..len])?;
assert_eq!(dec.sequence(), 7);
}
(From book/examples/heartbeat-encode.rs — compiled against the feature-tour codec.)
Set up → · Feature tour → · Coming from sbe-tool →
| Wire order | Named stages — asks before bids does not compile |
| Exact sizing | compute_length_with_header / *EncodedLength before you write; works with Aeron try_claim |
| Groups | Closures nest like the schema; no .parent() |
| Trust boundary | try_* → Result; bare wrap panics if short; bare decode panics if short and Err on wrong template/schema; unsafe *_unchecked skips the proof |
| Composites | #[repr(transparent)] Engine([u8; N]) — the value is the on-wire bytes |
| Domain types | Map wire Decimal to rust_decimal::Decimal with one config line |
| Bulk groups | bulk_add(&[Entry]) / bulk_decode() for fixed-stride leaf groups |
| Runtime deps | Generated codecs embed sbe_rt — no ergo-sbe on the hot path |
0.x experimental. Pin versions. Exit criteria: Road to 1.0.
It is not a line-for-line port of sbe-tool. The generated API is meant to be
easier under Rust’s borrow checker, safer at untrusted boundaries, and still
official-SBE on the wire. Nested groups use scoped closures so you do not
hand encoder ownership back with .parent():
#![allow(unused)]
fn main() {
pub fn encode_sample_car(buf: &mut [u8]) -> Result<usize, sbe_rt::EncodeError> {
let mut extras = OptionalExtras::default();
extras.cruise_control(true).sports_pack(true);
// Buffer pre-sized from EncodedLength; try_* still validates extent.
let len = CarEncoder::try_wrap_and_apply_header(buf, 0)
.unwrap()
.fixed(&CarFixedFields {
serial_number: 1234,
model_year: 2013,
available: true.into(),
code: Model::A,
some_numbers: [10, 20, 30, 40],
vehicle_code: [b'A', b'B', b'C', b'D', b'E', b'F'],
extras,
engine: Engine::new(
2000,
4,
[b'1', b'2', b'3'],
0i8,
false.into(),
Booster::new(BoostType::TURBO, 210),
),
})
.fuel_figures(2, |g| {
g.add(|mut e| {
e.speed(30).mpg(35.9);
e.usage_description_as_str("Urban")
})?;
g.add(|mut e| {
e.speed(60).mpg(25.0);
e.usage_description_as_str("Highway")
})?;
Ok(())
})?
.performance_figures(1, |g| {
g.add(|mut e| {
e.octane_rating(95);
e.acceleration(2, |a| {
a.add(|x| {
x.mph(30).seconds(4.0);
Ok(())
})?;
a.add(|x| {
x.mph(60).seconds(7.5);
Ok(())
})
})
})?;
Ok(())
})?
.manufacturer_as_str("Honda")?
.model_as_str("Civic VTi")?
.activation_code_as_str("abcdef")?
.encoded_length_with_header();
Ok(len)
}
}
(Compiled and run in CI.)
Wire parity is tested by the dual-encode suite (sbe_tool_wire_parity_test,
sbe_tool_multi_schema_wire_parity_test). The benchmarks also assert byte
parity before timing: Benchmarks.
Deployment
How to set up hosts and Kubernetes for these crates on AWS and Azure, x86 and Graviton: Deployment.
Rust 1.99+. Clone setup and checks: repository README.