· via dev.to (home feed)
rekuiper 0.500 moves its hot path to RAM and pins exact single-core limits up to 200k msg/s
A dev.to writeup details how the Rust stream engine rekuiper removed disk I/O from its hot path and measured exact single-core MQTT ceilings, hitting 200,000 msg/s on windowing workloads.

What happened
rekuiper, a Rust reimplementation of LF Edge eKuiper aimed at edge gateways and IoT hubs, has shipped version 0.500-beta with an engine that never touches the filesystem while processing messages. According to a dev.to post by ankurpaan, the accompanying benchmark campaign located the exact single-core throughput limit of five MQTT workloads — some as high as 200,000 messages per second — using a search that narrows results to 1,000 msg/s increments.
The post frames this as closing a gap left by earlier tests, in which rekuiper held 100,000 msg/s on one pinned core but dropped packets when offered 200,000. Everything between those two points was unknown territory, and the CPU was not even saturated on every workload that failed.
Disk I/O, not stream math, set the limit
When the developers profiled the engine under burst load, they found the choke point was neither parsing nor window arithmetic. The stalls came from metadata access: checking SQLite tables for stream and rule definitions, resolving authentication keys, reading configuration files, and persisting rule-state changes. On a developer machine's NVMe drive such lookups cost microseconds. On an industrial gateway with eMMC or microSD storage, batches of flash writes create I/O waits that stall the Tokio runtime thread.
Backpressure compounds this quickly. The post notes that a 10-millisecond stall at 150k msg/s produces a backlog of roughly 1,500 messages, and under QoS 0 the Mosquitto broker drops those packets outright. To push past 100k msg/s on one core, the hot path had to stop touching the disk entirely.
What changed in v0.500
The release separates metadata reads from metadata persistence. A new MemoryCatalog hydrates all streams, tables, and active rules from SQLite once at startup, then serves lookups from in-memory hash maps behind RwLocks — no system calls and no disk I/O during execution. Rule mutations update memory first and commit to SQLite asynchronously in the background.
Beyond the catalog, the team cached the RSA public keys used for JWT verification so ingest no longer reads key files per request, cached configuration and schema data in RAM on first access, added shared connection pools for SQL and database sinks, and switched relational sinks (PostgreSQL and SQLite) to parameterized multi-row INSERT statements instead of one query per record. Internal actor queue depths grew from 1,024 to 32,768 records, giving the engine headroom to absorb OS scheduling jitter without propagating backpressure into the MQTT network loop.
How the ceilings were measured
Rather than testing round numbers, the team ran a hierarchical ladder: coarse 10,000 msg/s steps to bracket the ceiling, then 2,500 msg/s steps, then 1,000 msg/s steps to pinpoint the tipping point.
The rig matches earlier benchmarks. A 12-core x86-64 host runs Docker on WSL2 (cgroup v2), with the engine container pinned to one CPU core, capped at 1 GiB of RAM with equal swap, and set to a single Tokio worker thread. Mosquitto runs in an isolated container on separate cores with a strict 4,096-message / 1 MiB outgoing queue, so any lag translates immediately into dropped QoS 0 packets. A standalone Rust load generator (mqttgen) pushes MQTT 3.1.1 traffic across eight connections, and a message-by-message sink validation checks counts, unique IDs, and per-device aggregates.
The results
| Workload | Ceiling | First failure | Limiting factor | RAM |
|---|---|---|---|---|
| Telemetry filter (1,000 devices) | 150,000 msg/s | 151,000 msg/s | CPU at 99.4%, broker drops 20.5% | 17.4 MB |
| Per-device 10s windows (1,000 devices) | 200,000 msg/s | 210,000 msg/s | Generator off schedule; engine lossless to 240k | 6.7 MB |
| ESPHome topics (10,000 topics) | 150,000 msg/s | 151,000 msg/s | CPU at 99.3%, broker drops 5.0% | 16.6 MB |
| Vehicle windows (10,000 VIN topics) | 200,000 msg/s | 210,000 msg/s | Generator off schedule; engine lossless to 220k | 18.1 MB |
| EV charger sessions | 126,000 msg/s | 127,000 msg/s | Session drain lag of 16s vs a 5s stability limit | 6.0 MB |
Two workloads hit the load generator's own scheduling limits rather than the engine's. In the per-device windowing case, the engine processed everything without loss up to 240,000 msg/s, with the pipeline collapsing at 250,000; the developers describe 200,000 as the certified on-schedule ceiling. Windowing also kept memory small — 6.7 MB — because aggregations such as count, average, and max update in place rather than buffering raw rows.
Why it matters
Edge gateways and Raspberry Pi-class hardware live with slow flash and few cores, and this writeup argues that on such machines the physical ceiling for stream processing is set by metadata I/O, not by parsing or window math. Moving catalogs and key material into RAM while keeping durability through asynchronous write-back proved to be the difference between roughly 100k and 150k–200k msg/s on a single core, at memory footprints small enough for constrained devices. The methodology also stands out: a strictly bounded broker and per-message validation produce ceiling numbers that capacity planners can actually trust. The usual caveats apply — these are self-reported benchmarks of beta software on one specific rig — but the design lesson about keeping hot paths off flash transfers well beyond this project.
- #rust
- #iot
- #mqtt
- #stream-processing
- #edge-computing
- #benchmarks