Skip to main content

differential_dataflow/operators/int_proxy/
mod.rs

1//! Backend-agnostic operator tactics using integer proxies.
2//!
3//! The tactics are intended to support custom operator implementations without rebuilding
4//! the non-trivial and often non-obvious time-based logic that supports them.
5//!
6//! The tactics here run DD's operator logic over consolidated `[((u64, u64), time, diff)]`
7//! lists, the first integer a hash of the "key" and granule of independence, the second an
8//! ephemeral data identifier understood by the backend but opaque to the operator harness.
9//! The tactics first elicit proxy identifiers from the backends, perform their necessary time
10//! and difference based computations to stage integer collections, and then re-invoke the
11//! backends with those same identifiers to produce the necessary output.
12//!
13//! The backend is oblivious to the navigation of time, and the operator to the backend's
14//! implementation.
15//!
16//! # The two integers
17//!
18//! The two integers play the role of key and value to the operator, but their connection
19//! to the key and value of the intended computation is nuanced.
20//!
21//! *   `key_hash: u64` — a content hash of the intended key.
22//!     This value should be identical for each instance of identical keys.
23//!     The value is not assumed to be a well-distributed quality hash, only distinct.
24//!     There may be collisions, and the next identifier should assist with this.
25//!
26//! *   `value_id: u64` — an ephemeral intra-hash value identifier.
27//!     These values should be distinct for each distinct datum with the same key_hash.
28//!     In particular, they should account for the conventional `(key, val)` data,
29//!     rather than only the value component, to avoid errors due to colliding keys.
30//!
31//! The key hash acts as an independence marker: keys with different hashes will be isolated,
32//! and their incremental updates performed independently. By forcing the keyspace into `u64`,
33//! there is the risk of hash collision. This means that per-hash logic should be prepared for
34//! this, and should not discard key information that remains semantically important.
35//!
36//! Informally, one should mentally rewrite one's `(key, val)` into `(hash(key), (key, val))`.
37//! Retaining the `key` as data provides access to it, and allows one to certainly respond to
38//! hash collisions that can occur. Although the operator will treat `hash(key)` as the "key",
39//! the backend implementation can apply its own subseqent logic to resolve unwanted matches.
40//! As examples, join logic can follow its hash-wise cross product with key-based filtering,
41//! and reduce logic can group by keys and apply logic to slices within the per-hash list.
42//! Both are welcome to efficiently notice that there have been no collisions and optimize,
43//! or to ignore the risk entirely and live dangerously.
44
45mod history;
46
47pub mod join;
48pub mod reduce;
49
50/// Integer-only exchange medium: a consolidated collection of `[((hash, id), time, diff)]`.
51///
52/// The [`debug_assert_sorted_bridge`] method is (and can be) used to validate this property.
53pub type ProxyBridge<T, R> = Vec<((u64, u64), T, R)>;
54
55/// Debug check that a presented [`ProxyBridge`] is consolidated.
56///
57/// Operator harnesses use the test to flag backend implementations that do not uphold it.
58pub(crate) fn debug_assert_sorted_bridge<T: Ord, R>(bridge: &ProxyBridge<T, R>, who: &str) {
59    debug_assert!(
60        bridge.windows(2).all(|w| (w[0].0, &w[0].1) < (w[1].0, &w[1].1)),
61        "{}: a presented bridge must be sorted & consolidated by ((key_hash, value_id), time)",
62        who,
63    );
64}
65
66pub use join::{JoinInstance, ProxyJoinBackend, ProxyJoinTactic};
67pub use reduce::{ProxyReduceBackend, ProxyReduceTactic, ReduceInstance, ReduceWindow};