Sign inSign up

nitecon/lance

By nitecon

•Updated 4 months ago

Log-based Asynchronous Networked Compute Engine

Image
Message queues
Machine learning & AI
Databases & storage
0

10K+

nitecon/lance repository overview

⁠LANCE

Log-based Asynchronous Networked Compute Engine

Rust License Docker Docker Pulls

A high-performance, non-blocking stream engine designed to replace Kafka-heavy workloads with zero-copy efficiency and deterministic memory management.


⁠The Modern Streaming Platform for Cloud-Native Infrastructure

LANCE is built for the realities of modern compute platforms. Whether you're running on Kubernetes, bare metal, or hybrid environments, LANCE delivers:

⁠☸️ Kubernetes-Native by Design
  • Ephemeral-friendly — Stateless server design with client-side offset tracking keeps brokers simple
  • Built-in tee forwarding — Fan-out streams to multiple downstream services without application logic
  • Graceful drain support — Clean shutdown integration with K8s preStop hooks and readiness probes
  • Horizontal scaling — Consumer groups with automatic partition rebalancing as pods scale
⁠⚡ Engineered for Raw Performance
  • io_uring native — Bypass syscall overhead with Linux's fastest async I/O interface
  • Zero-copy reads — Arc-based mmap sharing delivers data directly from page cache to network
  • NUMA-aware — Pin threads and allocate memory local to I/O devices for minimal latency
  • Deterministic latency — No GC, no allocations on hot path, sub-microsecond P99
⁠🧠 Smart Clients, Less Server Load

LANCE clients aren't dumb pipes—they're intelligent participants:

  • Grouped mode — Automatic partition assignment and rebalancing across consumer instances
  • Standalone mode — Direct offset control for batch jobs, replay scenarios, and CLI tools
  • Client-side batching — Aggregate records locally before transmission, reducing round-trips
  • Backpressure-aware — Clients respond to server signals, preventing cascade failures

The result? Your servers do less work while clients self-organize for optimal throughput.


⁠Why LANCE?

Modern stream processing systems face a fundamental tension: throughput vs. latency. Kafka and its alternatives make trade-offs that become bottlenecks at scale:

SystemLanguageAllocation ModelI/O ModelLatency Profile
KafkaJVMGC-managedepoll + threadsP99 spikes during GC
RedpandaC++Manualio_uringGood, but C++ complexity
NATSGoGC-managedgoroutinesP99 spikes during GC
LANCERustZero-copy poolsio_uringDeterministic

LANCE is designed from the ground up for 100Gbps sustained ingestion with sub-microsecond P99 latency.


⁠Key Design Principles

⁠🚫 No Allocations on Hot Path

Every allocation is a potential latency spike. LANCE pre-allocates all buffers at startup and uses a Loaner Pattern for buffer recycling:

Free Pool → Ingestion Actor → io_uring Poller → Free Pool

No malloc, no GC, no surprises.

⁠🔒 Lock-Free Data Plane

The data plane uses zero locks:

  • Atomic counters with Ordering::Relaxed
  • Lock-free SPSC/MPMC queues (ringbuf, crossbeam)
  • Ownership transfer instead of shared state
⁠⚡ io_uring Native

LANCE bypasses the traditional read/write syscall overhead:

  • Submission Queue: Batch operations to kernel
  • Completion Queue: Async notification of I/O completion
  • O_DIRECT: Bypass page cache for predictable latency
  • Registered Buffers: Zero-copy between kernel and userspace
⁠🧠 NUMA-Aware

At 100Gbps, crossing the QPI/UPI link between CPU sockets adds 30-50ns per access. LANCE:

  • Discovers NIC and NVMe NUMA topology at startup
  • Pins threads to cores on the same NUMA node as their I/O devices
  • Allocates buffers with mbind() for NUMA-local memory
  • Steers NIC IRQs to match Network Actor cores

⁠Architecture Overview

