Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions interactive/server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,6 @@ fronting proxy if a deployment ever needs one.
./target/release/ddir_server < interactive/server/demo/txn.txt
python3 interactive/server/demo/two_sessions.py # races + size gate over TCP

`load --explain` and `query` are reserved but unimplemented: explanation
support belongs on the scope-tree explanation machinery, and until that lands
the server reports an error rather than giving those commands an improvised
meaning.

## Benchmarking

The [LDBC-derived workload](bench/ldbc/README.md) runs parameterized interactive
Expand Down
166 changes: 14 additions & 152 deletions interactive/server/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! - `ok [body...]` — terminal success line
//! - `err [body...]` — terminal error line
//! - `data <fields...>` — one streamed body line (peek/tail batches)
//! - `end` — terminator after a stream of `data` lines
//! - `end` — terminator of a `tail` stream, once it is stopped
//!
//! Multi-line bodies use two-phase framing: `load ... begin` accepts literal
//! DDIR through `end-load`, while `feed <prog> <in#> begin` accepts row updates
Expand Down Expand Up @@ -33,8 +33,7 @@ pub type ConnectionId = u64;
#[derive(Debug)]
pub enum Cmd {
/// Install a dataflow.
/// `id_hint` — a client-chosen name; the server may keep it or assign
/// a fresh id (echo'd in the response).
/// `id_hint` — the client-chosen name the dataflow is installed under.
/// `bindings` — `import-name -> binding`, where the binding is either a
/// registered trace name or a builtin call (`random(...)`).
/// `program` — DDIR text, inline (`load … begin` … `end-load`) or read
Expand All @@ -48,10 +47,9 @@ pub enum Cmd {
program: String,
explain: Option<(usize, bool)>,
},
/// Drop the dataflow named by id or by `id_hint`. Fails if any
/// export of this dataflow is still imported by another live
/// dataflow or held by a reader.
Drop { target: DataflowRef },
/// Drop the named dataflow. Fails if any export of this dataflow is
/// still imported by another live dataflow or held by a reader.
Drop { name: String },
/// List held names.
List,
/// One-shot snapshot of a named trace, optionally of one key.
Expand Down Expand Up @@ -100,39 +98,12 @@ pub enum Cmd {
prog: String,
input: usize,
},
/// Push a row into the query input of a `--explain` dataflow
/// (reserved; unimplemented). Sign is `+1` for `add`, `-1` for `del`.
#[allow(dead_code)]
Query {
target: DataflowRef,
kind: QueryKind,
key: Vec<i64>,
val: Vec<i64>,
},
/// Advance ambient time by `n` (default 1).
Tick { n: u64 },
/// End the session.
Exit,
}

#[derive(Debug, Clone, Copy)]
pub enum QueryKind {
Add,
Del,
}

/// A reference to a registered dataflow, used by both `drop` and
/// `query`. Either a numeric dataflow id or a name (the load's
/// `id_hint`). Parsed by reading the token as a `u64` first, then
/// falling back to a string name. So `drop 5` and `drop my_reach`
/// both work, and `drop 5_alt` (which fails to parse as u64) falls
/// through to the name lookup.
#[derive(Debug, Clone)]
pub enum DataflowRef {
Id(u64),
Name(String),
}

/// A command ready to broadcast to the worker group. Protocol-only tail
/// lifecycle wraps the typed server command vocabulary rather than leaking
/// response channels into the dataflow.
Expand Down Expand Up @@ -172,17 +143,7 @@ pub fn prepare(command: Cmd) -> Result<PreparedCommand, String> {
program,
}
}
Cmd::Drop { target } => ServerCommand::Drop {
name: match target {
DataflowRef::Name(name) => name,
DataflowRef::Id(id) => {
return Err(format!(
"numeric dataflow id {} is no longer exposed; use its name",
id
))
}
},
},
Cmd::Drop { name } => ServerCommand::Drop { name },
Cmd::List => ServerCommand::List,
Cmd::Peek { name, key } => ServerCommand::Peek { trace: name, key },
Cmd::Tail { name } => return Ok(PreparedCommand::Tail { name }),
Expand Down Expand Up @@ -214,9 +175,6 @@ pub fn prepare(command: Cmd) -> Result<PreparedCommand, String> {
Cmd::Source { prog, input, source } => ServerCommand::Load { prog, input, source },
Cmd::Bind { trace, prog, input } => ServerCommand::Bind { trace, prog, input },
Cmd::Unbind { trace, prog, input } => ServerCommand::Unbind { trace, prog, input },
Cmd::Query { .. } => {
return Err("query is reserved for --explain dataflows and is not implemented".into())
}
Cmd::Tick { n } => ServerCommand::Tick { n },
Cmd::Exit => ServerCommand::Exit,
};
Expand Down Expand Up @@ -295,8 +253,8 @@ pub struct Request {
}

