Skip to content

Commit

Permalink
docs(hydroflow_plus): add initial docs on consistency and safety
Browse files Browse the repository at this point in the history
  • Loading branch information
shadaj committed Dec 4, 2024
1 parent 0dc709e commit 595d3f9
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 1 deletion.
70 changes: 70 additions & 0 deletions docs/docs/hydroflow_plus/consistency.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
sidebar_position: 3
---

# Consistency and Safety
A key feature of Hydroflow+ is its integration with the Rust type system to highlight possible sources of inconsistent distributed behavior due to sources of non-determinism such as batching, timeouts, and message reordering. In this section, we'll walk through the consistency guarantees in Hydroflow+ and how to use the **`unsafe`** keyword as an escape hatch when introducing sources of non-determinism.

:::note

Our consistency and safety model is based on the POPL'25 paper [Flo: A Semantic Foundation for Progressive Stream Processing](https://arxiv.org/abs/2411.08274), which covers the formal details and proofs underlying this system.

:::

## Eventual Determinism
Hydroflow+ provides strong guarantees on **determinism**, the property that when provided the same inputs, the outputs of the program are always the same. Even when the inputs and outputs are streaming, we can use this property by looking at the **aggregate collection** (i.e. the result of collecting the elements of the stream into a finite collection). This makes it easy to build composable blocks of code without having to worry about runtime behavior such as batching or network delays.

Because Hydroflow+ programs can involve network delay, we guarantee **eventual determinism**: given a set of streaming inputs which have arrived, the outputs of the program (which continuously change as inputs arrive) will **eventually** have the same _aggregate_ value.

Again, by focusing on the _aggregate_ value rather than individual outputs, Hydroflow+ programs can involve concepts such as retractions (for incremental computation) while still guaranteeing determinism because the _resolved_ output (after processing retractions) will eventually be the same.

:::note

Much existing literature in distributed systems focuses on consistency levels such as "eventual consistency" which typically correspond to guarantees when reading the state of a _replicated_ object (or set of objects) at a _specific point_ in time. Hydroflow+ does not use such a consistency model internally, instead focusing on the values local to each distributed location _over time_. Concepts such as replication, however, can be layered on top of this model.

:::

## Unsafe Operations in Hydroflow+
All **safe** APIs in Hydroflow+ (the ones you can call regularly in Rust), guarantee determinism. But oftentimes it is necessary to do something non-deterministic, like generate events at a fixed time interval or split an input into arbitrarily sized batches.

Hydroflow+ offers APIs for such concepts behind an **`unsafe`** guard. This keyword is typically used to mark Rust functions that may not be memory-safe, but we reuse this in Hydroflow+ to mark non-deterministic APIs.

To call such an API, the Rust compiler will ask you to wrap the call in an `unsafe` block. It is typically good practice to also include a `// SAFETY: ...` comment to explain why the non-determinism is there.

```rust,no_run
# use hydroflow_plus::*;
# let flow = FlowBuilder::new();
# let stream_inputs = flow.process::<()>().source_iter(q!([123]));
use std::time::Duration;
unsafe {
// SAFETY: intentional non-determinism
stream_inputs
.sample_every(q!(Duration::from_secs(1)))
}.for_each(q!(|v| println!("Sample: {:?}", v)))
```

When writing a function with Hydroflow+ that involves `unsafe` code, it is important to be extra careful about whether the non-determinism is exposed externally. In some applications, a utility function may involve local non-determinism (such as sending retries), but not expose it outside the function (via deduplication).

But other utilities may expose the non-determinism, in which case they should be marked `unsafe` as well. If the function is public, Rust will require you to put a `# Safety` section in its documentation explain the non-determinism.

```rust
# use hydroflow_plus::*;
use std::fmt::Debug;
use std::time::Duration;

/// ...
///
/// # Safety
/// This function will non-deterministically print elements
/// from the stream according to a timer.
unsafe fn print_samples<T: Debug, L>(
stream: Stream<T, Process<L>, Unbounded>
) {
unsafe {
// SAFETY: documented non-determinism
stream
.sample_every(q!(Duration::from_secs(1)))
}.for_each(q!(|v| println!("Sample: {:?}", v)))
}
```
2 changes: 1 addition & 1 deletion docs/docs/hydroflow_plus/stageleft.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Stageleft
sidebar_position: 3
sidebar_position: 4
---

import StageleftDocs from '../../../stageleft/README.md'
Expand Down
7 changes: 7 additions & 0 deletions hydroflow_plus_test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,10 @@ stageleft::stageleft_no_entry_crate!();

pub mod cluster;
pub mod distributed;

#[doc(hidden)]
#[stageleft::runtime]
pub mod docs {
#[doc = include_str!("../../docs/docs/hydroflow_plus/consistency.md")]
mod consistency {}
}

0 comments on commit 595d3f9

Please sign in to comment.