┌─────────────────────────────────────────────────────────────────────────────┐
│                              LANCE NODE                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   ┌─────────────┐     ┌─────────────┐     ┌─────────────┐                   │
│   │   Network   │────►│  Ingestion  │────►│  io_uring   │                   │
│   │   Actor     │     │   Actor     │     │   Poller    │                   │
│   │  (tokio)    │     │  (pinned)   │     │  (pinned)   │                   │
│   └─────────────┘     └─────────────┘     └──────┬──────┘                   │
│         │                                        │                          │
│         │ LWP Protocol                           │ O_DIRECT                 │
│         │ (44-byte frames)                       │ (io_uring)               │
│         ▼                                        ▼                          │
│   ┌─────────────┐                         ┌─────────────┐                   │
│   │   Clients   │                         │   Segment   │                   │
│   │  (TCP/TLS)  │                         │   Files     │                   │
│   └─────────────┘                         │  (.lnc)     │                   │
│                                           └─────────────┘                   │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
⁠Components
ComponentResponsibilityThread Model
Network ActorLWP protocol parsing, CRC validationtokio async
Ingestion ActorTLV encoding, SortKey assignment, batch sortingDedicated, pinned
io_uring PollerKernel I/O submission/completionDedicated, pinned, never parks
Replication ActorL1/L2 replication, quorum managementtokio async

⁠Storage Format

⁠Segment Files (.lnc)

Immutable, append-only files containing TLV-encoded records:

┌─────────┬─────────┬─────────┬─────────┬─────────┐
│  TLV 0  │  TLV 1  │  TLV 2  │   ...   │  TLV N  │
└─────────┴─────────┴─────────┴─────────┴─────────┘

TLV Header: [Type:1][Length:4][Value:N] (5 bytes + payload)

⁠Sparse Index (.idx)

Memory-mapped index for O(1) lookups:

┌────────────────────┬────────────────────┬─────┐
│ SortKey | Offset   │ SortKey | Offset   │ ... │
│ (16 + 8 bytes)     │ (16 + 8 bytes)     │     │
└────────────────────┴────────────────────┴─────┘
⁠128-bit Sort Key

Deterministic ordering across distributed nodes:

┌──────────────────┬───────────┬───────────┬──────────┐
│  timestamp_ns    │  node_id  │ actor_id  │ sequence │
│    (64 bits)     │ (16 bits) │  (8 bits) │ (40 bits)│
└──────────────────┴───────────┴───────────┴──────────┘

⁠Replication Modes

ModeConsistencyLatencyUse Case
L1EventualLowestHigh-throughput analytics
L2Strong (M/2+1 quorum)HigherFinancial transactions

L2 mode includes Adaptive Eviction: slow followers are automatically removed from quorum to prevent tail latency poisoning.


⁠Performance Targets

MetricTargetNotes
Ingestion throughput100 GbpsSustained, not burst
P50 latency< 500 nsNetwork → Disk acknowledged
P99 latency< 5 μsNo GC, no locks
P999 latency< 50 μsDeterministic, not probabilistic

Below is a local performance benchmark run on a 16-core AMD Ryzen 9 7950X3D with 32gb of ram. You can run this yourself with:

./scripts/run-docker-tests.sh --local --benchmark
⁠LANCE Performance Benchmark Results
========================================
LANCE Performance Benchmark Results
========================================
Date: Wed Feb  4 09:18:46 PM EST 2026
Kernel: 6.14.0-37-generic
CPU: AMD Ryzen 9 7950X3D 16-Core Processor
Memory: 29Gi

=== io_uring Support ===
[21:18:46] Checking kernel TEE/splice support...
[21:18:46]   Kernel version: 6.14.0-37-generic
[21:18:46]   ✓ Kernel supports io_uring TEE operations
[21:18:46]   ✓ io_uring is enabled
TEE: Supported

=== Cargo Benchmarks ===

=== IO Backend Benchmarks ===
    Finished `release` profile [optimized] target(s) in 0.06s
     Running unittests src/lib.rs (target/release/deps/lnc_io-c112fd2f8fad787d)

running 6 tests
test backend::tests::test_io_backend_type_clone ... ok
test backend::tests::test_io_backend_type_debug ... ok
test backend::tests::test_io_backend_type_default ... ok
test backend::tests::test_io_backend_type_equality ... ok
test backend::tests::test_probe_io_uring ... ok
test fallback::tests::test_pwritev2_backend_write_read ... ok

test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 34 filtered out; finished in 0.00s


=== Priority Queue Benchmarks ===
    Finished `release` profile [optimized] target(s) in 0.06s
     Running unittests src/lib.rs (target/release/deps/lnc_io-c112fd2f8fad787d)

running 5 tests
test priority::tests::test_priority_preempts ... ok
test priority::tests::test_priority_ordering ... ok
test priority::tests::test_priority_queue_strict ... ok
test priority::tests::test_priority_queue_weighted ... ok
test priority::tests::test_priority_stats ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 35 filtered out; finished in 0.00s