/// State carried between lines so the parser can splice a multi-line load or
/// feed body together. The parser hands back either a complete `Request` or
/// `None` (more lines required).
/// feed body together. The parser hands back a request id with its parsed
/// command (or parse error), or `None` when more lines are required.
#[derive(Default)]
pub struct LineParser {
pending_load: Option<PendingLoad>,
Expand All @@ -309,7 +267,7 @@ pub struct LineParser {
/// Tokens that introduce a command. If a line begins with one of these
/// instead of an explicit reqid, the parser synthesizes a reqid.
const COMMAND_KEYWORDS: &[&str] = &[
"load", "drop", "list", "peek", "tail", "stop", "tick", "query", "exit", "feed", "bind",
"load", "drop", "list", "peek", "tail", "stop", "tick", "exit", "feed", "bind",
"unbind",
];

Expand Down Expand Up @@ -683,63 +641,6 @@ fn parse_cmd(cmd: &str, args: &[&str]) -> ParseOutcome {
},
}
}
"query" => {
// Syntax: `query <df-id-or-name> add|del <k-fields> ; <v-fields>`
// Where k/v-fields are comma-separated i64. Empty side allowed
// (write nothing before/after the `;`).
if args.len() < 3 {
return ParseOutcome::Err(
"query: expected `<df-id-or-name> add|del <k1,k2,..> ; <v1,v2,..>`".into(),
);
}
let target = match args[0].parse::<u64>() {
Ok(n) => DataflowRef::Id(n),
Err(_) => DataflowRef::Name(args[0].to_string()),
};
let kind = match args[1] {
"add" => QueryKind::Add,
"del" => QueryKind::Del,
other => {
return ParseOutcome::Err(format!(
"query: kind must be add|del, got {:?}",
other
))
}
};
// Find the `;` separator among the remaining tokens.
let rest = &args[2..];
let sep = rest.iter().position(|t| *t == ";");
let (k_toks, v_toks): (&[&str], &[&str]) = match sep {
Some(i) => (&rest[..i], &rest[i + 1..]),
None => (rest, &[]),
};
fn parse_fields(toks: &[&str]) -> Result<Vec<i64>, String> {
let mut out = Vec::new();
for t in toks {
for piece in t.split(',') {
if piece.is_empty() {
continue;
}
out.push(piece.parse().map_err(|_| format!("bad i64 {:?}", piece))?);
}
}
Ok(out)
}
let key = match parse_fields(k_toks) {
Ok(v) => v,
Err(e) => return ParseOutcome::Err(format!("query key: {}", e)),
};
let val = match parse_fields(v_toks) {
Ok(v) => v,
Err(e) => return ParseOutcome::Err(format!("query val: {}", e)),
};
ParseOutcome::Cmd(Cmd::Query {
target,
kind,
key,
val,
})
}
"feed" => {
if let [prog, input, "begin"] = args {
let input = match input.parse() {
Expand Down Expand Up @@ -848,14 +749,8 @@ fn parse_cmd(cmd: &str, args: &[&str]) -> ParseOutcome {
_ => ParseOutcome::Err(format!("{}: expected `<trace> <prog> <in#>`", cmd)),
},
"drop" => match args {
[tok] => {
let target = match tok.parse::<u64>() {
Ok(n) => DataflowRef::Id(n),
Err(_) => DataflowRef::Name((*tok).to_string()),
};
ParseOutcome::Cmd(Cmd::Drop { target })
}
_ => ParseOutcome::Err("drop: expected `<dataflow-id-or-name>`".into()),
[name] => ParseOutcome::Cmd(Cmd::Drop { name: (*name).to_string() }),
_ => ParseOutcome::Err("drop: expected `<name>`".into()),
},
"list" => match args {
[] => ParseOutcome::Cmd(Cmd::List),
Expand Down Expand Up @@ -939,12 +834,7 @@ mod tests {
assert_eq!(got.len(), 4);
assert!(matches!(got[0].1, Ok(Cmd::List)));
assert!(matches!(got[1].1, Ok(Cmd::Tick { n: 5 })));
assert!(matches!(
got[2].1,
Ok(Cmd::Drop {
target: DataflowRef::Id(3)
})
));
assert!(matches!(got[2].1, Ok(Cmd::Drop { ref name }) if name == "3"));
assert!(matches!(got[3].1, Ok(Cmd::Peek { ref name, key: None }) if name == "foo"));
}

Expand Down Expand Up @@ -1038,27 +928,6 @@ mod tests {
assert!(matches!(&got[2].1, Err(_)));
}

#[test]
fn query_cmd() {
let mut p = LineParser::new();
let got = feed_all(&mut p, &["rQ query 3 add 1,2 ; 99"]);
assert_eq!(got.len(), 1);
match &got[0].1 {
Ok(Cmd::Query {
target,
kind,
key,
val,
}) => {
assert!(matches!(target, DataflowRef::Id(3)));
assert!(matches!(kind, QueryKind::Add));
assert_eq!(key, &vec![1, 2]);
assert_eq!(val, &vec![99]);
}
_ => panic!("expected Query, got {:?}", got[0].1),
}
}

#[test]
fn auto_reqid_for_bare_command() {
let mut p = LineParser::new();
Expand Down Expand Up @@ -1221,15 +1090,8 @@ mod tests {
],
);
assert_eq!(got.len(), 3);
assert!(matches!(
got[0].1,
Ok(Cmd::Drop {
target: DataflowRef::Id(3)
})
));
assert!(
matches!(got[1].1, Ok(Cmd::Drop { target: DataflowRef::Name(ref n) }) if n == "my_reach")
);
assert!(matches!(got[0].1, Ok(Cmd::Drop { ref name }) if name == "3"));
assert!(matches!(got[1].1, Ok(Cmd::Drop { ref name }) if name == "my_reach"));
assert!(matches!(got[2].1, Err(_)));
}
}
6 changes: 3 additions & 3 deletions interactive/server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
//! independently.
//!
//! Threads:
//! - main: spawns the worker, the TCP listener, and the stdin session;
//! then waits for the worker to finish.
//! - main: spawns the worker, the TCP and WebSocket listeners, and the stdin
//! session; then waits for the worker to finish.
//! - worker 0: admits the transport FIFO to the distributed control stream.
//! - all workers: replay the same registry + dispatch operations.
//! - per-session reader: parses lines into commands, tags each with this
Expand All @@ -27,7 +27,7 @@ mod control_loop;

use mimalloc::MiMalloc;

/// The allocator the retired example driver ran on; the arrangement-heavy paths lean on it.
/// The arrangement-heavy paths lean on mimalloc.
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;

Expand Down
4 changes: 2 additions & 2 deletions interactive/src/backend/corgi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! comparison, but its representation choices do not define corgi's physical semantics.
//!
//! All `Backend` methods are corgi-native: `linear` folds a `LinearOp` chain over each container
//! ([`apply_ops`], columnar fast paths with row-wise fallbacks); `arrange` ingests columns without
//! ([`apply_ops`]); `arrange` ingests columns without
//! a row round-trip; `join`/`reduce` run through the int-proxy tactics ([`CorgiJoinBackend`],
//! [`CorgiReduceBackend`]) over the columnar chunks.

Expand Down Expand Up @@ -335,7 +335,7 @@ impl Backend for CorgiBackend {
}

fn leave_dynamic<'s>(c: Collection<'s, Time, CC>, level: usize) -> Collection<'s, Time, CC> {
// Mirror DD's `Collection::leave_dynamic` (dynamic/mod.rs:40), but over a `CorgiContainer`:
// Mirror DD's `Collection::leave_dynamic`, but over a `CorgiContainer`:
// strip all but `level-1` PointStamp coordinates from the capability AND from each row's time
// (stored columnar in `CorgiContainer.times`, not inline in the data tuples). The input
// connection summary advertises the `retain` so timely's progress tracking stays correct.
Expand Down
8 changes: 3 additions & 5 deletions interactive/src/corgi/chunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,8 +480,6 @@ where

}

