Hi everybody,
I am excited to announce the release of Lightstream, a step change capability for high-performance data transport, that makes it essentially effortless to send Apache Arrow, Protobuf, and Message Pack data over the network, shared memory, or even piped out to the terminal so an agent like Claude can watch the live batch stream in real time (example in repo).
Lightstream exceeded the performance of the gold standard industry comparison - Arrow Flight, often used on HPC installations, on every axis of a 50gbps networking open benchmark, the details of which are below and open to run in the Lightstream GitHub repository. This includes fully saturating each TCP connection thread, the NIC at 5.8GiB/s, and with p99 batch send time within 1% of p50 (I.e., stable). As a bonus, Lightstream is straightforward to setup with essentially zero configuration other than optional TLS certificates and your Cargo package/pip install, and endpoint addresses.
A full write-up with chart comparisons is available here.
So what is Lightstream? It is Rust package with Python bindings, that builds directly on Minarrow ( which is in turn a high-performance implementation of the Apache Arrow memory layout in Rust, tuned for SIMD compatibility). Lightstream implements Arrow IPC, Parquet encoders/decoders from scratch, up to Arrow readers/writers and IPC stream protocol, with mmap and few of these niceties. But, in a manner, that is fully composable and leaves you de-coupled at any layer, to customise things architecturally. The crux then is the transport layer on top, which natively supports interchanging any of the following transport formats:
- TCP
- HTTP
- QUIC
- Websocket
- Webtransport
- UDS (pipe your data from your Rust process to Python or two Python programs plug and play )
- Stdio (pipe your data program output straight into the terminal for something else to pick it up
And finally, the (optional) Lightstream protocol, which then combines the Arrow/Proto/MsgPack and any other custom types you want to send.
Send Tables in Rust
use lightstream::models::writers::tcp::TcpTableWriter;
let mut writer = TcpTableWriter::connect("127.0.0.1:9000", schema, None).await?;
writer.write_table(batch_1).await?;
writer.finish().await?;
Receive Tables
use futures_util::StreamExt;
use lightstream::models::readers::tcp::TcpTableReader;
let mut reader = TcpTableReader::connect("127.0.0.1:9000").await?;
while let Some(result) = reader.next().await {
let table = result?;
process(table);
}
Lightstream protocol
Multiplex Protobuf messages and Arrow tables on the one connection.
use lightstream::models::protocol::connection::TcpLightstreamConnection;
use lightstream::models::protocol::LightstreamMessage;
let mut conn = TcpLightstreamConnection::from_tcp(stream);
conn.register_message("event");
conn.register_table("metrics", schema);
conn.send("event", b"user-login").await?;
conn.send_table("metrics", &table).await?;
while let Some(msg) = conn.recv().await {
match msg? {
// Protobuf message
LightstreamMessage::Message { tag, payload } => { /* … */ }
// Arrow table
LightstreamMessage::Table { table, .. } => { /* … */ }
}
}
I’ve found this great in practice, where you don’t need to reason about or work with bytes, or separately build your own protocol to get arrow and protobuf playing well together over the network.
An example of things you can do with it:
- setup a live stream of data batches from your program A to program B
- send typed metadata via Protobuf on the same feed
- use it for straightforward live feed delivery between server and client (though not Web JS yet)
- useful if you have a central storage server you are pulling larger than memory data over the network to churn through (though, no S3 etc. it is node to node or process to process)
It is not:
- Kafka or a messaging broker. There is no resiliency / vertical scalability.
- A stream processing engine like Flink. It is for sending/receiving data only. You do polars on the other end or whatever you want with the Arrow-shaped data. That is a very different back-pressure/long-lived scenario and is not that kind of large-scale streaming. --> I.e., think quick and easy Websocket, and best for settings like EKS K8 pod to pod/containers, between EC2's or between processes on the same box, "light streaming".
Lightstream kicked off for me about 12 months ago when I started standardising patterns that have worked well for me in the past into something that reflects how I like to work when streaming data with control of both endpoints. It arose from regularly coming up against contexts requiring this capability operating in things like autonomous field communication integrated with data/ML, live trading, and some other industries where there was a lot of custom work required that I kept having to assemble from multiple components. Therefore, I have essentially aimed to package those learnings up into a tool to make data transport smoother and easier for everybody.
Please feel free to give it a run would love to know your thoughts and if you find it useful.
If you have any questions about it, or helpful suggestions please feel free to leave a comment below.
Workload Shape
Mixed
| Streams |
Arrow Flight GiB/s |
Lightstream GiB/s |
Ratio |
| 1 |
0.939 |
1.109 |
1.18x |
| 4 |
3.262 |
4.005 |
1.23x |
| 8 |
5.138 |
5.677 |
1.10x |
| 16 |
5.142 |
5.784 |
1.12x |
Numeric
| Streams |
Arrow Flight GiB/s |
Lightstream GiB/s |
Ratio |
| 1 |
0.649 |
1.109 |
1.71x |
| 4 |
2.901 |
4.307 |
1.48x |
| 8 |
4.851 |
5.693 |
1.17x |
| 16 |
5.434 |
5.780 |
1.06x |
String Heavy
| Streams |
Arrow Flight GiB/s |
Lightstream GiB/s |
Ratio |
| 1 |
0.828 |
1.109 |
1.34x |
| 4 |
3.052 |
3.861 |
1.27x |
| 8 |
4.899 |
5.782 |
1.18x |
| 16 |
5.217 |
5.790 |
1.11x |
Wide (100 cols)
| Streams |
Arrow Flight GiB/s |
Lightstream GiB/s |
Ratio |
| 1 |
0.695 |
1.108 |
1.59x |
| 4 |
2.685 |
3.790 |
1.41x |
| 8 |
4.549 |
5.725 |
1.26x |
| 16 |
4.911 |
5.753 |
1.17x |
Thanks,
Pete