=== Forward Config Benchmarks ===
    Finished `release` profile [optimized] target(s) in 0.06s
     Running unittests src/lib.rs (target/release/deps/lnc_replication-91dcf25c49e61c4d)

running 15 tests
test forward::tests::test_forward_config_default ... ok
test forward::tests::test_acquire_fails_without_leader ... ok
test forward::tests::test_forward_config_pool_size_bounds ... ok
test forward::tests::test_concurrent_leader_change_safety ... ok
test forward::tests::test_forward_config_with_tee ... ok
test forward::tests::test_concurrent_leader_addr_access ... ok
test forward::tests::test_forward_error_display_coverage ... ok
test forward::tests::test_local_write_error_display ... ok
test forward::tests::test_pool_tee_status_methods ... ok
test forward::tests::test_pooled_connection_pool_accessor ... ok
test forward::tests::test_pool_leader_unknown ... ok
test forward::tests::test_tee_forwarding_status_disabled ... ok
test forward::tests::test_noop_local_processor ... ok
test forward::tests::test_tee_forwarding_status_enum ... ok
test forward::tests::test_pool_leader_change ... ok

test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 70 filtered out; finished in 0.00s


=== TEE vs Splice Performance Benchmark ===
    Finished `release` profile [optimized] target(s) in 0.06s
     Running unittests src/lib.rs (target/release/deps/lnc_io-c112fd2f8fad787d)

running 1 test

=== TEE vs Splice Performance Benchmark ===
TEE Support: Supported
Splice Support: Supported

Forwarder Status:
  Splice Forwarder: created
  TEE Forwarder: created
  TEE fully supported: true

--- Pipe Creation Benchmark ---
Pipe create+close: 3619 ns/op (276319 ops/sec)

--- Memory Overhead ---
SpliceForwarder size: 280 bytes
TeeForwarder size: 280 bytes
SplicePipe size: 8 bytes

--- Operation Tracking ---
Splice pending ops: 0
TEE pending ops: 0

--- Performance Summary ---
TEE forwarding duplicates data in-kernel without userspace copies.
For L2 sync quorum: forward to leader + local ACK = 2 destinations, 0 copies.
Standard forwarding would require: read() + write() + write() = 2 copies.

Payload       Std (2 copies)    TEE (0 copy)      Savings
----------------------------------------------------------
1024B                6144 ns          500 ns       91.9%
4096B               24576 ns          500 ns       98.0%
16384B              98304 ns          500 ns       99.5%
65536B             393216 ns          500 ns       99.9%

Note: Actual performance depends on kernel version, CPU, and memory bandwidth.
Run integration tests with --benchmark for end-to-end latency measurements.

test uring::tests::benchmark_tee_vs_splice_performance ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 39 filtered out; finished in 0.04s


=== to_vec() Overhead Benchmark (Compression None) ===
    Finished `release` profile [optimized] target(s) in 0.06s
     Running unittests src/lib.rs (target/release/deps/lnc_network-3a4f9166bd071da6)

running 1 test

=== to_vec() Overhead Benchmark (CompressionAlgorithm::None) ===
Size           Total (µs)  Per-op (ns)   Throughput
----------------------------------------------------
1024B                  16           16   63960.02 MB/s
4096B                  53           53   77022.88 MB/s
16384B                212          212   77037.01 MB/s
65536B               1083         1083   60501.88 MB/s
262144B              3774         3774   69443.39 MB/s

Note: to_vec() creates a full copy. Consider Bytes::copy_from_slice()
or Cow<[u8]> if zero-copy passthrough is needed on hot path.

test compression::tests::benchmark_to_vec_overhead_none_compression ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 37 filtered out; finished in 0.01s


========================================
Benchmark Summary
========================================
Completed at: Wed Feb  4 09:18:47 PM EST 2026

⁠Getting Started

⁠Prerequisites
  • Rust: 2024 edition (1.85+) - Required for gen blocks, async closures, and Linux-specific features
  • Linux: 5.15+ (io_uring with IORING_OP_SEND_ZC, IORING_OP_TEE)
  • Hardware: NVMe SSD, 10G+ NIC recommended

Note: LANCE is Linux-only due to io_uring requirements. The client library (lnc-client) supports all platforms.

⁠Build
cargo build --release
⁠Run
# Start a single-node instance
./target/release/lance --config lance.toml

# With NUMA awareness (production)
numactl --cpunodebind=0 --membind=0 ./target/release/lance --config lance.toml
⁠Configuration
# lance.toml
[node]
id = 1
data_dir = "/var/lib/lance"