/// Concatenate chunks' columns into flat `(keys, vals, times, diffs)` with **no transcode** — for
/// reading an arrangement back column-natively (e.g. `Backend::as_collection` straight into a
/// Build a `ChunkBatch<CorgiChunk>` from corgi key/val COLUMNS directly (no transcode): sort +
/// consolidate into one chunk, then `settle`. The column-native egress the reduce backend seals its
/// output with (it resolves proxy ids to real columns by `gather` and hands them here).
Expand All @@ -494,7 +492,7 @@ where
settle_one(chunk)
}

/// Grade one chunk into a `ChunkBatch` (shared tail of `rows_to_batch`/`columns_to_batch`).
/// Grade one chunk into a `ChunkBatch`.
fn settle_one<T, R>(chunk: CorgiChunk<T, R>) -> ChunkBatch<CorgiChunk<T, R>>
where
T: ColTime,
Expand All @@ -512,7 +510,7 @@ where
/// standard `ChunkBatcher`/`ChunkBuilder`, the arrange ingest stays column-native — no
/// columns→rows→columns round-trip at the arrangement boundary.
///
/// Crucially it **accumulates to `TARGET`** before consolidating (like `ContainerChunker`), so it
/// Crucially it **accumulates to `INGEST`** before consolidating (like `ContainerChunker`), so it
/// emits few large chunks rather than one tiny chunk per input container — otherwise the columnar
/// per-chunk set-up (`gather`/`sort_perm`) dominates when input arrives as many small batches.
pub struct CorgiChunker<T, R> {
Expand Down Expand Up @@ -659,7 +657,7 @@ where
R: Semigroup + Clone + 'static,
{
type Container = CorgiChunk<T, R>;
// `extract` ships ready chunks, leaving the sub-TARGET remainder to accumulate further.
// `extract` ships ready chunks, leaving the sub-`INGEST` remainder to accumulate further.
fn extract(&mut self) -> Option<&mut Self::Container> {
self.current = self.ready.pop_front();
self.current.as_mut()
Expand Down
2 changes: 1 addition & 1 deletion interactive/src/corgi/container.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Phase 2 — the corgi-native container: corgi columns for the (key,val) payload, times as a
//! The corgi-native container: corgi columns for the (key,val) payload, times as a
//! lane column, diffs a plain Rust Vec (corgi never touches the lattice). This is what flows on
//! dataflow edges in the corgi backend; operators transform block→block via `eval_graph` with NO
//! per-op transcode. Conversion to/from DDIR rows happens only at I/O boundaries
Expand Down
10 changes: 4 additions & 6 deletions interactive/src/corgi/exchange.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,10 @@
//! # The hash
//!
//! Routing uses [`corgi::hash`] — the same structural content hash that
//! [`present_key`](crate::corgi::chunk::present_key) prepends as a key's identifier lane. Any
//! deterministic function of the key would be correct here; choosing *this* one means the
//! distributor and the arrangement agree on what a key's identifier is, so the hash a receiver
//! recomputes is the one the sender routed by. (It is also seed-free and structural, so it agrees
//! across processes and across runs, and its low bits are mixed — a raw key column would route a
//! strided identifier space onto a fraction of the workers.)
//! [`present_key`](crate::corgi::chunk::present_key) prepends as a compound key's identifier
//! lane. Any deterministic function of the key would be correct here; this one is seed-free and
//! structural, so it agrees across processes and runs, and its low bits are mixed — a raw key
//! column would route a strided identifier space onto a fraction of the workers.

use timely::communication::Push;
use timely::dataflow::channels::Message;
Expand Down
5 changes: 2 additions & 3 deletions interactive/src/corgi/join.rs
Original file line number Diff line number Diff line change
Expand Up @@ -631,8 +631,7 @@ fn stage_collision<T: ColTime>(
/// Two regimes: when one side is much smaller (the fresh delta against an accumulated
/// trace), the small side DRIVES and the large side is presented only at the driver's keys
/// (sorted probes — cost tracks the driver plus matches). When the sides are
/// comparable, probing costs `n log n` against a merge's `n`, so both sides are pulled and
/// merged symmetrically instead.
/// comparable, both sides are pulled and merged symmetrically instead.
fn advance_leaf<T: ColTime>(
chunks0: &[&CorgiChunk<T, Diff>],
chunks1: &[&CorgiChunk<T, Diff>],
Expand Down Expand Up @@ -767,7 +766,7 @@ fn leaf_probe<'a, T: ColTime>(
}

/// Comparable-sides regime: both sides pulled and merged symmetrically on the `u64`
/// buffers — a probe here would cost `n log n` against this merge's `n`.
/// buffers.
fn leaf_merge<'a, T: ColTime>(
mut views0: Vec<LeafView<'a, T>>,
mut views1: Vec<LeafView<'a, T>>,
Expand Down
Loading
Loading