DEV Community

Ankur Kumar Pandey
Ankur Kumar Pandey

Posted on

We rewrote LF Edge eKuiper in Rust: 425k eps, 8MB RAM, 13ms boot (vs Flink, Go eKuiper & Benthos)

Over at I-Dacs Labs, we run high-throughput telemetry pipelines on edge devices (Raspberry Pis, Advantech gateways, and embedded x86/ARM boxes). We've been using LF Edge eKuiper for local stream processing (SQL filtering, sliding windows, and MQTT/Kafka sinks), but kept hitting the classic edge computing wall:

  1. JVM engines (Apache Flink): Incredible throughput, but require >1 GB RAM and take 20+ seconds to boot. Unusable on small industrial hardware.
  2. Go engines (Upstream eKuiper, Benthos, Telegraf): Much lighter, but continuous Stop-The-World GC sweeps introduced tail latency jitter. Worse, under burst sensor loads (10k–100k events/sec), Go channel buffer saturation led to silent packet loss. We decided to rewrite the entire engine in pure Rust: rekuiper (v0.421-beta, dual licensed under MIT / Apache-2.0). --- ### What We Built
  3. Core Architecture: Lock-free stream bus (StreamBus), Tokio async actors for rule execution, and bounded actor queues for sinks with zero runtime GC pauses.
  4. 100% Drop-In Parity: Fully compatible with the existing eKuiper Manager Web UI, OpenAPI 3.0 schemas, and standard streaming SQL. Zero scaffolded stubs across all 98 REST endpoints.
  5. Footprint: 9.60 MB stripped static binary, ~6 – 8.2 MB idle RAM consumption.

- Sub-15ms Cold Boot: 12.5 – 14.5 ms internal daemon bootstrap; 123 ms end-to-end process-spawn-to-ready.

Empirical Head-to-Head Benchmarks (500,000 Records)

Rather than hand-waving estimates, we ran all engines head-to-head on the exact same Linux machine (WSL2 / Ubuntu x86_64) using an identical 500,000-record telemetry workload:
Pipeline: Parse 500k JSON events → Compute formula (temp * 1.8 + 32) → Filter (temp > 20.0) → Project (id, temp_f) → Sink

Engine Runtime 500k Elapsed Throughput Data Drops Memory
rekuiper (0.421) Pure Rust 1.176 s 425,308 eps 0 (0.0%) ~8 MB
Apache Flink Java / JVM 2.144 s 233,209 eps 0 (0.0%) ~1,022 MB
Telegraf Go 8.194 s 61,019 eps 0 (0.0%) ~50 MB
Upstream Go eKuiper Go 11.290 s 44,287 eps 72,921 (14.6%) ~45 MB
Redpanda Connect Go 19.236 s 25,993 eps 0 (0.0%) ~38 MB

Key Observations:

  1. Channel Saturation in Go: Under sustained 500k burst ingestion, upstream Go eKuiper dropped 72,921 records (14.6% data loss) due to channel saturation (buffer full, drop message). rekuiper processed all 500,000 events with 0 drops in 1.176s (9.6x faster).
  2. vs Apache Flink: Flink’s execution graph is fast (233k eps), but the JobManager + TaskManager JVM consumed over 1 GB of RAM. rekuiper beats it in single-core throughput while consuming 125x less RAM (< 8.2 MB).

3. Cold Boot Time: rekuiper boots internally in ~13 ms (123 ms OS spawn to socket ready), compared to 1.2s for Go eKuiper and 20s for Apache Flink.

Reproduce It in 2 Minutes

All reproduction scripts and Docker configs are in the repository. Anyone can run the whole suite:

git clone https://github.com/ankur-paan/rekuiper.git
cd rekuiper
./test/benchmark/run_all.sh
Enter fullscreen mode Exit fullscreen mode

Top comments (3)

Collapse
 
aman_social profile image
Aman

425k eps with an 8MB idle footprint on edge hardware is incredible work. Go’s GC stop-the-world jitter and channel buffer dropouts have burned so many industrial IoT pipelines, so moving to a lock-free StreamBus in Rust is the exact right answer.

Question regarding your backpressure design: under sustained burst loads where downstream sinks (like a flaky cellular MQTT connection or slow Kafka broker) stall, how does your Tokio actor queue handle backpressure without unbounded heap allocation? Are you dropping with a deterministic ring-buffer strategy, or propagating pushback upstream to throttle the ingestion source?

Also, are you planning to open-source the benchmark suite comparing against Benthos and eKuiper? Would love to run this on our embedded ARM gateway clusters!

Collapse
 
ankurpaan profile image
Ankur Kumar Pandey • Edited

Thanks! Flaky cellular sinks on edge hardware were actually our main headache that started this rewrite.

On backpressure, it’s a two-stage approach:

Upstream pushback first: Sinks use bounded Tokio mpsc channels. When an MQTT connection or Kafka broker stalls, the worker stops consuming, and the rule evaluator awaits sink.send().await. This suspends the evaluator task and pauses socket reads, pushing TCP backpressure upstream to the sender.
Deterministic ring buffer fallback: The internal StreamBus uses fixed-capacity ring buffers (tokio::broadcast). If a UDP or burst source keeps blasting data anyway, memory stays strictly capped at O(1)—we yield Lagged(n) and drop stale frames rather than letting the heap balloon and OOM-killing the gateway.
And yes, the full benchmark suite is already open-sourced in the repo under test/benchmark/ (scripts for rekuiper, eKuiper, Benthos, Telegraf, and Flink).

Would love to see how it runs on your ARM cluster—if you get around to testing it, drop your numbers in an issue or PR!
This is still in beta as we are trying to cover all use cases for general stable release and would love to incorporate any suggestion if you/your team comes up with.

Collapse
 
aman_social profile image
Aman

That two-stage backpressure strategy is textbook perfection for edge hardware. Using cooperative sink.send().await for TCP window pushback while falling back to tokio::broadcast with Lagged(n) strictly guarantees O(1) heap bounds so the kernel OOM-killer never takes down the node.

One quick thought/suggestion for the beta: when Lagged(n) does trigger on UDP/uncooperative bursts, do you expose an internal drop counter metric (e.g., via a Prometheus endpoint like rekuiper_dropped_events_total tagged by stream/rule)? In industrial/SCADA setups, having deterministic drop auditability is huge for compliance when downstream networks degrade.

Awesome that the benchmark suite is already under test/benchmark/—grabbing the repo now to clone and star. Will run this against our ARM setup and definitely open an issue/PR with the telemetry profile. Excited to see this hit 1.0!