[network]
bind = "0.0.0.0:9000"
nic_pci_addr = "0000:3b:00.0"

[io]
uring_sq_depth = 4096
batch_size = 262144  # 256KB
o_direct = true

[numa]
enabled = true
nic_node = 0
nvme_node = 0

[replication]
mode = "L1"  # or "L2"
peers = ["lance-2:9000", "lance-3:9000"]

⁠Kubernetes Deployment

A production-ready Kubernetes StatefulSet configuration is available at k8s/lance-cluster.yaml⁠.

⁠Quick Start
# Create namespace
kubectl create namespace lance

# Label namespace for io_uring support (requires privileged pods)
kubectl label namespace lance pod-security.kubernetes.io/enforce=privileged

# Deploy the cluster
kubectl apply -n lance -f k8s/lance-cluster.yaml
⁠Configuration Notes

Before deploying, update the following to match your environment:

SettingLocationDescription
storageClassNamevolumeClaimTemplatesChange ceph-block to your cluster's storage class
DNS names--peers argumentUpdate lance-X.lance-headless if using a different service name
Replicasspec.replicasAdjust cluster size (update --peers accordingly)

For cleaner configuration, mount a ConfigMap with lance.toml instead of passing CLI arguments:

apiVersion: v1
kind: ConfigMap
metadata:
  name: lance-config
data:
  lance.toml: |
    [node]
    data_dir = "/var/lib/lance"
    
    [replication]
    mode = "L2"
    peers = ["lance-0.lance-headless:1993", "lance-1.lance-headless:1993", "lance-2.lance-headless:1993"]

Then mount it in your StatefulSet:

volumes:
- name: config
  configMap:
    name: lance-config
containers:
- name: lance
  command: ["/usr/local/bin/lance", "--config", "/etc/lance/lance.toml", "--node-id", "$(NODE_ID)"]
  volumeMounts:
  - name: config
    mountPath: /etc/lance
⁠Security Context

The StatefulSet requires:

  • seccompProfile: Unconfined — Required for io_uring syscalls
  • SYS_ADMIN capability — Required for io_uring ring setup
  • fsGroup: 1000 — Matches the lance user in the container

⁠Installation

⁠Pre-built Binaries

Official releases include the LANCE server (lance) and CLI client (lnc) as pre-built binaries:

# Download from GitHub Releases
curl -LO https://github.com/nitecon/lance/releases/latest/download/lance-linux-amd64.tar.gz
tar xzf lance-linux-amd64.tar.gz

# Server
./lance --config lance.toml

# CLI client
./lnc produce --topic events --data '{"event": "click"}'
./lnc consume --topic events --offset earliest
⁠Client Library

The Rust client library is available on crates.io:

cargo add lnc-client

⁠Documentation

DocumentDescription
Architecture⁠Deep-dive into system design
Coding Guidelines⁠Engineering standards and requirements
Kubernetes Deployment⁠Production StatefulSet configuration
LWP Specification⁠Lance Wire Protocol (LWP) Specification
Monitoring⁠Monitoring and Observability
Recovery⁠Recovery Procedures

⁠Contributing

We welcome contributions! Please read our Contributing Guide⁠ and Coding Guidelines⁠ before submitting PRs.

⁠Developer Setup
# Install Rust 2024 edition toolchain
rustup update
rustup component add rustfmt clippy

# Install pre-commit hooks
pip install pre-commit
pre-commit install
pre-commit install --hook-type commit-msg

# Verify everything works
cargo build
cargo test
pre-commit run --all-files

Key rules:

  • No allocations on hot path
  • No locks on data plane
  • All unsafe blocks must have // SAFETY: comments
  • Performance regression tests required for data plane changes
  • Commit messages must follow Conventional Commits⁠

⁠License

Apache License 2.0 - see LICENSE⁠ for details.


⁠Acknowledgments

LANCE draws inspiration from:

  • Redpanda⁠ - Proving C++ can achieve Kafka-like semantics with better performance
  • io_uring⁠ - Jens Axboe's revolutionary Linux I/O interface
  • LMAX Disruptor⁠ - Lock-free inter-thread messaging patterns
  • Aeron⁠ - High-performance messaging with mechanical sympathy

Built with 🦀 Rust for deterministic performance

Tag summary

Content type

Image

Digest

sha256:c57bc235d…

Size

33.4 MB

Last updated

4 months ago

docker pull nitecon/lance