ff_engine/partition_router.rs
1//! Partition-aware dispatch for cross-partition operations.
2//!
3//! The PartitionRouter resolves an ExecutionId to its partition and provides
4//! key contexts for Valkey operations. For Phase 1, a single ferriskey::Client
5//! connection is shared across all partitions (the client handles cluster routing
6//! internally via hash tags).
7
8use ff_core::keys::{ExecKeyContext, IndexKeys};
9use ff_core::partition::{
10 Partition, PartitionConfig, PartitionFamily, execution_partition,
11};
12use ff_core::types::ExecutionId;
13
14/// Routes execution operations to the correct partition.
15///
16/// In a Valkey Cluster deployment, the ferriskey client handles slot-level
17/// routing transparently — all keys for a partition share the same `{p:N}`
18/// hash tag, so they land on the same shard. The router's job is partition
19/// computation and key context construction, not connection selection.
20pub struct PartitionRouter {
21 config: PartitionConfig,
22}
23
24impl PartitionRouter {
25 pub fn new(config: PartitionConfig) -> Self {
26 Self { config }
27 }
28
29 /// Resolve an execution ID to its partition.
30 pub fn partition_for(&self, eid: &ExecutionId) -> Partition {
31 execution_partition(eid, &self.config)
32 }
33
34 /// Build an ExecKeyContext for the given execution.
35 pub fn exec_keys(&self, eid: &ExecutionId) -> ExecKeyContext {
36 let partition = self.partition_for(eid);
37 ExecKeyContext::new(&partition, eid)
38 }
39
40 /// Build IndexKeys for a given partition index.
41 pub fn index_keys(&self, partition_index: u16) -> IndexKeys {
42 let partition = Partition {
43 family: PartitionFamily::Execution,
44 index: partition_index,
45 };
46 IndexKeys::new(&partition)
47 }
48
49 /// The partition config.
50 pub fn config(&self) -> &PartitionConfig {
51 &self.config
52 }
53
54 /// Total number of flow partitions.
55 ///
56 /// Post-RFC-011: exec keys co-locate with their parent flow's partition
57 /// under hash-tag routing, so this count governs exec routing too.
58 /// There is no separate `num_execution_partitions`.
59 pub fn num_flow_partitions(&self) -> u16 {
60 self.config.num_flow_partitions
61 }
62}
63
64// ── Cluster 4 relocation (PR-7b) ────────────────────────────────────
65//
66// The pre-PR-7b `dispatch_dependency_resolution` +
67// `dispatch_via_postgres` free functions + `is_child_skipped_result`
68// helper lived here. They have been moved behind
69// `EngineBackend::cascade_completion`:
70//
71// - Valkey: `ff_backend_valkey::cascade::run_cascade`
72// - Postgres: `PostgresBackend::cascade_completion` →
73// `ff_backend_postgres::dispatch::dispatch_completion`
74//
75// The single caller, `completion_listener::spawn_dispatch_loop`, now
76// trait-routes via `Arc<dyn EngineBackend>`.
77