Introducing Wavelet

Introducing Wavelet: A Graph-Based Stream Processing Runtime

Hey Rustaceans! :waving_hand:

I've just published Wavelet, a stream processing runtime that takes a graph-based approach to reactive systems. Instead of async/await, you build processing pipelines as computation graphs with deterministic execution.

Why Graph-Based Architecture?

The computation graph isn't just a different way to organize code - it solves fundamental problems in complex data processing systems:

Incremental Computation & Consistency

The Problem: In traditional imperative code, it's easy to create data consistency bugs where you use data A to compute data B, then later mutate data A, leaving data B stale and invalid. In sufficiently sophisticated systems, these dependency chains can become nearly impossible to track manually until a bug crops up.

The Solution: Nodes automatically broadcast updates to their children when they change, ensuring dependent computations stay consistent. The graph makes data dependencies explicit and handles propagation automatically.

Zero-Copy Fanout & Reusability

The Problem: When multiple components need the same data, you often end up copying it around or creating complex shared ownership patterns.

The Solution: Child nodes can reference parent data directly through the graph relationships. One data source can efficiently fan out to multiple consumers without copying, and expensive computations get reused across multiple downstream dependencies.

Testable State Machines

The Problem: Testing complex stateful logic often requires setting up elaborate mock environments and managing intricate state transitions.

The Solution: Each node (even a full subgraph) is essentially a mini state machine with well-defined inputs and outputs. You can test individual nodes in isolation, mock their dependencies easily, and compose them into larger testable systems.

How it works

Nodes process data and control downstream propagation:

let processor = NodeBuilder::new(DataProcessor::new())
    .triggered_by(&parent_node)
    .build(&mut executor, |data, ctx| {
        data.transform();
        Control::Broadcast  // Triggers downstream nodes
    });

Multi-depth scheduling ensures nodes execute in dependency order with predictable latency. The runtime handles I/O events, timers, and cooperative scheduling automatically.

Key characteristics

  • Single-threaded by design - deterministic execution, no hidden concurrency bugs
  • Event-driven - integrates I/O, timers, and yield events seamlessly
  • Dependency injection - swap implementations (live/test/mock) while keeping graph topology, build DSLs via factories creating complete memoized subgraphs (behaviors)
  • Cross-thread channels - ring buffers for external data injection
  • Garbage collection - automatic cleanup of completed subgraphs

Sweet spot

Built for continuous stream processing:

  • Financial market data processing
  • IoT sensor analytics
  • Real-time protocol handling
  • Live media processing

Not trying to replace Tokio for request/response - different tool for different jobs.

Current state & opportunities

Wavelet is production-capable for its intended use cases, but there are exciting areas for contribution:

:rocket: High impact: Networking module (WebSocket, UDP protocols), standard library nodes, real-world examples

:microscope: Research: Multi-threading approaches, observability

The runtime handles sophisticated internals (epoch-based scheduling, cross-platform precision timing) but keeps the API straightforward.

Check it out

Would love feedback from the community, especially if you're working on streaming/reactive systems! And if any of the contribution areas sound interesting, I'd be excited to collaborate.

Built this after working with various stream processing frameworks and wanting something that felt more native to Rust's ownership model. Hope others find it useful! :crab:

8 Likes

The example in README is not very readable, like its boilerplate overshadows what is actually done.

Have you tested the crate on larger applications? What's its performance, relative to older approaches? Can the code be maintained?

I plan to add some examples going forward with an example showing Tokio single threaded vs this framework on a similar workflow. That one should show a more realistic example.

I have not yet tested this exact framework on a larger application just yet, but the architecture is not uncommon in the HFT space. This graph processing architecture has been used in production at scale, so just need to see how this specific implementation performs.

I don't know if the above point answers the maintenance question. If that question is more regarding me maintaining, then yes I will be continuing to contribute to it.

Hi @Abso1ut3Zer0,

The project looks nice, but the package size on crates is bigger than the expected size. You can add the exclude configuration to your cargo.toml and it will reduce the size. Something like this:

[package]
...
...
exclude = [
    ".github/*",
    ".idea/*",
    ".editorconfig",
    ".gitignore",
    "wavelet.png",
    "CONTRIBUTING.md",
    "README.md",
    "license",
]
1 Like

the readme and the license should be included in the crates.io upload

1 Like

Why? The crates show the README.md from your repository, it will causes more time to download your crate to user machine.

That is not true. crates.io does not display any information taken from the repository; package.repository is only used for informing the user of where to find the repository if they want to do anything with it.

4 Likes

Mine is not publishing the Readme and the documentation is always updated there: rs-mock-server

I downloaded rs-mock-server-0.6.11.crate from crates.io, and the package does contain README.md.

I wonder if readme = "README.md" in your manifest overrides the exclude entry.

2 Likes

Thanks, it is good to know

If I understood this correctly, I should then use the exclude listed above, but keep the README.md and the license?

I just added a quick and dirty comparison of tokio vs wavelet. The example isn't showcasing the extent of the framework's capabilities - just want to show some simple performance numbers.

To be clear, wavelet isn't a crate designed for the same types of workflows as tokio, although it should be able to support handling asynchronous tasks. The purpose of the framework is still to provide very generic means to create stream processing pipelines that enforce "compute once" and correct scheduling of components to support both events and derived events (think of each node as allowing you to subscribe to that node's data), all while maintaining consistent performance.

Having said that, below are some HDR histogram results. For the blocking execution mode, they are roughly the same. However, using Spin mode (trade off CPU for latency) shows a big difference. Note: all times below are in nanoseconds.

Tokio:
p50=9871
p75=10967
p95=53631
p99=100351
p999=201983

Wavlet (Block Execution Mode)
p50=9607
p75=10319
p95=19727
p99=93375
p999=125695

Wavelet (Spin Execution Mode)
p50=1495
p75=1693
p95=2241
p99=3433
p999=12607

Yes, especially to remove the image from the package

Just a few random remarks:

Strange name for what it does: I thought it was a library for signal processing. :slight_smile:
Don't hesitate to add keywords and, if applicable, categories to make what it does more obvious in the search.

The correct crate link is https://crates.io/crates/wavelet (you linked the generic site).

In your Cargo.toml file, it's recommended to put the readme field in your package definition. I also find it useful to indicate rust-version, though the 2024 edition doesn't have many versions yet.

Just published a new version that should address these.

On the name, yeah I was trying to think of a good name to match the visual for updates disseminating through the dependency graph. In some sense it is signaling but internally :).

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.