1use std::cmp::Ordering;
48use std::collections::{BTreeMap, BinaryHeap, HashMap};
49use std::io::Cursor;
50use std::sync::Arc;
51use std::time::{Duration, Instant};
52
53use arrow::array::{
54 Array, ArrayRef, Float64Array, Int64Array, RecordBatch, UInt32Array, UInt64Array,
55};
56use arrow::compute::{concat_batches, interleave, take};
57use arrow::datatypes::{DataType, Field, Schema, SchemaRef};
58use arrow::ipc::reader::StreamReader;
59use arrow::ipc::writer::StreamWriter;
60use arrow::row::{RowConverter, SortField};
61use arrow::util::display::array_value_to_string;
62use bincode::Options;
63use futures::stream::{self, BoxStream, FuturesUnordered, StreamExt};
64use mongreldb_core::{CancellationReason, ExecutionControl, RowId};
65use mongreldb_types::ids::{MetadataVersion, QueryId, TabletId};
66use parking_lot::Mutex;
67use serde::{Deserialize, Serialize};
68
69use crate::query_registry::{CancelOutcome, RegisteredSqlQuery, SqlQueryOptions, SqlQueryRegistry};
70
71pub const DEFAULT_BROADCAST_THRESHOLD_BYTES: u64 = 8 * 1024 * 1024;
74
75pub const DEFAULT_MAX_SPILL_BYTES_PER_FRAGMENT: u64 = 256 * 1024 * 1024;
79pub const MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES: usize = 64 * 1024;
81
82const COORDINATOR_OUTPUT_BATCH_ROWS: usize = 8_192;
84
85pub const TOPK_ROWID_COLUMN: &str = "__rowid";
89
90#[non_exhaustive]
92#[derive(Debug, thiserror::Error)]
93pub enum DistributedError {
94 #[error("unknown table `{0}`")]
96 UnknownTable(String),
97 #[error("table `{table}` has no tablets in its layout")]
99 EmptyLayout { table: String },
100 #[error("invalid plan: {0}")]
102 InvalidPlan(String),
103 #[error("fragment {fragment_id} resource reservation denied: {reason}")]
105 Reservation { fragment_id: u32, reason: String },
106 #[error("fragment {fragment_id} failed on worker {worker}: {message}")]
108 FragmentExecution {
109 fragment_id: u32,
110 worker: String,
111 message: String,
112 },
113 #[error("distributed query cancelled: {0:?}")]
115 Cancelled(CancellationReason),
116 #[error("unsupported in this wave: {0}")]
118 Unsupported(String),
119 #[error("arrow error: {0}")]
121 Arrow(String),
122 #[error("remote fragment transport error: {0}")]
124 RemoteTransport(String),
125 #[error("remote fragment protocol error: {0}")]
127 RemoteProtocol(String),
128}
129
130pub type DistributedResult<T> = Result<T, DistributedError>;
132
133impl From<arrow::error::ArrowError> for DistributedError {
134 fn from(error: arrow::error::ArrowError) -> Self {
135 Self::Arrow(error.to_string())
136 }
137}
138
139fn fnv1a64(bytes: &[u8]) -> u64 {
141 let mut hash = 0xcbf2_9ce4_8422_2325_u64;
142 for byte in bytes {
143 hash ^= u64::from(*byte);
144 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
145 }
146 hash
147}
148
149#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
157pub enum PartitionSpec {
158 Hash { columns: Vec<String>, buckets: u32 },
160 Range { columns: Vec<String> },
162 Tenant {
164 column: String,
165 buckets_per_tenant: u32,
166 },
167 TimeRange { column: String },
169 Unpartitioned,
171}
172
173impl PartitionSpec {
174 pub fn partition_columns(&self) -> Vec<&str> {
176 match self {
177 Self::Hash { columns, .. } | Self::Range { columns } => {
178 columns.iter().map(String::as_str).collect()
179 }
180 Self::Tenant { column, .. } | Self::TimeRange { column } => vec![column.as_str()],
181 Self::Unpartitioned => Vec::new(),
182 }
183 }
184
185 pub fn colocated_with(&self, other: &PartitionSpec) -> bool {
190 match (self, other) {
191 (Self::Unpartitioned, Self::Unpartitioned) => true,
192 (Self::Unpartitioned, _) | (_, Self::Unpartitioned) => false,
193 _ => self == other,
194 }
195 }
196}
197
198#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
200pub struct TableStats {
201 pub row_count: u64,
203 pub total_bytes: u64,
205}
206
207pub trait TabletLocator: Send + Sync {
212 fn tablets_for_table(&self, table: &str) -> DistributedResult<Vec<TabletId>>;
214 fn partitioning(&self, table: &str) -> DistributedResult<PartitionSpec>;
216}
217
218pub trait ClusterMetadata: Send + Sync {
221 fn metadata_version(&self) -> MetadataVersion;
223 fn table_stats(&self, table: &str) -> DistributedResult<TableStats>;
225}
226
227#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
233pub struct JoinKey {
234 pub left: String,
236 pub right: String,
238}
239
240#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
242pub struct SortKey {
243 pub column: String,
245 pub descending: bool,
247}
248
249#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
251pub struct AggregateExpr {
252 pub function: AggregateFunction,
254 pub column: Option<String>,
256}
257
258#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
260pub enum AggregateFunction {
261 Count,
263 Sum,
265 Min,
267 Max,
269 Avg,
271}
272
273impl AggregateFunction {
274 fn name(self) -> &'static str {
275 match self {
276 Self::Count => "count",
277 Self::Sum => "sum",
278 Self::Min => "min",
279 Self::Max => "max",
280 Self::Avg => "avg",
281 }
282 }
283}
284
285#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
291pub enum LogicalPlanLite {
292 Scan {
294 table: String,
296 predicate: Option<String>,
299 projection: Vec<String>,
301 },
302 Aggregate {
304 input: Box<LogicalPlanLite>,
306 group_by: Vec<String>,
308 aggregates: Vec<AggregateExpr>,
310 },
311 Join {
313 left: Box<LogicalPlanLite>,
315 right: Box<LogicalPlanLite>,
317 on: Vec<JoinKey>,
319 },
320 Sort {
322 input: Box<LogicalPlanLite>,
324 keys: Vec<SortKey>,
326 limit: Option<usize>,
328 },
329 Limit {
331 input: Box<LogicalPlanLite>,
333 limit: usize,
335 },
336 Union {
338 inputs: Vec<LogicalPlanLite>,
340 },
341}
342
343pub type FragmentId = u32;
349pub type ExchangeId = u32;
351
352#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
354pub enum FragmentAssignment {
355 Tablet(TabletId),
357 Coordinator,
359}
360
361#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
363pub enum BuildSide {
364 Left,
366 Right,
368}
369
370#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
372pub enum FragmentOperator {
373 TabletScan {
375 table: String,
377 predicate: Option<String>,
379 projection: Vec<String>,
381 },
382 RemoteExchangeSource {
384 exchange: ExchangeId,
386 },
387 RemoteExchangeSink {
389 exchange: ExchangeId,
391 },
392 PartialAggregate {
394 group_by: Vec<String>,
396 aggregates: Vec<AggregateExpr>,
398 },
399 FinalAggregate {
401 group_by: Vec<String>,
403 aggregates: Vec<AggregateExpr>,
405 },
406 DistributedHashJoin {
408 on: Vec<JoinKey>,
410 },
411 BroadcastJoin {
414 on: Vec<JoinKey>,
416 build_side: BuildSide,
418 },
419 RepartitionJoin {
421 on: Vec<JoinKey>,
423 },
424 MergeSort {
427 keys: Vec<SortKey>,
429 limit: Option<usize>,
431 },
432 DistributedTopK {
436 k: usize,
438 score: SortKey,
440 },
441 DistributedLimit {
443 limit: usize,
445 },
446}
447
448#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
450pub enum ExchangeKind {
451 HashRepartition {
454 keys: Vec<String>,
456 },
457 Broadcast,
459 Merge,
461}
462
463#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
465pub struct ExchangeDescriptor {
466 pub exchange_id: ExchangeId,
468 pub producer: FragmentId,
470 pub consumer: FragmentId,
472 pub kind: ExchangeKind,
474 pub schema_fingerprint: u64,
477}
478
479#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
481pub struct PlanFragment {
482 pub fragment_id: FragmentId,
484 pub assignment: FragmentAssignment,
486 pub operators: Vec<FragmentOperator>,
488 pub estimated_rows: u64,
490 pub estimated_bytes: u64,
492 pub max_spill_bytes: u64,
494}
495
496#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
499pub struct DistributedPlan {
500 pub query_id: QueryId,
502 pub metadata_version: MetadataVersion,
504 pub fragments: Vec<PlanFragment>,
506 pub exchanges: Vec<ExchangeDescriptor>,
508}
509
510impl DistributedPlan {
511 pub fn root_fragment_id(&self) -> Option<FragmentId> {
513 self.fragments
514 .iter()
515 .map(|fragment| fragment.fragment_id)
516 .find(|id| !self.exchanges.iter().any(|edge| edge.producer == *id))
517 }
518
519 pub fn fragment(&self, fragment_id: FragmentId) -> Option<&PlanFragment> {
521 self.fragments.get(fragment_id as usize)
522 }
523
524 pub fn exchanges_from(
526 &self,
527 producer: FragmentId,
528 ) -> impl Iterator<Item = &ExchangeDescriptor> {
529 self.exchanges
530 .iter()
531 .filter(move |edge| edge.producer == producer)
532 }
533
534 pub fn exchanges_into(&self, consumer: FragmentId) -> Vec<&ExchangeDescriptor> {
536 let mut edges: Vec<&ExchangeDescriptor> = self
537 .exchanges
538 .iter()
539 .filter(|edge| edge.consumer == consumer)
540 .collect();
541 edges.sort_by_key(|edge| edge.producer);
542 edges
543 }
544}
545
546#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
552pub struct PlanDescription {
553 pub query_id: QueryId,
555 pub root: LogicalPlanLite,
557 pub options: PlannerOptions,
559}
560
561#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
563pub struct PlannerOptions {
564 pub broadcast_threshold_bytes: u64,
566 pub max_spill_bytes_per_fragment: u64,
568}
569
570impl Default for PlannerOptions {
571 fn default() -> Self {
572 Self {
573 broadcast_threshold_bytes: DEFAULT_BROADCAST_THRESHOLD_BYTES,
574 max_spill_bytes_per_fragment: DEFAULT_MAX_SPILL_BYTES_PER_FRAGMENT,
575 }
576 }
577}
578
579pub fn distribute(
597 description: &PlanDescription,
598 locator: &dyn TabletLocator,
599 metadata: &dyn ClusterMetadata,
600) -> DistributedResult<DistributedPlan> {
601 validate_lite(&description.root)?;
602 let mut planner = Planner {
603 locator,
604 metadata,
605 options: description.options,
606 fragments: Vec::new(),
607 exchanges: Vec::new(),
608 };
609 let stage = planner.plan_node(&description.root)?;
610 let root = match stage.producers.as_slice() {
611 [only]
612 if planner.fragments[*only as usize].assignment == FragmentAssignment::Coordinator =>
613 {
614 *only
615 }
616 _ => {
617 let gather = planner.push_fragment(
618 FragmentAssignment::Coordinator,
619 Vec::new(),
620 stage.estimated_rows,
621 stage.estimated_bytes,
622 );
623 planner.wire(&stage.producers, &[gather], ExchangeKind::Merge);
624 gather
625 }
626 };
627 debug_assert_eq!(
628 planner
629 .fragments
630 .iter()
631 .filter(|fragment| {
632 !planner
633 .exchanges
634 .iter()
635 .any(|edge| edge.producer == fragment.fragment_id)
636 })
637 .count(),
638 1,
639 "exactly one root fragment"
640 );
641 let _ = root;
642 Ok(DistributedPlan {
643 query_id: description.query_id,
644 metadata_version: metadata.metadata_version(),
645 fragments: planner.fragments,
646 exchanges: planner.exchanges,
647 })
648}
649
650fn validate_lite(node: &LogicalPlanLite) -> DistributedResult<()> {
652 match node {
653 LogicalPlanLite::Scan { table, .. } => {
654 if table.is_empty() {
655 return Err(DistributedError::InvalidPlan(
656 "scan needs a table name".to_owned(),
657 ));
658 }
659 }
660 LogicalPlanLite::Aggregate {
661 input, aggregates, ..
662 } => {
663 for aggregate in aggregates {
664 if aggregate.function != AggregateFunction::Count && aggregate.column.is_none() {
665 return Err(DistributedError::InvalidPlan(format!(
666 "{} needs a column",
667 aggregate.function.name()
668 )));
669 }
670 }
671 validate_lite(input)?;
672 }
673 LogicalPlanLite::Join { left, right, on } => {
674 if on.is_empty() {
675 return Err(DistributedError::InvalidPlan(
676 "join needs at least one key".to_owned(),
677 ));
678 }
679 validate_lite(left)?;
680 validate_lite(right)?;
681 }
682 LogicalPlanLite::Sort { input, keys, .. } => {
683 if keys.is_empty() {
684 return Err(DistributedError::InvalidPlan(
685 "sort needs at least one key".to_owned(),
686 ));
687 }
688 validate_lite(input)?;
689 }
690 LogicalPlanLite::Limit { input, .. } => validate_lite(input)?,
691 LogicalPlanLite::Union { inputs } => {
692 if inputs.len() < 2 {
693 return Err(DistributedError::InvalidPlan(
694 "union needs at least two inputs".to_owned(),
695 ));
696 }
697 for input in inputs {
698 validate_lite(input)?;
699 }
700 }
701 }
702 Ok(())
703}
704
705struct Stage {
707 producers: Vec<FragmentId>,
709 estimated_rows: u64,
710 estimated_bytes: u64,
711 partitioning: Option<PartitionSpec>,
713 tablets: Vec<TabletId>,
715}
716
717struct Planner<'a> {
718 locator: &'a dyn TabletLocator,
719 metadata: &'a dyn ClusterMetadata,
720 options: PlannerOptions,
721 fragments: Vec<PlanFragment>,
722 exchanges: Vec<ExchangeDescriptor>,
723}
724
725impl Planner<'_> {
726 fn push_fragment(
727 &mut self,
728 assignment: FragmentAssignment,
729 operators: Vec<FragmentOperator>,
730 estimated_rows: u64,
731 estimated_bytes: u64,
732 ) -> FragmentId {
733 let fragment_id = self.fragments.len() as FragmentId;
734 self.fragments.push(PlanFragment {
735 fragment_id,
736 assignment,
737 operators,
738 estimated_rows,
739 estimated_bytes,
740 max_spill_bytes: self.options.max_spill_bytes_per_fragment,
741 });
742 fragment_id
743 }
744
745 fn wire(&mut self, producers: &[FragmentId], consumers: &[FragmentId], kind: ExchangeKind) {
749 for &producer in producers {
750 let schema_fingerprint = self.schema_fingerprint(producer);
751 for &consumer in consumers {
752 let exchange_id = self.exchanges.len() as ExchangeId;
753 self.exchanges.push(ExchangeDescriptor {
754 exchange_id,
755 producer,
756 consumer,
757 kind: kind.clone(),
758 schema_fingerprint,
759 });
760 self.fragments[producer as usize].operators.push(
761 FragmentOperator::RemoteExchangeSink {
762 exchange: exchange_id,
763 },
764 );
765 self.fragments[consumer as usize].operators.push(
766 FragmentOperator::RemoteExchangeSource {
767 exchange: exchange_id,
768 },
769 );
770 }
771 }
772 }
773
774 fn schema_fingerprint(&self, fragment_id: FragmentId) -> u64 {
776 let columns = fragment_output_columns(&self.fragments[fragment_id as usize]);
777 let mut bytes = Vec::new();
778 for column in &columns {
779 bytes.extend_from_slice(&(column.len() as u32).to_le_bytes());
780 bytes.extend_from_slice(column.as_bytes());
781 }
782 fnv1a64(&bytes)
783 }
784
785 fn metadata_stats(&self, table: &str) -> DistributedResult<TableStats> {
786 self.metadata.table_stats(table)
787 }
788
789 fn plan_node(&mut self, node: &LogicalPlanLite) -> DistributedResult<Stage> {
790 match node {
791 LogicalPlanLite::Scan {
792 table,
793 predicate,
794 projection,
795 } => self.plan_scan(table, predicate, projection),
796 LogicalPlanLite::Aggregate {
797 input,
798 group_by,
799 aggregates,
800 } => self.plan_aggregate(input, group_by, aggregates),
801 LogicalPlanLite::Join { left, right, on } => self.plan_join(left, right, on),
802 LogicalPlanLite::Sort { input, keys, limit } => self.plan_sort(input, keys, *limit),
803 LogicalPlanLite::Limit { input, limit } => self.plan_limit(input, *limit),
804 LogicalPlanLite::Union { inputs } => self.plan_union(inputs),
805 }
806 }
807
808 fn plan_scan(
809 &mut self,
810 table: &str,
811 predicate: &Option<String>,
812 projection: &[String],
813 ) -> DistributedResult<Stage> {
814 let tablets = self.locator.tablets_for_table(table)?;
815 if tablets.is_empty() {
816 return Err(DistributedError::EmptyLayout {
817 table: table.to_owned(),
818 });
819 }
820 let spec = self.locator.partitioning(table)?;
821 let stats = self.metadata_stats(table)?;
822 let count = tablets.len() as u64;
823 let per_rows = stats.row_count.div_ceil(count);
824 let per_bytes = stats.total_bytes.div_ceil(count);
825 let mut producers = Vec::with_capacity(tablets.len());
826 for tablet in &tablets {
827 producers.push(self.push_fragment(
828 FragmentAssignment::Tablet(*tablet),
829 vec![FragmentOperator::TabletScan {
830 table: table.to_owned(),
831 predicate: predicate.clone(),
832 projection: projection.to_vec(),
833 }],
834 per_rows,
835 per_bytes,
836 ));
837 }
838 Ok(Stage {
839 producers,
840 estimated_rows: stats.row_count,
841 estimated_bytes: stats.total_bytes,
842 partitioning: Some(spec),
843 tablets,
844 })
845 }
846
847 fn plan_aggregate(
848 &mut self,
849 input: &LogicalPlanLite,
850 group_by: &[String],
851 aggregates: &[AggregateExpr],
852 ) -> DistributedResult<Stage> {
853 let child = self.plan_node(input)?;
854 let estimated_rows = if group_by.is_empty() {
855 1
856 } else {
857 (child.estimated_rows / 2).max(1)
858 };
859 let per_row = child
860 .estimated_bytes
861 .checked_div(child.estimated_rows.max(1))
862 .unwrap_or(0)
863 .max(1);
864 let estimated_bytes = estimated_rows.saturating_mul(per_row);
865 if let [only] = child.producers.as_slice() {
866 if self.fragments[*only as usize].assignment == FragmentAssignment::Coordinator {
867 self.fragments[*only as usize].operators.extend([
869 FragmentOperator::PartialAggregate {
870 group_by: group_by.to_vec(),
871 aggregates: aggregates.to_vec(),
872 },
873 FragmentOperator::FinalAggregate {
874 group_by: group_by.to_vec(),
875 aggregates: aggregates.to_vec(),
876 },
877 ]);
878 return Ok(Stage {
879 estimated_rows,
880 estimated_bytes,
881 ..child
882 });
883 }
884 }
885 for &producer in &child.producers {
886 self.fragments[producer as usize]
887 .operators
888 .push(FragmentOperator::PartialAggregate {
889 group_by: group_by.to_vec(),
890 aggregates: aggregates.to_vec(),
891 });
892 }
893 let consumer = self.push_fragment(
894 FragmentAssignment::Coordinator,
895 Vec::new(),
896 estimated_rows,
897 estimated_bytes,
898 );
899 let kind = if group_by.is_empty() {
900 ExchangeKind::Merge
901 } else {
902 ExchangeKind::HashRepartition {
903 keys: group_by.to_vec(),
904 }
905 };
906 self.wire(&child.producers, &[consumer], kind);
907 self.fragments[consumer as usize]
908 .operators
909 .push(FragmentOperator::FinalAggregate {
910 group_by: group_by.to_vec(),
911 aggregates: aggregates.to_vec(),
912 });
913 Ok(Stage {
914 producers: vec![consumer],
915 estimated_rows,
916 estimated_bytes,
917 partitioning: None,
918 tablets: Vec::new(),
919 })
920 }
921
922 fn plan_join(
923 &mut self,
924 left: &LogicalPlanLite,
925 right: &LogicalPlanLite,
926 on: &[JoinKey],
927 ) -> DistributedResult<Stage> {
928 if let (
930 LogicalPlanLite::Scan {
931 table: left_table, ..
932 },
933 LogicalPlanLite::Scan {
934 table: right_table, ..
935 },
936 ) = (left, right)
937 {
938 let left_tablets = self.locator.tablets_for_table(left_table)?;
939 if left_tablets.is_empty() {
940 return Err(DistributedError::EmptyLayout {
941 table: left_table.clone(),
942 });
943 }
944 let right_tablets = self.locator.tablets_for_table(right_table)?;
945 if right_tablets.is_empty() {
946 return Err(DistributedError::EmptyLayout {
947 table: right_table.clone(),
948 });
949 }
950 let left_spec = self.locator.partitioning(left_table)?;
951 let right_spec = self.locator.partitioning(right_table)?;
952 if left_spec.colocated_with(&right_spec) && left_tablets == right_tablets {
953 return self.plan_colocated_join(left, right, on, &left_tablets, &left_spec);
954 }
955 }
956 let left_stage = self.plan_node(left)?;
957 let right_stage = self.plan_node(right)?;
958 if left_stage.estimated_bytes.min(right_stage.estimated_bytes)
959 <= self.options.broadcast_threshold_bytes
960 {
961 self.plan_broadcast_join(left_stage, right_stage, on)
962 } else {
963 self.plan_repartition_join(left_stage, right_stage, on)
964 }
965 }
966
967 fn plan_colocated_join(
970 &mut self,
971 left: &LogicalPlanLite,
972 right: &LogicalPlanLite,
973 on: &[JoinKey],
974 tablets: &[TabletId],
975 spec: &PartitionSpec,
976 ) -> DistributedResult<Stage> {
977 let (
978 LogicalPlanLite::Scan {
979 table: left_table,
980 predicate: left_predicate,
981 projection: left_projection,
982 },
983 LogicalPlanLite::Scan {
984 table: right_table,
985 predicate: right_predicate,
986 projection: right_projection,
987 },
988 ) = (left, right)
989 else {
990 return Err(DistributedError::InvalidPlan(
991 "colocated join needs scan inputs".to_owned(),
992 ));
993 };
994 let left_stats = self.metadata_stats(left_table)?;
995 let right_stats = self.metadata_stats(right_table)?;
996 let estimated_rows = left_stats.row_count.max(right_stats.row_count);
997 let estimated_bytes = left_stats.total_bytes.max(right_stats.total_bytes);
998 let count = tablets.len() as u64;
999 let mut producers = Vec::with_capacity(tablets.len());
1000 for tablet in tablets {
1001 producers.push(self.push_fragment(
1002 FragmentAssignment::Tablet(*tablet),
1003 vec![
1004 FragmentOperator::TabletScan {
1005 table: left_table.clone(),
1006 predicate: left_predicate.clone(),
1007 projection: left_projection.clone(),
1008 },
1009 FragmentOperator::TabletScan {
1010 table: right_table.clone(),
1011 predicate: right_predicate.clone(),
1012 projection: right_projection.clone(),
1013 },
1014 FragmentOperator::DistributedHashJoin { on: on.to_vec() },
1015 ],
1016 estimated_rows.div_ceil(count),
1017 estimated_bytes.div_ceil(count),
1018 ));
1019 }
1020 Ok(Stage {
1021 producers,
1022 estimated_rows,
1023 estimated_bytes,
1024 partitioning: Some(spec.clone()),
1025 tablets: tablets.to_vec(),
1026 })
1027 }
1028
1029 fn plan_broadcast_join(
1032 &mut self,
1033 left_stage: Stage,
1034 right_stage: Stage,
1035 on: &[JoinKey],
1036 ) -> DistributedResult<Stage> {
1037 let (big, small, build_side) = if right_stage.estimated_bytes <= left_stage.estimated_bytes
1038 {
1039 (left_stage, right_stage, BuildSide::Right)
1040 } else {
1041 (right_stage, left_stage, BuildSide::Left)
1042 };
1043 self.wire(&small.producers, &big.producers, ExchangeKind::Broadcast);
1044 for &producer in &big.producers {
1045 self.fragments[producer as usize]
1046 .operators
1047 .push(FragmentOperator::BroadcastJoin {
1048 on: on.to_vec(),
1049 build_side,
1050 });
1051 }
1052 let estimated_rows = big.estimated_rows;
1053 let estimated_bytes = big.estimated_bytes.saturating_add(small.estimated_bytes);
1054 Ok(Stage {
1055 producers: big.producers,
1056 estimated_rows,
1057 estimated_bytes,
1058 partitioning: big.partitioning,
1059 tablets: big.tablets,
1060 })
1061 }
1062
1063 fn plan_repartition_join(
1066 &mut self,
1067 left_stage: Stage,
1068 right_stage: Stage,
1069 on: &[JoinKey],
1070 ) -> DistributedResult<Stage> {
1071 let join_tablets = if left_stage.tablets.len() >= right_stage.tablets.len() {
1072 left_stage.tablets.clone()
1073 } else {
1074 right_stage.tablets.clone()
1075 };
1076 if join_tablets.is_empty() {
1077 return Err(DistributedError::InvalidPlan(
1078 "repartition join needs at least one tablet-backed input".to_owned(),
1079 ));
1080 }
1081 let width = left_stage.producers.len().max(right_stage.producers.len());
1082 let estimated_rows = left_stage.estimated_rows.max(right_stage.estimated_rows);
1083 let estimated_bytes = left_stage
1084 .estimated_bytes
1085 .saturating_add(right_stage.estimated_bytes);
1086 let mut consumers = Vec::with_capacity(width);
1087 for index in 0..width {
1088 consumers.push(self.push_fragment(
1089 FragmentAssignment::Tablet(join_tablets[index % join_tablets.len()]),
1090 Vec::new(),
1091 estimated_rows.div_ceil(width as u64),
1092 estimated_bytes.div_ceil(width as u64),
1093 ));
1094 }
1095 let left_keys: Vec<String> = on.iter().map(|key| key.left.clone()).collect();
1096 let right_keys: Vec<String> = on.iter().map(|key| key.right.clone()).collect();
1097 self.wire(
1098 &left_stage.producers,
1099 &consumers,
1100 ExchangeKind::HashRepartition { keys: left_keys },
1101 );
1102 self.wire(
1103 &right_stage.producers,
1104 &consumers,
1105 ExchangeKind::HashRepartition { keys: right_keys },
1106 );
1107 for &consumer in &consumers {
1108 self.fragments[consumer as usize]
1109 .operators
1110 .push(FragmentOperator::RepartitionJoin { on: on.to_vec() });
1111 }
1112 Ok(Stage {
1113 producers: consumers,
1114 estimated_rows,
1115 estimated_bytes,
1116 partitioning: None,
1117 tablets: join_tablets,
1118 })
1119 }
1120
1121 fn plan_sort(
1122 &mut self,
1123 input: &LogicalPlanLite,
1124 keys: &[SortKey],
1125 limit: Option<usize>,
1126 ) -> DistributedResult<Stage> {
1127 let child = self.plan_node(input)?;
1128 let top_k = match (limit, keys) {
1131 (Some(k), [score]) if score.descending => Some((k, score.clone())),
1132 _ => None,
1133 };
1134 let estimated_rows = limit.map_or(child.estimated_rows, |limit| {
1135 child.estimated_rows.min(limit as u64)
1136 });
1137 let estimated_bytes =
1138 scaled_bytes(child.estimated_bytes, child.estimated_rows, estimated_rows);
1139 let local_op = match &top_k {
1140 Some((k, score)) => FragmentOperator::DistributedTopK {
1141 k: *k,
1142 score: score.clone(),
1143 },
1144 None => FragmentOperator::MergeSort {
1145 keys: keys.to_vec(),
1146 limit,
1147 },
1148 };
1149 if let [only] = child.producers.as_slice() {
1150 if self.fragments[*only as usize].assignment == FragmentAssignment::Coordinator {
1151 self.fragments[*only as usize].operators.push(local_op);
1152 return Ok(Stage {
1153 estimated_rows,
1154 estimated_bytes,
1155 ..child
1156 });
1157 }
1158 }
1159 for &producer in &child.producers {
1160 self.fragments[producer as usize]
1161 .operators
1162 .push(local_op.clone());
1163 }
1164 let consumer = self.push_fragment(
1165 FragmentAssignment::Coordinator,
1166 Vec::new(),
1167 estimated_rows,
1168 estimated_bytes,
1169 );
1170 self.wire(&child.producers, &[consumer], ExchangeKind::Merge);
1171 let root_op = match &top_k {
1172 Some((k, score)) => FragmentOperator::DistributedTopK {
1173 k: *k,
1174 score: score.clone(),
1175 },
1176 None => FragmentOperator::MergeSort {
1177 keys: keys.to_vec(),
1178 limit,
1179 },
1180 };
1181 self.fragments[consumer as usize].operators.push(root_op);
1182 Ok(Stage {
1183 producers: vec![consumer],
1184 estimated_rows,
1185 estimated_bytes,
1186 partitioning: None,
1187 tablets: Vec::new(),
1188 })
1189 }
1190
1191 fn plan_limit(&mut self, input: &LogicalPlanLite, limit: usize) -> DistributedResult<Stage> {
1192 let child = self.plan_node(input)?;
1193 let estimated_rows = child.estimated_rows.min(limit as u64);
1194 let estimated_bytes =
1195 scaled_bytes(child.estimated_bytes, child.estimated_rows, estimated_rows);
1196 if let [only] = child.producers.as_slice() {
1197 if self.fragments[*only as usize].assignment == FragmentAssignment::Coordinator {
1198 self.fragments[*only as usize]
1199 .operators
1200 .push(FragmentOperator::DistributedLimit { limit });
1201 return Ok(Stage {
1202 estimated_rows,
1203 estimated_bytes,
1204 ..child
1205 });
1206 }
1207 }
1208 for &producer in &child.producers {
1209 self.fragments[producer as usize]
1210 .operators
1211 .push(FragmentOperator::DistributedLimit { limit });
1212 }
1213 let consumer = self.push_fragment(
1214 FragmentAssignment::Coordinator,
1215 Vec::new(),
1216 estimated_rows,
1217 estimated_bytes,
1218 );
1219 self.wire(&child.producers, &[consumer], ExchangeKind::Merge);
1220 self.fragments[consumer as usize]
1221 .operators
1222 .push(FragmentOperator::DistributedLimit { limit });
1223 Ok(Stage {
1224 producers: vec![consumer],
1225 estimated_rows,
1226 estimated_bytes,
1227 partitioning: None,
1228 tablets: Vec::new(),
1229 })
1230 }
1231
1232 fn plan_union(&mut self, inputs: &[LogicalPlanLite]) -> DistributedResult<Stage> {
1233 let mut producers = Vec::new();
1234 let mut estimated_rows = 0u64;
1235 let mut estimated_bytes = 0u64;
1236 for input in inputs {
1237 let stage = self.plan_node(input)?;
1238 producers.extend(stage.producers);
1239 estimated_rows = estimated_rows.saturating_add(stage.estimated_rows);
1240 estimated_bytes = estimated_bytes.saturating_add(stage.estimated_bytes);
1241 }
1242 if producers.is_empty() {
1243 return Err(DistributedError::InvalidPlan(
1244 "union produced no fragments".to_owned(),
1245 ));
1246 }
1247 if producers.len() == 1 {
1250 return Ok(Stage {
1251 producers,
1252 estimated_rows,
1253 estimated_bytes,
1254 partitioning: None,
1255 tablets: Vec::new(),
1256 });
1257 }
1258 let consumer = self.push_fragment(
1259 FragmentAssignment::Coordinator,
1260 Vec::new(),
1261 estimated_rows,
1262 estimated_bytes,
1263 );
1264 self.wire(&producers, &[consumer], ExchangeKind::Merge);
1265 Ok(Stage {
1266 producers: vec![consumer],
1267 estimated_rows,
1268 estimated_bytes,
1269 partitioning: None,
1270 tablets: Vec::new(),
1271 })
1272 }
1273}
1274
1275#[derive(Clone, Debug)]
1286pub struct DataFusionDistributedPlanner {
1287 query_id: QueryId,
1288 options: PlannerOptions,
1289}
1290
1291impl DataFusionDistributedPlanner {
1292 pub fn new(query_id: QueryId) -> Self {
1294 Self {
1295 query_id,
1296 options: PlannerOptions::default(),
1297 }
1298 }
1299
1300 pub fn with_options(query_id: QueryId, options: PlannerOptions) -> Self {
1302 Self { query_id, options }
1303 }
1304
1305 pub fn query_id(&self) -> QueryId {
1307 self.query_id
1308 }
1309
1310 pub fn lower(
1313 &self,
1314 plan: &datafusion::logical_expr::LogicalPlan,
1315 locator: &dyn TabletLocator,
1316 metadata: &dyn ClusterMetadata,
1317 ) -> DistributedResult<DistributedPlan> {
1318 let root = self.to_lite(plan)?;
1319 distribute(
1320 &PlanDescription {
1321 query_id: self.query_id,
1322 root,
1323 options: self.options,
1324 },
1325 locator,
1326 metadata,
1327 )
1328 }
1329
1330 pub fn to_lite(
1333 &self,
1334 plan: &datafusion::logical_expr::LogicalPlan,
1335 ) -> DistributedResult<LogicalPlanLite> {
1336 lower_datafusion_plan(plan)
1337 }
1338}
1339
1340fn lower_datafusion_plan(
1341 plan: &datafusion::logical_expr::LogicalPlan,
1342) -> DistributedResult<LogicalPlanLite> {
1343 use datafusion::logical_expr::LogicalPlan;
1344
1345 match plan {
1346 LogicalPlan::TableScan(scan) => lower_table_scan(scan),
1347 LogicalPlan::Projection(projection) => lower_projection(projection),
1348 LogicalPlan::Filter(filter) => lower_filter(filter),
1349 LogicalPlan::Aggregate(aggregate) => lower_aggregate(aggregate),
1350 LogicalPlan::Sort(sort) => lower_sort(sort),
1351 LogicalPlan::Limit(limit) => lower_limit(limit),
1352 LogicalPlan::Join(join) => lower_join(join),
1353 LogicalPlan::Union(union) => lower_union(union),
1354 LogicalPlan::SubqueryAlias(alias) => lower_datafusion_plan(alias.input.as_ref()),
1355 other => Err(DistributedError::Unsupported(format!(
1356 "DataFusion operator not supported for distributed planning: {}",
1357 other.display()
1358 ))),
1359 }
1360}
1361
1362fn lower_table_scan(
1363 scan: &datafusion::logical_expr::TableScan,
1364) -> DistributedResult<LogicalPlanLite> {
1365 let table = scan.table_name.table().to_owned();
1366 if table.is_empty() {
1367 return Err(DistributedError::InvalidPlan(
1368 "table scan needs a table name".to_owned(),
1369 ));
1370 }
1371 let projection = if let Some(indices) = &scan.projection {
1372 let schema = scan.source.schema();
1373 let mut columns = Vec::with_capacity(indices.len());
1374 for &idx in indices {
1375 if idx >= schema.fields().len() {
1376 return Err(DistributedError::InvalidPlan(format!(
1377 "table scan projection index {idx} out of range"
1378 )));
1379 }
1380 columns.push(schema.field(idx).name().clone());
1381 }
1382 columns
1383 } else {
1384 scan.projected_schema
1386 .fields()
1387 .iter()
1388 .map(|field| field.name().clone())
1389 .collect()
1390 };
1391 let predicate = combine_filter_predicates(&scan.filters)?;
1392 let mut root = LogicalPlanLite::Scan {
1393 table,
1394 predicate,
1395 projection,
1396 };
1397 if let Some(fetch) = scan.fetch {
1398 root = LogicalPlanLite::Limit {
1399 input: Box::new(root),
1400 limit: fetch,
1401 };
1402 }
1403 Ok(root)
1404}
1405
1406fn lower_projection(
1407 projection: &datafusion::logical_expr::Projection,
1408) -> DistributedResult<LogicalPlanLite> {
1409 let mut input = lower_datafusion_plan(projection.input.as_ref())?;
1410 let columns = projection_column_names(&projection.expr)?;
1411 if let Some((predicate, projection_cols)) = scan_fields_mut(&mut input) {
1413 let _ = predicate; if !columns.is_empty() {
1415 if projection_cols.is_empty() {
1416 *projection_cols = columns;
1417 } else {
1418 let map: HashMap<String, String> = projection_cols
1420 .iter()
1421 .map(|name| (name.clone(), name.clone()))
1422 .collect();
1423 let mut next = Vec::with_capacity(columns.len());
1424 for column in &columns {
1425 let Some(existing) = map.get(column.as_str()) else {
1426 return Err(DistributedError::InvalidPlan(format!(
1427 "projection column `{column}` is not in the scan projection"
1428 )));
1429 };
1430 next.push(existing.clone());
1431 }
1432 *projection_cols = next;
1433 }
1434 }
1435 return Ok(input);
1436 }
1437 if columns.is_empty()
1441 || matches!(
1442 input,
1443 LogicalPlanLite::Aggregate { .. }
1444 | LogicalPlanLite::Sort { .. }
1445 | LogicalPlanLite::Limit { .. }
1446 | LogicalPlanLite::Join { .. }
1447 | LogicalPlanLite::Union { .. }
1448 )
1449 {
1450 return Ok(input);
1451 }
1452 Err(DistributedError::Unsupported(
1453 "projection over a non-scan plan is not supported; push the projection onto the base table"
1454 .to_owned(),
1455 ))
1456}
1457
1458fn lower_filter(filter: &datafusion::logical_expr::Filter) -> DistributedResult<LogicalPlanLite> {
1459 assert_filter_evaluable(&filter.predicate)?;
1461 let mut input = lower_datafusion_plan(filter.input.as_ref())?;
1462 let text = filter.predicate.to_string();
1463 if let Some((predicate, _)) = scan_fields_mut(&mut input) {
1464 *predicate = match predicate.take() {
1465 Some(existing) => Some(format!("({existing}) AND ({text})")),
1466 None => Some(text),
1467 };
1468 return Ok(input);
1469 }
1470 Err(DistributedError::Unsupported(
1471 "filter must push down onto a table scan for distributed execution".to_owned(),
1472 ))
1473}
1474
1475fn lower_aggregate(
1476 aggregate: &datafusion::logical_expr::Aggregate,
1477) -> DistributedResult<LogicalPlanLite> {
1478 let input = lower_datafusion_plan(aggregate.input.as_ref())?;
1479 let mut group_by = Vec::with_capacity(aggregate.group_expr.len());
1480 for expr in &aggregate.group_expr {
1481 group_by.push(column_name(expr).ok_or_else(|| {
1482 DistributedError::Unsupported(format!(
1483 "aggregate group-by expression must be a column, got {expr}"
1484 ))
1485 })?);
1486 }
1487 let mut aggregates = Vec::with_capacity(aggregate.aggr_expr.len());
1488 for expr in &aggregate.aggr_expr {
1489 aggregates.push(aggregate_expr_from_df(expr)?);
1490 }
1491 if aggregates.is_empty() {
1492 return Err(DistributedError::InvalidPlan(
1493 "aggregate needs at least one aggregate expression".to_owned(),
1494 ));
1495 }
1496 Ok(LogicalPlanLite::Aggregate {
1497 input: Box::new(input),
1498 group_by,
1499 aggregates,
1500 })
1501}
1502
1503fn lower_sort(sort: &datafusion::logical_expr::Sort) -> DistributedResult<LogicalPlanLite> {
1504 let input = lower_datafusion_plan(sort.input.as_ref())?;
1505 if sort.expr.is_empty() {
1506 return Err(DistributedError::InvalidPlan(
1507 "sort needs at least one key".to_owned(),
1508 ));
1509 }
1510 let mut keys = Vec::with_capacity(sort.expr.len());
1511 for sort_expr in &sort.expr {
1512 let column = column_name(&sort_expr.expr).ok_or_else(|| {
1513 DistributedError::Unsupported(format!(
1514 "sort key must be a column, got {}",
1515 sort_expr.expr
1516 ))
1517 })?;
1518 keys.push(SortKey {
1519 column,
1520 descending: !sort_expr.asc,
1521 });
1522 }
1523 Ok(LogicalPlanLite::Sort {
1524 input: Box::new(input),
1525 keys,
1526 limit: sort.fetch,
1527 })
1528}
1529
1530fn lower_limit(limit: &datafusion::logical_expr::Limit) -> DistributedResult<LogicalPlanLite> {
1531 use datafusion::logical_expr::{FetchType, SkipType};
1532
1533 match limit.get_skip_type() {
1534 Ok(SkipType::Literal(0)) => {}
1535 Ok(SkipType::Literal(skip)) => {
1536 return Err(DistributedError::Unsupported(format!(
1537 "OFFSET {skip} is not supported in distributed planning"
1538 )));
1539 }
1540 Ok(SkipType::UnsupportedExpr) => {
1541 return Err(DistributedError::Unsupported(
1542 "non-literal OFFSET is not supported in distributed planning".to_owned(),
1543 ));
1544 }
1545 Err(error) => {
1546 return Err(DistributedError::InvalidPlan(error.to_string()));
1547 }
1548 }
1549 let fetch = match limit.get_fetch_type() {
1550 Ok(FetchType::Literal(Some(n))) => n,
1551 Ok(FetchType::Literal(None)) => {
1552 return lower_datafusion_plan(limit.input.as_ref());
1554 }
1555 Ok(FetchType::UnsupportedExpr) => {
1556 return Err(DistributedError::Unsupported(
1557 "non-literal LIMIT is not supported in distributed planning".to_owned(),
1558 ));
1559 }
1560 Err(error) => return Err(DistributedError::InvalidPlan(error.to_string())),
1561 };
1562 let input = lower_datafusion_plan(limit.input.as_ref())?;
1563 Ok(LogicalPlanLite::Limit {
1564 input: Box::new(input),
1565 limit: fetch,
1566 })
1567}
1568
1569fn lower_join(join: &datafusion::logical_expr::Join) -> DistributedResult<LogicalPlanLite> {
1570 use datafusion::logical_expr::{JoinConstraint, JoinType};
1571
1572 if join.join_type != JoinType::Inner {
1573 return Err(DistributedError::Unsupported(format!(
1574 "only Inner joins are supported for distributed planning, got {:?}",
1575 join.join_type
1576 )));
1577 }
1578 if join.filter.is_some() {
1579 return Err(DistributedError::Unsupported(
1580 "non-equi join filters are not supported for distributed planning".to_owned(),
1581 ));
1582 }
1583 if !matches!(
1584 join.join_constraint,
1585 JoinConstraint::On | JoinConstraint::Using
1586 ) {
1587 return Err(DistributedError::Unsupported(format!(
1588 "unsupported join constraint {:?}",
1589 join.join_constraint
1590 )));
1591 }
1592 if join.on.is_empty() {
1593 return Err(DistributedError::InvalidPlan(
1594 "join needs at least one key".to_owned(),
1595 ));
1596 }
1597 let left = lower_datafusion_plan(join.left.as_ref())?;
1598 let right = lower_datafusion_plan(join.right.as_ref())?;
1599 let mut on = Vec::with_capacity(join.on.len());
1600 for (left_expr, right_expr) in &join.on {
1601 let left_col = column_name(left_expr).ok_or_else(|| {
1602 DistributedError::Unsupported(format!(
1603 "join key must be a column, got left={left_expr}"
1604 ))
1605 })?;
1606 let right_col = column_name(right_expr).ok_or_else(|| {
1607 DistributedError::Unsupported(format!(
1608 "join key must be a column, got right={right_expr}"
1609 ))
1610 })?;
1611 on.push(JoinKey {
1612 left: left_col,
1613 right: right_col,
1614 });
1615 }
1616 Ok(LogicalPlanLite::Join {
1617 left: Box::new(left),
1618 right: Box::new(right),
1619 on,
1620 })
1621}
1622
1623fn lower_union(union: &datafusion::logical_expr::Union) -> DistributedResult<LogicalPlanLite> {
1624 if union.inputs.len() < 2 {
1625 return Err(DistributedError::InvalidPlan(
1626 "union needs at least two inputs".to_owned(),
1627 ));
1628 }
1629 let mut inputs = Vec::with_capacity(union.inputs.len());
1630 for input in &union.inputs {
1631 inputs.push(lower_datafusion_plan(input.as_ref())?);
1632 }
1633 Ok(LogicalPlanLite::Union { inputs })
1634}
1635
1636fn scan_fields_mut(node: &mut LogicalPlanLite) -> Option<(&mut Option<String>, &mut Vec<String>)> {
1639 match node {
1640 LogicalPlanLite::Scan {
1641 predicate,
1642 projection,
1643 ..
1644 } => Some((predicate, projection)),
1645 LogicalPlanLite::Limit { input, .. } => match input.as_mut() {
1646 LogicalPlanLite::Scan {
1647 predicate,
1648 projection,
1649 ..
1650 } => Some((predicate, projection)),
1651 _ => None,
1652 },
1653 _ => None,
1654 }
1655}
1656
1657fn projection_column_names(
1658 exprs: &[datafusion::logical_expr::Expr],
1659) -> DistributedResult<Vec<String>> {
1660 let mut columns = Vec::with_capacity(exprs.len());
1661 for expr in exprs {
1662 if let Some(name) = column_name(expr) {
1663 columns.push(name);
1664 continue;
1665 }
1666 return Err(DistributedError::Unsupported(format!(
1669 "projection expression must be a column (or alias of a column), got {expr}"
1670 )));
1671 }
1672 Ok(columns)
1673}
1674
1675fn column_name(expr: &datafusion::logical_expr::Expr) -> Option<String> {
1676 use datafusion::logical_expr::Expr;
1677 match expr {
1678 Expr::Column(column) => Some(column.name.clone()),
1679 Expr::Alias(alias) => column_name(alias.expr.as_ref()),
1680 _ => None,
1681 }
1682}
1683
1684fn combine_filter_predicates(
1685 filters: &[datafusion::logical_expr::Expr],
1686) -> DistributedResult<Option<String>> {
1687 if filters.is_empty() {
1688 return Ok(None);
1689 }
1690 let mut parts = Vec::with_capacity(filters.len());
1691 for filter in filters {
1692 assert_filter_evaluable(filter)?;
1693 parts.push(format!("({filter})"));
1694 }
1695 Ok(Some(parts.join(" AND ")))
1696}
1697
1698fn assert_filter_evaluable(expr: &datafusion::logical_expr::Expr) -> DistributedResult<()> {
1702 use datafusion::logical_expr::{Expr, Operator};
1703
1704 match expr {
1705 Expr::Column(_) | Expr::Literal(_, _) | Expr::IsNull(_) | Expr::IsNotNull(_) => Ok(()),
1706 Expr::Alias(alias) => assert_filter_evaluable(alias.expr.as_ref()),
1707 Expr::Not(inner) => assert_filter_evaluable(inner.as_ref()),
1708 Expr::BinaryExpr(binary) => match binary.op {
1709 Operator::And
1710 | Operator::Or
1711 | Operator::Eq
1712 | Operator::NotEq
1713 | Operator::Lt
1714 | Operator::LtEq
1715 | Operator::Gt
1716 | Operator::GtEq => {
1717 assert_filter_evaluable(binary.left.as_ref())?;
1718 assert_filter_evaluable(binary.right.as_ref())
1719 }
1720 other => Err(DistributedError::Unsupported(format!(
1721 "filter operator {other:?} is not supported for distributed planning"
1722 ))),
1723 },
1724 Expr::Between(between) => {
1725 assert_filter_evaluable(between.expr.as_ref())?;
1726 assert_filter_evaluable(between.low.as_ref())?;
1727 assert_filter_evaluable(between.high.as_ref())
1728 }
1729 Expr::InList(list) => {
1730 assert_filter_evaluable(list.expr.as_ref())?;
1731 for value in &list.list {
1732 assert_filter_evaluable(value)?;
1733 }
1734 Ok(())
1735 }
1736 Expr::Like(like) => {
1737 assert_filter_evaluable(like.expr.as_ref())?;
1738 assert_filter_evaluable(like.pattern.as_ref())
1739 }
1740 other => Err(DistributedError::Unsupported(format!(
1741 "filter expression is not supported for distributed planning: {other}"
1742 ))),
1743 }
1744}
1745
1746#[derive(Clone, Debug, Default)]
1753pub struct PlanningTableCatalog {
1754 tables: HashMap<String, SchemaRef>,
1755}
1756
1757impl PlanningTableCatalog {
1758 pub fn new() -> Self {
1760 Self::default()
1761 }
1762
1763 pub fn insert(&mut self, table: impl Into<String>, schema: SchemaRef) {
1765 self.tables.insert(table.into(), schema);
1766 }
1767
1768 pub fn with_table(mut self, table: impl Into<String>, schema: SchemaRef) -> Self {
1770 self.insert(table, schema);
1771 self
1772 }
1773
1774 pub fn table_names(&self) -> Vec<&str> {
1776 let mut names: Vec<&str> = self.tables.keys().map(String::as_str).collect();
1777 names.sort_unstable();
1778 names
1779 }
1780
1781 pub fn schema(&self, table: &str) -> Option<&SchemaRef> {
1783 self.tables.get(table)
1784 }
1785}
1786
1787pub fn plan_logical_distributed(
1793 plan: &datafusion::logical_expr::LogicalPlan,
1794 locator: &dyn TabletLocator,
1795 metadata: &dyn ClusterMetadata,
1796) -> DistributedResult<DistributedPlan> {
1797 plan_logical_distributed_with_id(plan, QueryId::new_random(), locator, metadata)
1798}
1799
1800pub fn plan_logical_distributed_with_id(
1802 plan: &datafusion::logical_expr::LogicalPlan,
1803 query_id: QueryId,
1804 locator: &dyn TabletLocator,
1805 metadata: &dyn ClusterMetadata,
1806) -> DistributedResult<DistributedPlan> {
1807 DataFusionDistributedPlanner::new(query_id).lower(plan, locator, metadata)
1808}
1809
1810pub async fn plan_sql_distributed(
1818 sql: &str,
1819 catalog: &PlanningTableCatalog,
1820 locator: &dyn TabletLocator,
1821 metadata: &dyn ClusterMetadata,
1822) -> DistributedResult<DistributedPlan> {
1823 plan_sql_distributed_with_id(sql, catalog, QueryId::new_random(), locator, metadata).await
1824}
1825
1826pub async fn plan_sql_distributed_with_id(
1828 sql: &str,
1829 catalog: &PlanningTableCatalog,
1830 query_id: QueryId,
1831 locator: &dyn TabletLocator,
1832 metadata: &dyn ClusterMetadata,
1833) -> DistributedResult<DistributedPlan> {
1834 let sql = sql.trim();
1835 if sql.is_empty() {
1836 return Err(DistributedError::InvalidPlan(
1837 "SQL statement is empty".to_owned(),
1838 ));
1839 }
1840 let ctx = datafusion::prelude::SessionContext::new();
1841 for (name, schema) in &catalog.tables {
1842 let provider = datafusion::datasource::MemTable::try_new(
1844 Arc::clone(schema),
1845 vec![vec![RecordBatch::new_empty(Arc::clone(schema))]],
1846 )
1847 .map_err(|error| {
1848 DistributedError::InvalidPlan(format!(
1849 "failed to register planning table `{name}`: {error}"
1850 ))
1851 })?;
1852 ctx.register_table(name.as_str(), Arc::new(provider))
1853 .map_err(|error| {
1854 DistributedError::InvalidPlan(format!(
1855 "failed to register planning table `{name}`: {error}"
1856 ))
1857 })?;
1858 }
1859 let df = ctx.sql(sql).await.map_err(|error| {
1860 DistributedError::InvalidPlan(format!("DataFusion failed to plan SQL: {error}"))
1861 })?;
1862 plan_logical_distributed_with_id(df.logical_plan(), query_id, locator, metadata)
1863}
1864
1865fn aggregate_expr_from_df(
1866 expr: &datafusion::logical_expr::Expr,
1867) -> DistributedResult<AggregateExpr> {
1868 use datafusion::logical_expr::Expr;
1869
1870 let expr = match expr {
1871 Expr::Alias(alias) => alias.expr.as_ref(),
1872 other => other,
1873 };
1874 let Expr::AggregateFunction(agg) = expr else {
1875 return Err(DistributedError::Unsupported(format!(
1876 "aggregate expression must be an aggregate function, got {expr}"
1877 )));
1878 };
1879 let name = agg.func.name().to_ascii_lowercase();
1880 let function = match name.as_str() {
1881 "count" => AggregateFunction::Count,
1882 "sum" => AggregateFunction::Sum,
1883 "min" => AggregateFunction::Min,
1884 "max" => AggregateFunction::Max,
1885 "avg" | "mean" => AggregateFunction::Avg,
1886 other => {
1887 return Err(DistributedError::Unsupported(format!(
1888 "aggregate function `{other}` is not supported for distributed planning"
1889 )));
1890 }
1891 };
1892 let column = match agg.params.args.as_slice() {
1893 [] => None,
1894 [arg] if function == AggregateFunction::Count => column_name(arg),
1896 [arg] => Some(column_name(arg).ok_or_else(|| {
1897 DistributedError::Unsupported(format!("aggregate argument must be a column, got {arg}"))
1898 })?),
1899 _ => {
1900 return Err(DistributedError::Unsupported(
1901 "multi-argument aggregates are not supported for distributed planning".to_owned(),
1902 ));
1903 }
1904 };
1905 if function != AggregateFunction::Count && column.is_none() {
1906 return Err(DistributedError::InvalidPlan(format!(
1907 "{} needs a column",
1908 function.name()
1909 )));
1910 }
1911 Ok(AggregateExpr { function, column })
1912}
1913
1914fn scaled_bytes(bytes: u64, rows: u64, new_rows: u64) -> u64 {
1916 if rows == 0 {
1917 return 0;
1918 }
1919 bytes
1920 .saturating_mul(new_rows)
1921 .checked_div(rows)
1922 .unwrap_or(bytes)
1923}
1924
1925fn fragment_output_columns(fragment: &PlanFragment) -> Vec<String> {
1927 let mut columns = Vec::new();
1928 for operator in &fragment.operators {
1929 match operator {
1930 FragmentOperator::TabletScan {
1931 table, projection, ..
1932 } => {
1933 if projection.is_empty() {
1934 columns = vec![format!("{table}.*")];
1935 } else {
1936 columns = projection
1937 .iter()
1938 .map(|column| format!("{table}.{column}"))
1939 .collect();
1940 }
1941 }
1942 FragmentOperator::PartialAggregate {
1943 group_by,
1944 aggregates,
1945 } => {
1946 columns = group_by.to_vec();
1947 columns.extend(partial_column_names(aggregates));
1948 }
1949 FragmentOperator::FinalAggregate {
1950 group_by,
1951 aggregates,
1952 } => {
1953 columns = group_by.to_vec();
1954 columns.extend(aggregates.iter().map(aggregate_output_name));
1955 }
1956 FragmentOperator::DistributedHashJoin { .. }
1957 | FragmentOperator::BroadcastJoin { .. }
1958 | FragmentOperator::RepartitionJoin { .. } => {
1959 columns = vec!["*join*".to_owned()];
1960 }
1961 FragmentOperator::RemoteExchangeSource { .. }
1962 | FragmentOperator::RemoteExchangeSink { .. }
1963 | FragmentOperator::MergeSort { .. }
1964 | FragmentOperator::DistributedTopK { .. }
1965 | FragmentOperator::DistributedLimit { .. } => {}
1966 }
1967 }
1968 columns
1969}
1970
1971fn partial_column_names(aggregates: &[AggregateExpr]) -> Vec<String> {
1974 let mut names = Vec::new();
1975 for (index, aggregate) in aggregates.iter().enumerate() {
1976 if aggregate.function == AggregateFunction::Avg {
1977 names.push(format!("__partial_{index}_sum"));
1978 names.push(format!("__partial_{index}_count"));
1979 } else {
1980 names.push(format!("__partial_{index}"));
1981 }
1982 }
1983 names
1984}
1985
1986fn aggregate_output_name(aggregate: &AggregateExpr) -> String {
1988 format!(
1989 "{}_{}",
1990 aggregate.function.name(),
1991 aggregate.column.as_deref().unwrap_or("star")
1992 )
1993}
1994
1995#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
2003pub struct TopKCandidate {
2004 pub score: u64,
2006 pub tablet: TabletId,
2008 pub row_id: RowId,
2010}
2011
2012pub fn topk_cmp(a: &TopKCandidate, b: &TopKCandidate) -> Ordering {
2016 b.score
2017 .cmp(&a.score)
2018 .then_with(|| a.tablet.cmp(&b.tablet))
2019 .then_with(|| a.row_id.cmp(&b.row_id))
2020}
2021
2022#[derive(Clone, Debug)]
2025pub struct TabletTopK {
2026 pub tablet: TabletId,
2028 pub rows: Vec<TopKCandidate>,
2030 pub unseen_bound: Option<u64>,
2033}
2034
2035#[derive(Clone, Debug, PartialEq, Eq)]
2037pub struct TopKMerge {
2038 pub winners: Vec<TopKCandidate>,
2040 pub refill: Vec<TabletId>,
2043}
2044
2045pub fn merge_top_k(shards: &[TabletTopK], k: usize) -> TopKMerge {
2068 if k == 0 {
2069 return TopKMerge {
2070 winners: Vec::new(),
2071 refill: Vec::new(),
2072 };
2073 }
2074 let mut received: Vec<TopKCandidate> = shards
2075 .iter()
2076 .flat_map(|shard| shard.rows.iter().copied())
2077 .collect();
2078 received.sort_by(topk_cmp);
2079 received.truncate(k);
2080 let mut refill = Vec::new();
2081 if received.len() < k {
2082 for shard in shards {
2083 if shard.unseen_bound.is_some() {
2084 refill.push(shard.tablet);
2085 }
2086 }
2087 } else {
2088 let threshold = received[k - 1];
2089 for shard in shards {
2090 let Some(bound) = shard.unseen_bound else {
2091 continue;
2092 };
2093 let optimistic = TopKCandidate {
2094 score: bound,
2095 tablet: shard.tablet,
2096 row_id: RowId::MIN,
2097 };
2098 if topk_cmp(&optimistic, &threshold) != Ordering::Greater {
2099 refill.push(shard.tablet);
2100 }
2101 }
2102 }
2103 refill.sort();
2104 refill.dedup();
2105 TopKMerge {
2106 winners: received,
2107 refill,
2108 }
2109}
2110
2111pub fn exact_top_k(
2123 k: usize,
2124 initial: Vec<TabletTopK>,
2125 mut refill_batch: impl FnMut(TabletId) -> TabletTopK,
2126) -> DistributedResult<Vec<TopKCandidate>> {
2127 let mut shards: BTreeMap<TabletId, TabletTopK> = initial
2128 .into_iter()
2129 .map(|shard| (shard.tablet, shard))
2130 .collect();
2131 loop {
2132 let ordered: Vec<TabletTopK> = shards.values().cloned().collect();
2133 let merge = merge_top_k(&ordered, k);
2134 if merge.refill.is_empty() {
2135 return Ok(merge.winners);
2136 }
2137 for tablet in merge.refill {
2138 let batch = refill_batch(tablet);
2139 let entry = shards.get_mut(&tablet).ok_or_else(|| {
2140 DistributedError::InvalidPlan(format!(
2141 "top-k refill requested for unknown tablet {tablet}"
2142 ))
2143 })?;
2144 if batch.rows.is_empty() && batch.unseen_bound == entry.unseen_bound {
2145 return Err(DistributedError::InvalidPlan(format!(
2146 "top-k refill for tablet {tablet} made no progress"
2147 )));
2148 }
2149 entry.rows.extend(batch.rows);
2150 entry.unseen_bound = batch.unseen_bound;
2151 }
2152 }
2153}
2154
2155#[derive(Debug, Clone)]
2163pub struct FragmentControl {
2164 pub control: ExecutionControl,
2167 pub max_spill_bytes: u64,
2169 pub authorization_context: Arc<[u8]>,
2172}
2173
2174impl FragmentControl {
2175 pub fn begin_spill(
2179 &self,
2180 manager: &mongreldb_core::SpillManager,
2181 query_id: mongreldb_types::ids::QueryId,
2182 ) -> Result<mongreldb_core::SpillSession, mongreldb_core::SpillError> {
2183 manager.begin_query(query_id, self.max_spill_bytes)
2184 }
2185}
2186
2187#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
2191pub enum ScoreBound {
2192 Unknown,
2195 AtMost(u64),
2197 Exhausted,
2199}
2200
2201#[derive(Debug, Clone)]
2203pub struct BatchFrame {
2204 pub batch: RecordBatch,
2206 pub score_bound: ScoreBound,
2208}
2209
2210impl BatchFrame {
2211 pub fn data(batch: RecordBatch) -> Self {
2213 Self {
2214 batch,
2215 score_bound: ScoreBound::Unknown,
2216 }
2217 }
2218}
2219
2220pub type FragmentStream = BoxStream<'static, DistributedResult<BatchFrame>>;
2222
2223#[derive(Debug)]
2226pub struct TopKRefill {
2227 pub rows: Vec<TopKCandidate>,
2230 pub payload: RecordBatch,
2232 pub unseen_bound: Option<u64>,
2234}
2235
2236#[async_trait::async_trait]
2240pub trait FragmentExecutor: Send + Sync {
2241 async fn execute(
2244 &self,
2245 fragment: &PlanFragment,
2246 inputs: Vec<FragmentStream>,
2247 control: FragmentControl,
2248 ) -> DistributedResult<FragmentStream>;
2249
2250 fn refill_top_k(
2254 &self,
2255 fragment: &PlanFragment,
2256 offset: usize,
2257 limit: usize,
2258 control: FragmentControl,
2259 ) -> DistributedResult<TopKRefill> {
2260 let _ = (fragment, offset, limit, control);
2261 Err(DistributedError::Unsupported(
2262 "top-k refill is not implemented by this executor".to_owned(),
2263 ))
2264 }
2265}
2266
2267#[async_trait::async_trait]
2272pub trait FragmentTransport: Send + Sync {
2273 async fn execute_fragment(
2276 &self,
2277 query_id: QueryId,
2278 fragment: &PlanFragment,
2279 inputs: Vec<FragmentStream>,
2280 control: FragmentControl,
2281 ) -> DistributedResult<FragmentStream>;
2282
2283 fn cancel_fragment(&self, query_id: QueryId, fragment_id: FragmentId) -> DistributedResult<()>;
2285
2286 async fn refill_top_k(
2289 &self,
2290 query_id: QueryId,
2291 fragment: &PlanFragment,
2292 offset: usize,
2293 limit: usize,
2294 control: FragmentControl,
2295 ) -> DistributedResult<TopKRefill> {
2296 let _ = (query_id, fragment, offset, limit, control);
2297 Err(DistributedError::Unsupported(
2298 "top-k refill over this transport is not bound in this wave".to_owned(),
2299 ))
2300 }
2301}
2302
2303pub struct InMemoryTransport {
2307 default_executor: Arc<dyn FragmentExecutor>,
2308 executors: parking_lot::RwLock<HashMap<TabletId, Arc<dyn FragmentExecutor>>>,
2309 started: Mutex<Vec<FragmentId>>,
2310 cancelled: Mutex<Vec<FragmentId>>,
2311 controls: Mutex<HashMap<FragmentId, ExecutionControl>>,
2312 refills: Mutex<Vec<(FragmentId, usize, usize)>>,
2313}
2314
2315impl InMemoryTransport {
2316 pub fn new(default_executor: Arc<dyn FragmentExecutor>) -> Self {
2318 Self {
2319 default_executor,
2320 executors: parking_lot::RwLock::new(HashMap::new()),
2321 started: Mutex::new(Vec::new()),
2322 cancelled: Mutex::new(Vec::new()),
2323 controls: Mutex::new(HashMap::new()),
2324 refills: Mutex::new(Vec::new()),
2325 }
2326 }
2327
2328 pub fn with_executor(self, tablet: TabletId, executor: Arc<dyn FragmentExecutor>) -> Self {
2330 self.executors.write().insert(tablet, executor);
2331 self
2332 }
2333
2334 fn executor_for(&self, assignment: &FragmentAssignment) -> Arc<dyn FragmentExecutor> {
2335 match assignment {
2336 FragmentAssignment::Tablet(tablet) => self
2337 .executors
2338 .read()
2339 .get(tablet)
2340 .cloned()
2341 .unwrap_or_else(|| Arc::clone(&self.default_executor)),
2342 FragmentAssignment::Coordinator => Arc::clone(&self.default_executor),
2343 }
2344 }
2345
2346 pub fn started_fragments(&self) -> Vec<FragmentId> {
2348 self.started.lock().clone()
2349 }
2350
2351 pub fn cancelled_fragments(&self) -> Vec<FragmentId> {
2353 self.cancelled.lock().clone()
2354 }
2355
2356 pub fn refill_log(&self) -> Vec<(FragmentId, usize, usize)> {
2358 self.refills.lock().clone()
2359 }
2360
2361 pub fn control_for(&self, fragment_id: FragmentId) -> Option<ExecutionControl> {
2363 self.controls.lock().get(&fragment_id).cloned()
2364 }
2365}
2366
2367#[async_trait::async_trait]
2368impl FragmentTransport for InMemoryTransport {
2369 async fn execute_fragment(
2370 &self,
2371 _query_id: QueryId,
2372 fragment: &PlanFragment,
2373 inputs: Vec<FragmentStream>,
2374 control: FragmentControl,
2375 ) -> DistributedResult<FragmentStream> {
2376 self.started.lock().push(fragment.fragment_id);
2377 self.controls
2378 .lock()
2379 .insert(fragment.fragment_id, control.control.clone());
2380 self.executor_for(&fragment.assignment)
2381 .execute(fragment, inputs, control)
2382 .await
2383 }
2384
2385 fn cancel_fragment(
2386 &self,
2387 _query_id: QueryId,
2388 fragment_id: FragmentId,
2389 ) -> DistributedResult<()> {
2390 self.cancelled.lock().push(fragment_id);
2391 if let Some(control) = self.controls.lock().get(&fragment_id) {
2392 control.cancel(CancellationReason::ClientRequest);
2393 }
2394 Ok(())
2395 }
2396
2397 async fn refill_top_k(
2398 &self,
2399 _query_id: QueryId,
2400 fragment: &PlanFragment,
2401 offset: usize,
2402 limit: usize,
2403 control: FragmentControl,
2404 ) -> DistributedResult<TopKRefill> {
2405 self.refills
2406 .lock()
2407 .push((fragment.fragment_id, offset, limit));
2408 self.executor_for(&fragment.assignment)
2409 .refill_top_k(fragment, offset, limit, control)
2410 }
2411}
2412
2413pub const REMOTE_FRAGMENT_PROTOCOL_VERSION: u16 = 1;
2422pub const REMOTE_FRAGMENT_SERVICE_ID: u32 = 1;
2425
2426pub const DEFAULT_REMOTE_FRAGMENT_MESSAGE_BYTES: usize = 16 * 1024 * 1024;
2428
2429pub const DEFAULT_REMOTE_FRAGMENT_EXECUTIONS: usize = 1_024;
2431
2432#[derive(Debug, Clone, Serialize, Deserialize)]
2433struct RemoteFragmentEnvelope {
2434 version: u16,
2435 request: RemoteFragmentRequest,
2436}
2437
2438#[derive(Debug, Clone, Serialize, Deserialize)]
2439enum RemoteFragmentRequest {
2440 Start {
2441 query_id: QueryId,
2442 fragment: PlanFragment,
2443 inputs: Vec<Vec<RemoteBatchFrame>>,
2444 max_spill_bytes: u64,
2445 authorization_context: Vec<u8>,
2446 deadline_ms: Option<u64>,
2447 },
2448 Pull {
2449 query_id: QueryId,
2450 fragment_id: FragmentId,
2451 },
2452 Cancel {
2453 query_id: QueryId,
2454 fragment_id: FragmentId,
2455 },
2456 RefillTopK {
2457 query_id: QueryId,
2458 fragment: PlanFragment,
2459 offset: usize,
2460 limit: usize,
2461 authorization_context: Vec<u8>,
2462 deadline_ms: Option<u64>,
2463 },
2464}
2465
2466#[derive(Debug, Clone, Serialize, Deserialize)]
2467struct RemoteFragmentResponseEnvelope {
2468 version: u16,
2469 response: RemoteFragmentResponse,
2470}
2471
2472#[derive(Debug, Clone, Serialize, Deserialize)]
2473enum RemoteFragmentResponse {
2474 Started,
2475 Frame(Option<RemoteBatchFrame>),
2476 Cancelled,
2477 TopKRefill {
2478 rows: Vec<TopKCandidate>,
2479 payload: Vec<u8>,
2480 unseen_bound: Option<u64>,
2481 },
2482 Error(String),
2483}
2484
2485#[derive(Debug, Clone, Serialize, Deserialize)]
2486struct RemoteBatchFrame {
2487 ipc: Vec<u8>,
2488 score_bound: ScoreBound,
2489}
2490
2491type RemoteExecutionKey = (QueryId, FragmentId);
2492
2493struct RemoteExecution {
2494 stream: tokio::sync::Mutex<Option<FragmentStream>>,
2495 control: ExecutionControl,
2496}
2497
2498#[derive(Debug, Default)]
2500pub struct FragmentLifecycleMetrics {
2501 pub starts: std::sync::atomic::AtomicU64,
2503 pub pulls: std::sync::atomic::AtomicU64,
2505 pub cancels: std::sync::atomic::AtomicU64,
2507 pub completes: std::sync::atomic::AtomicU64,
2509 pub bytes_in: std::sync::atomic::AtomicU64,
2511 pub bytes_out: std::sync::atomic::AtomicU64,
2513}
2514
2515impl FragmentLifecycleMetrics {
2516 pub fn snapshot(&self) -> FragmentLifecycleSnapshot {
2518 use std::sync::atomic::Ordering::Relaxed;
2519 FragmentLifecycleSnapshot {
2520 starts: self.starts.load(Relaxed),
2521 pulls: self.pulls.load(Relaxed),
2522 cancels: self.cancels.load(Relaxed),
2523 completes: self.completes.load(Relaxed),
2524 bytes_in: self.bytes_in.load(Relaxed),
2525 bytes_out: self.bytes_out.load(Relaxed),
2526 active_executions: 0,
2527 }
2528 }
2529}
2530
2531#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
2533pub struct FragmentLifecycleSnapshot {
2534 pub starts: u64,
2535 pub pulls: u64,
2536 pub cancels: u64,
2537 pub completes: u64,
2538 pub bytes_in: u64,
2539 pub bytes_out: u64,
2540 pub active_executions: usize,
2541}
2542
2543pub struct RemoteFragmentEndpoint {
2550 executor: Arc<dyn FragmentExecutor>,
2551 executions: parking_lot::Mutex<HashMap<RemoteExecutionKey, Arc<RemoteExecution>>>,
2552 max_executions: usize,
2553 max_message_bytes: usize,
2554 metrics: FragmentLifecycleMetrics,
2555}
2556
2557impl RemoteFragmentEndpoint {
2558 pub fn new(executor: Arc<dyn FragmentExecutor>) -> Self {
2560 Self::with_limits(
2561 executor,
2562 DEFAULT_REMOTE_FRAGMENT_EXECUTIONS,
2563 DEFAULT_REMOTE_FRAGMENT_MESSAGE_BYTES,
2564 )
2565 }
2566
2567 pub fn with_limits(
2569 executor: Arc<dyn FragmentExecutor>,
2570 max_executions: usize,
2571 max_message_bytes: usize,
2572 ) -> Self {
2573 Self {
2574 executor,
2575 executions: parking_lot::Mutex::new(HashMap::new()),
2576 max_executions: max_executions.max(1),
2577 max_message_bytes: max_message_bytes.max(1),
2578 metrics: FragmentLifecycleMetrics::default(),
2579 }
2580 }
2581
2582 pub fn active_executions(&self) -> usize {
2584 self.executions.lock().len()
2585 }
2586
2587 pub fn lifecycle_metrics(&self) -> FragmentLifecycleSnapshot {
2589 let mut snap = self.metrics.snapshot();
2590 snap.active_executions = self.active_executions();
2591 snap
2592 }
2593
2594 pub async fn handle(&self, bytes: &[u8]) -> DistributedResult<Vec<u8>> {
2596 use std::sync::atomic::Ordering::Relaxed;
2597 self.metrics.bytes_in.fetch_add(bytes.len() as u64, Relaxed);
2598 if bytes.len() > self.max_message_bytes {
2599 return Err(DistributedError::RemoteProtocol(format!(
2600 "fragment request is {} bytes; limit is {}",
2601 bytes.len(),
2602 self.max_message_bytes
2603 )));
2604 }
2605 let envelope: RemoteFragmentEnvelope = decode_remote_wire(bytes, self.max_message_bytes)?;
2606 if envelope.version != REMOTE_FRAGMENT_PROTOCOL_VERSION {
2607 return Err(DistributedError::RemoteProtocol(format!(
2608 "unsupported fragment protocol version {}; supported version is {}",
2609 envelope.version, REMOTE_FRAGMENT_PROTOCOL_VERSION
2610 )));
2611 }
2612 let response = match self.handle_request(envelope.request).await {
2613 Ok(response) => response,
2614 Err(error) => RemoteFragmentResponse::Error(error.to_string()),
2615 };
2616 let encoded = encode_remote_wire(
2617 &RemoteFragmentResponseEnvelope {
2618 version: REMOTE_FRAGMENT_PROTOCOL_VERSION,
2619 response,
2620 },
2621 self.max_message_bytes,
2622 )?;
2623 self.metrics
2624 .bytes_out
2625 .fetch_add(encoded.len() as u64, std::sync::atomic::Ordering::Relaxed);
2626 Ok(encoded)
2627 }
2628
2629 async fn handle_request(
2630 &self,
2631 request: RemoteFragmentRequest,
2632 ) -> DistributedResult<RemoteFragmentResponse> {
2633 match request {
2634 RemoteFragmentRequest::Start {
2635 query_id,
2636 fragment,
2637 inputs,
2638 max_spill_bytes,
2639 authorization_context,
2640 deadline_ms,
2641 } => {
2642 if authorization_context.len() > MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES {
2643 return Err(DistributedError::RemoteProtocol(format!(
2644 "fragment authorization context is {} bytes; limit is {}",
2645 authorization_context.len(),
2646 MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES
2647 )));
2648 }
2649 let key = (query_id, fragment.fragment_id);
2650 {
2651 let executions = self.executions.lock();
2652 if executions.contains_key(&key) {
2653 return Err(DistributedError::RemoteProtocol(format!(
2654 "fragment {} for query {query_id} is already running",
2655 fragment.fragment_id
2656 )));
2657 }
2658 if executions.len() >= self.max_executions {
2659 return Err(DistributedError::Reservation {
2660 fragment_id: fragment.fragment_id,
2661 reason: format!(
2662 "worker holds {} remote fragments; limit is {}",
2663 executions.len(),
2664 self.max_executions
2665 ),
2666 });
2667 }
2668 }
2669 let inputs = inputs
2670 .into_iter()
2671 .map(|frames| {
2672 frames
2673 .into_iter()
2674 .map(|frame| decode_remote_frame(frame, self.max_message_bytes))
2675 .collect::<DistributedResult<Vec<_>>>()
2676 .map(|frames| {
2677 Box::pin(stream::iter(frames.into_iter().map(Ok))) as FragmentStream
2678 })
2679 })
2680 .collect::<DistributedResult<Vec<_>>>()?;
2681 let control = deadline_ms.map_or_else(
2682 || ExecutionControl::new(None),
2683 |milliseconds| {
2684 ExecutionControl::with_timeout(Duration::from_millis(milliseconds))
2685 },
2686 );
2687 let execution = Arc::new(RemoteExecution {
2688 stream: tokio::sync::Mutex::new(None),
2689 control: control.clone(),
2690 });
2691 {
2692 let mut executions = self.executions.lock();
2693 if executions.contains_key(&key) {
2694 return Err(DistributedError::RemoteProtocol(format!(
2695 "fragment {} for query {query_id} raced another start",
2696 fragment.fragment_id
2697 )));
2698 }
2699 if executions.len() >= self.max_executions {
2700 return Err(DistributedError::Reservation {
2701 fragment_id: fragment.fragment_id,
2702 reason: "remote fragment limit reached during start".to_owned(),
2703 });
2704 }
2705 executions.insert(key, Arc::clone(&execution));
2706 }
2707 let stream = match self
2708 .executor
2709 .execute(
2710 &fragment,
2711 inputs,
2712 FragmentControl {
2713 control,
2714 max_spill_bytes,
2715 authorization_context: authorization_context.into(),
2716 },
2717 )
2718 .await
2719 {
2720 Ok(stream) => stream,
2721 Err(error) => {
2722 self.executions.lock().remove(&key);
2723 return Err(error);
2724 }
2725 };
2726 if self.executions.lock().get(&key).is_none() {
2727 return Err(DistributedError::Cancelled(
2728 CancellationReason::ClientRequest,
2729 ));
2730 }
2731 if let Err(error) = checkpoint(&execution.control) {
2732 self.executions.lock().remove(&key);
2733 return Err(error);
2734 }
2735 *execution.stream.lock().await = Some(stream);
2736 self.metrics
2737 .starts
2738 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2739 Ok(RemoteFragmentResponse::Started)
2740 }
2741 RemoteFragmentRequest::Pull {
2742 query_id,
2743 fragment_id,
2744 } => {
2745 let key = (query_id, fragment_id);
2746 let execution = self.executions.lock().get(&key).cloned().ok_or_else(|| {
2747 DistributedError::RemoteProtocol(format!(
2748 "fragment {fragment_id} for query {query_id} is not running"
2749 ))
2750 })?;
2751 let next = {
2752 checkpoint(&execution.control)?;
2753 let mut stream = execution.stream.lock().await;
2754 let stream = stream.as_mut().ok_or_else(|| {
2755 DistributedError::RemoteProtocol(format!(
2756 "fragment {fragment_id} for query {query_id} is not ready"
2757 ))
2758 })?;
2759 stream.next().await.transpose()?
2760 };
2761 self.metrics
2762 .pulls
2763 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2764 match next {
2765 Some(frame) => Ok(RemoteFragmentResponse::Frame(Some(encode_remote_frame(
2766 &frame,
2767 self.max_message_bytes,
2768 )?))),
2769 None => {
2770 self.executions.lock().remove(&key);
2771 self.metrics
2772 .completes
2773 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2774 Ok(RemoteFragmentResponse::Frame(None))
2775 }
2776 }
2777 }
2778 RemoteFragmentRequest::Cancel {
2779 query_id,
2780 fragment_id,
2781 } => {
2782 if let Some(execution) = self.executions.lock().remove(&(query_id, fragment_id)) {
2783 execution.control.cancel(CancellationReason::ClientRequest);
2784 self.metrics
2785 .cancels
2786 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2787 }
2788 Ok(RemoteFragmentResponse::Cancelled)
2789 }
2790 RemoteFragmentRequest::RefillTopK {
2791 query_id: _,
2792 fragment,
2793 offset,
2794 limit,
2795 authorization_context,
2796 deadline_ms,
2797 } => {
2798 if authorization_context.len() > MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES {
2799 return Err(DistributedError::RemoteProtocol(format!(
2800 "fragment authorization context is {} bytes; limit is {}",
2801 authorization_context.len(),
2802 MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES
2803 )));
2804 }
2805 let control = deadline_ms.map_or_else(
2806 || ExecutionControl::new(None),
2807 |milliseconds| {
2808 ExecutionControl::with_timeout(Duration::from_millis(milliseconds))
2809 },
2810 );
2811 let refill = self.executor.refill_top_k(
2812 &fragment,
2813 offset,
2814 limit,
2815 FragmentControl {
2816 control,
2817 max_spill_bytes: fragment.max_spill_bytes,
2818 authorization_context: authorization_context.into(),
2819 },
2820 )?;
2821 let payload = encode_record_batch(&refill.payload, self.max_message_bytes)?;
2822 Ok(RemoteFragmentResponse::TopKRefill {
2823 rows: refill.rows,
2824 payload,
2825 unseen_bound: refill.unseen_bound,
2826 })
2827 }
2828 }
2829 }
2830}
2831
2832#[async_trait::async_trait]
2838pub trait FragmentRpcClient: Send + Sync {
2839 async fn call(&self, request: Vec<u8>) -> DistributedResult<Vec<u8>>;
2841}
2842
2843pub struct LoopbackFragmentRpcClient {
2845 endpoint: Arc<RemoteFragmentEndpoint>,
2846}
2847
2848impl LoopbackFragmentRpcClient {
2849 pub fn new(endpoint: Arc<RemoteFragmentEndpoint>) -> Self {
2851 Self { endpoint }
2852 }
2853}
2854
2855#[async_trait::async_trait]
2856impl FragmentRpcClient for LoopbackFragmentRpcClient {
2857 async fn call(&self, request: Vec<u8>) -> DistributedResult<Vec<u8>> {
2858 self.endpoint.handle(&request).await
2859 }
2860}
2861
2862pub struct RemoteFragmentTransport {
2867 default_client: Option<Arc<dyn FragmentRpcClient>>,
2868 clients: parking_lot::RwLock<HashMap<TabletId, Arc<dyn FragmentRpcClient>>>,
2869 active: Arc<parking_lot::Mutex<HashMap<RemoteExecutionKey, Arc<dyn FragmentRpcClient>>>>,
2870 max_message_bytes: usize,
2871}
2872
2873impl RemoteFragmentTransport {
2874 pub fn new(default_client: Arc<dyn FragmentRpcClient>) -> Self {
2876 Self::with_message_limit(default_client, DEFAULT_REMOTE_FRAGMENT_MESSAGE_BYTES)
2877 }
2878
2879 pub fn with_message_limit(
2881 default_client: Arc<dyn FragmentRpcClient>,
2882 max_message_bytes: usize,
2883 ) -> Self {
2884 Self {
2885 default_client: Some(default_client),
2886 clients: parking_lot::RwLock::new(HashMap::new()),
2887 active: Arc::new(parking_lot::Mutex::new(HashMap::new())),
2888 max_message_bytes: max_message_bytes.max(1),
2889 }
2890 }
2891
2892 pub fn routed() -> Self {
2894 Self {
2895 default_client: None,
2896 clients: parking_lot::RwLock::new(HashMap::new()),
2897 active: Arc::new(parking_lot::Mutex::new(HashMap::new())),
2898 max_message_bytes: DEFAULT_REMOTE_FRAGMENT_MESSAGE_BYTES,
2899 }
2900 }
2901
2902 pub fn with_client(self, tablet: TabletId, client: Arc<dyn FragmentRpcClient>) -> Self {
2904 self.clients.write().insert(tablet, client);
2905 self
2906 }
2907
2908 fn client_for(&self, fragment: &PlanFragment) -> DistributedResult<Arc<dyn FragmentRpcClient>> {
2909 let tablet = tablet_of(fragment)?;
2910 self.clients
2911 .read()
2912 .get(&tablet)
2913 .cloned()
2914 .or_else(|| self.default_client.as_ref().map(Arc::clone))
2915 .ok_or_else(|| {
2916 DistributedError::RemoteTransport(format!(
2917 "no authenticated fragment route for tablet {tablet}"
2918 ))
2919 })
2920 }
2921
2922 async fn call(
2923 &self,
2924 client: &Arc<dyn FragmentRpcClient>,
2925 request: RemoteFragmentRequest,
2926 ) -> DistributedResult<RemoteFragmentResponse> {
2927 remote_call(client, request, self.max_message_bytes).await
2928 }
2929}
2930
2931struct RemoteStreamState {
2932 client: Arc<dyn FragmentRpcClient>,
2933 key: RemoteExecutionKey,
2934 active: Arc<parking_lot::Mutex<HashMap<RemoteExecutionKey, Arc<dyn FragmentRpcClient>>>>,
2935 max_message_bytes: usize,
2936 complete: bool,
2937 yielded_error: bool,
2938}
2939
2940impl RemoteStreamState {
2941 fn mark_complete(&mut self) {
2942 self.complete = true;
2943 }
2944}
2945
2946impl Drop for RemoteStreamState {
2947 fn drop(&mut self) {
2948 self.active.lock().remove(&self.key);
2949 if self.complete {
2950 return;
2951 }
2952 let request = RemoteFragmentRequest::Cancel {
2953 query_id: self.key.0,
2954 fragment_id: self.key.1,
2955 };
2956 let client = Arc::clone(&self.client);
2957 let max_message_bytes = self.max_message_bytes;
2958 if let Ok(runtime) = tokio::runtime::Handle::try_current() {
2959 runtime.spawn(async move {
2960 let _ = remote_call(&client, request, max_message_bytes).await;
2961 });
2962 }
2963 }
2964}
2965
2966#[async_trait::async_trait]
2967impl FragmentTransport for RemoteFragmentTransport {
2968 async fn execute_fragment(
2969 &self,
2970 query_id: QueryId,
2971 fragment: &PlanFragment,
2972 inputs: Vec<FragmentStream>,
2973 control: FragmentControl,
2974 ) -> DistributedResult<FragmentStream> {
2975 let client = self.client_for(fragment)?;
2976 let mut wire_inputs = Vec::with_capacity(inputs.len());
2977 for input in inputs {
2978 let frames = drain_stream(input, &control.control).await?;
2979 wire_inputs.push(
2980 frames
2981 .iter()
2982 .map(|frame| encode_remote_frame(frame, self.max_message_bytes))
2983 .collect::<DistributedResult<Vec<_>>>()?,
2984 );
2985 }
2986 let key = (query_id, fragment.fragment_id);
2987 self.active.lock().insert(key, Arc::clone(&client));
2988 let start = self.call(
2989 &client,
2990 RemoteFragmentRequest::Start {
2991 query_id,
2992 fragment: fragment.clone(),
2993 inputs: wire_inputs,
2994 max_spill_bytes: control.max_spill_bytes,
2995 authorization_context: control.authorization_context.to_vec(),
2996 deadline_ms: control
2997 .control
2998 .remaining_duration()
2999 .map(|duration| duration.as_millis().min(u128::from(u64::MAX)) as u64),
3000 },
3001 );
3002 let response = tokio::select! {
3003 response = start => response,
3004 _ = control.control.cancelled() => {
3005 let _ = remote_call(
3006 &client,
3007 RemoteFragmentRequest::Cancel {
3008 query_id,
3009 fragment_id: fragment.fragment_id,
3010 },
3011 self.max_message_bytes,
3012 ).await;
3013 Err(DistributedError::Cancelled(control.control.reason()))
3014 }
3015 };
3016 let response = match response {
3017 Ok(response) => response,
3018 Err(error) => {
3019 self.active.lock().remove(&key);
3020 return Err(error);
3021 }
3022 };
3023 match response {
3024 RemoteFragmentResponse::Started => {}
3025 other => {
3026 self.active.lock().remove(&key);
3027 return Err(unexpected_remote_response("Started", &other));
3028 }
3029 }
3030 let state = RemoteStreamState {
3031 client,
3032 key,
3033 active: Arc::clone(&self.active),
3034 max_message_bytes: self.max_message_bytes,
3035 complete: false,
3036 yielded_error: false,
3037 };
3038 let output = stream::unfold(state, |mut state| async move {
3039 if state.complete || state.yielded_error {
3040 return None;
3041 }
3042 let response = remote_call(
3043 &state.client,
3044 RemoteFragmentRequest::Pull {
3045 query_id: state.key.0,
3046 fragment_id: state.key.1,
3047 },
3048 state.max_message_bytes,
3049 )
3050 .await;
3051 match response {
3052 Ok(RemoteFragmentResponse::Frame(Some(frame))) => {
3053 let item = decode_remote_frame(frame, state.max_message_bytes);
3054 if item.is_err() {
3055 state.yielded_error = true;
3056 }
3057 Some((item, state))
3058 }
3059 Ok(RemoteFragmentResponse::Frame(None)) => {
3060 state.mark_complete();
3061 None
3062 }
3063 Ok(other) => {
3064 state.yielded_error = true;
3065 Some((Err(unexpected_remote_response("Frame", &other)), state))
3066 }
3067 Err(error) => {
3068 state.yielded_error = true;
3069 Some((Err(error), state))
3070 }
3071 }
3072 });
3073 Ok(Box::pin(output))
3074 }
3075
3076 fn cancel_fragment(&self, query_id: QueryId, fragment_id: FragmentId) -> DistributedResult<()> {
3077 let key = (query_id, fragment_id);
3078 let Some(client) = self.active.lock().remove(&key) else {
3079 return Ok(());
3080 };
3081 let max_message_bytes = self.max_message_bytes;
3082 let runtime = tokio::runtime::Handle::try_current().map_err(|error| {
3083 DistributedError::RemoteTransport(format!(
3084 "cannot schedule fragment cancellation outside Tokio: {error}"
3085 ))
3086 })?;
3087 runtime.spawn(async move {
3088 let _ = remote_call(
3089 &client,
3090 RemoteFragmentRequest::Cancel {
3091 query_id,
3092 fragment_id,
3093 },
3094 max_message_bytes,
3095 )
3096 .await;
3097 });
3098 Ok(())
3099 }
3100
3101 async fn refill_top_k(
3102 &self,
3103 query_id: QueryId,
3104 fragment: &PlanFragment,
3105 offset: usize,
3106 limit: usize,
3107 control: FragmentControl,
3108 ) -> DistributedResult<TopKRefill> {
3109 let client = self.client_for(fragment)?;
3110 match self
3111 .call(
3112 &client,
3113 RemoteFragmentRequest::RefillTopK {
3114 query_id,
3115 fragment: fragment.clone(),
3116 offset,
3117 limit,
3118 authorization_context: control.authorization_context.to_vec(),
3119 deadline_ms: control
3120 .control
3121 .remaining_duration()
3122 .map(|duration| duration.as_millis().min(u128::from(u64::MAX)) as u64),
3123 },
3124 )
3125 .await?
3126 {
3127 RemoteFragmentResponse::TopKRefill {
3128 rows,
3129 payload,
3130 unseen_bound,
3131 } => Ok(TopKRefill {
3132 rows,
3133 payload: decode_record_batch(&payload, self.max_message_bytes)?,
3134 unseen_bound,
3135 }),
3136 other => Err(unexpected_remote_response("TopKRefill", &other)),
3137 }
3138 }
3139}
3140
3141async fn remote_call(
3142 client: &Arc<dyn FragmentRpcClient>,
3143 request: RemoteFragmentRequest,
3144 max_message_bytes: usize,
3145) -> DistributedResult<RemoteFragmentResponse> {
3146 let request = encode_remote_wire(
3147 &RemoteFragmentEnvelope {
3148 version: REMOTE_FRAGMENT_PROTOCOL_VERSION,
3149 request,
3150 },
3151 max_message_bytes,
3152 )?;
3153 let response = client.call(request).await?;
3154 let envelope: RemoteFragmentResponseEnvelope =
3155 decode_remote_wire(&response, max_message_bytes)?;
3156 if envelope.version != REMOTE_FRAGMENT_PROTOCOL_VERSION {
3157 return Err(DistributedError::RemoteProtocol(format!(
3158 "peer answered with fragment protocol version {}; supported version is {}",
3159 envelope.version, REMOTE_FRAGMENT_PROTOCOL_VERSION
3160 )));
3161 }
3162 match envelope.response {
3163 RemoteFragmentResponse::Error(message) => Err(DistributedError::RemoteTransport(message)),
3164 response => Ok(response),
3165 }
3166}
3167
3168fn remote_wire_options() -> impl Options {
3169 bincode::DefaultOptions::new()
3170 .with_fixint_encoding()
3171 .reject_trailing_bytes()
3172}
3173
3174fn encode_remote_wire<T: Serialize>(
3175 value: &T,
3176 max_message_bytes: usize,
3177) -> DistributedResult<Vec<u8>> {
3178 let bytes = remote_wire_options()
3179 .serialize(value)
3180 .map_err(|error| DistributedError::RemoteProtocol(error.to_string()))?;
3181 if bytes.len() > max_message_bytes {
3182 return Err(DistributedError::RemoteProtocol(format!(
3183 "encoded fragment message is {} bytes; limit is {max_message_bytes}",
3184 bytes.len()
3185 )));
3186 }
3187 Ok(bytes)
3188}
3189
3190fn decode_remote_wire<T: for<'de> Deserialize<'de>>(
3191 bytes: &[u8],
3192 max_message_bytes: usize,
3193) -> DistributedResult<T> {
3194 if bytes.len() > max_message_bytes {
3195 return Err(DistributedError::RemoteProtocol(format!(
3196 "fragment message is {} bytes; limit is {max_message_bytes}",
3197 bytes.len()
3198 )));
3199 }
3200 remote_wire_options()
3201 .with_limit(max_message_bytes as u64)
3202 .deserialize(bytes)
3203 .map_err(|error| DistributedError::RemoteProtocol(error.to_string()))
3204}
3205
3206fn encode_record_batch(
3207 batch: &RecordBatch,
3208 max_message_bytes: usize,
3209) -> DistributedResult<Vec<u8>> {
3210 let mut ipc = Vec::new();
3211 {
3212 let mut writer = StreamWriter::try_new(&mut ipc, &batch.schema())?;
3213 writer.write(batch)?;
3214 writer.finish()?;
3215 }
3216 if ipc.len() > max_message_bytes {
3217 return Err(DistributedError::RemoteProtocol(format!(
3218 "Arrow IPC batch is {} bytes; limit is {max_message_bytes}",
3219 ipc.len()
3220 )));
3221 }
3222 Ok(ipc)
3223}
3224
3225fn decode_record_batch(ipc: &[u8], max_message_bytes: usize) -> DistributedResult<RecordBatch> {
3226 if ipc.len() > max_message_bytes {
3227 return Err(DistributedError::RemoteProtocol(format!(
3228 "Arrow IPC batch is {} bytes; limit is {max_message_bytes}",
3229 ipc.len()
3230 )));
3231 }
3232 let mut reader = StreamReader::try_new(Cursor::new(ipc), None)?;
3233 let batch = reader.next().transpose()?.ok_or_else(|| {
3234 DistributedError::RemoteProtocol("Arrow IPC frame contains no record batch".to_owned())
3235 })?;
3236 if reader.next().transpose()?.is_some() {
3237 return Err(DistributedError::RemoteProtocol(
3238 "Arrow IPC frame contains more than one record batch".to_owned(),
3239 ));
3240 }
3241 Ok(batch)
3242}
3243
3244fn encode_remote_frame(
3245 frame: &BatchFrame,
3246 max_message_bytes: usize,
3247) -> DistributedResult<RemoteBatchFrame> {
3248 Ok(RemoteBatchFrame {
3249 ipc: encode_record_batch(&frame.batch, max_message_bytes)?,
3250 score_bound: frame.score_bound,
3251 })
3252}
3253
3254fn decode_remote_frame(
3255 frame: RemoteBatchFrame,
3256 max_message_bytes: usize,
3257) -> DistributedResult<BatchFrame> {
3258 Ok(BatchFrame {
3259 batch: decode_record_batch(&frame.ipc, max_message_bytes)?,
3260 score_bound: frame.score_bound,
3261 })
3262}
3263
3264fn unexpected_remote_response(expected: &str, actual: &RemoteFragmentResponse) -> DistributedError {
3265 DistributedError::RemoteProtocol(format!(
3266 "expected remote {expected} response, got {actual:?}"
3267 ))
3268}
3269
3270pub trait FragmentTableSource: Send + Sync {
3274 fn scan(
3276 &self,
3277 table: &str,
3278 tablet: TabletId,
3279 include_row_id: bool,
3280 control: Option<&FragmentControl>,
3281 ) -> DistributedResult<Vec<RecordBatch>>;
3282
3283 fn schema(&self, table: &str, tablet: TabletId) -> DistributedResult<Option<SchemaRef>>;
3285}
3286
3287pub trait FragmentDatabaseProvider: Send + Sync {
3289 fn database(&self, tablet: TabletId) -> Option<Arc<mongreldb_core::Database>>;
3291}
3292
3293pub trait FragmentAuthorizationResolver: Send + Sync {
3295 fn resolve(
3298 &self,
3299 database: &mongreldb_core::Database,
3300 context: &[u8],
3301 ) -> DistributedResult<Option<mongreldb_core::Principal>>;
3302}
3303
3304pub struct CoreFragmentTableSource {
3306 databases: Arc<dyn FragmentDatabaseProvider>,
3307 authorization: Arc<dyn FragmentAuthorizationResolver>,
3308}
3309
3310impl CoreFragmentTableSource {
3311 pub fn new(
3313 databases: Arc<dyn FragmentDatabaseProvider>,
3314 authorization: Arc<dyn FragmentAuthorizationResolver>,
3315 ) -> Self {
3316 Self {
3317 databases,
3318 authorization,
3319 }
3320 }
3321}
3322
3323impl FragmentTableSource for CoreFragmentTableSource {
3324 fn scan(
3325 &self,
3326 table: &str,
3327 tablet: TabletId,
3328 include_row_id: bool,
3329 control: Option<&FragmentControl>,
3330 ) -> DistributedResult<Vec<RecordBatch>> {
3331 let control = control.ok_or_else(|| {
3332 DistributedError::RemoteProtocol(
3333 "core tablet scan requires fragment authorization control".to_owned(),
3334 )
3335 })?;
3336 checkpoint(&control.control)?;
3337 let database = self.databases.database(tablet).ok_or_else(|| {
3338 DistributedError::RemoteTransport(format!("this node does not host tablet {tablet}"))
3339 })?;
3340 let principal = self
3341 .authorization
3342 .resolve(&database, &control.authorization_context)?;
3343 let schema = database
3344 .table(table)
3345 .map_err(|error| DistributedError::RemoteTransport(error.to_string()))?
3346 .lock()
3347 .schema()
3348 .clone();
3349 let rows = database
3350 .query_as_principal_controlled(
3351 table,
3352 &mongreldb_core::Query {
3353 conditions: Vec::new(),
3354 limit: None,
3359 offset: 0,
3360 },
3361 None,
3362 principal.as_ref(),
3363 &control.control,
3364 )
3365 .map_err(|error| DistributedError::RemoteTransport(error.to_string()))?;
3366 let batch = crate::arrow_conv::rows_to_batch(&rows, &schema)
3367 .map_err(|error| DistributedError::Arrow(error.to_string()))?;
3368 if !include_row_id {
3369 return Ok(vec![batch]);
3370 }
3371 let mut fields = batch.schema().fields().iter().cloned().collect::<Vec<_>>();
3372 fields.push(Arc::new(Field::new(
3373 TOPK_ROWID_COLUMN,
3374 DataType::UInt64,
3375 false,
3376 )));
3377 let mut columns = batch.columns().to_vec();
3378 columns.push(Arc::new(UInt64Array::from(
3379 rows.iter().map(|row| row.row_id.0).collect::<Vec<_>>(),
3380 )));
3381 let batch = RecordBatch::try_new(Arc::new(Schema::new(fields)), columns)?;
3382 Ok(vec![batch])
3383 }
3384
3385 fn schema(&self, _table: &str, _tablet: TabletId) -> DistributedResult<Option<SchemaRef>> {
3386 Ok(None)
3389 }
3390}
3391
3392#[derive(Default)]
3395pub struct InMemoryTableStore {
3396 tables: parking_lot::RwLock<HashMap<(String, TabletId), Vec<RecordBatch>>>,
3397 schemas: parking_lot::RwLock<HashMap<String, SchemaRef>>,
3398}
3399
3400impl InMemoryTableStore {
3401 pub fn new() -> Self {
3403 Self::default()
3404 }
3405
3406 pub fn insert(&self, table: &str, tablet: TabletId, batch: RecordBatch) {
3409 self.schemas
3410 .write()
3411 .entry(table.to_owned())
3412 .or_insert_with(|| batch.schema());
3413 self.tables
3414 .write()
3415 .entry((table.to_owned(), tablet))
3416 .or_default()
3417 .push(batch);
3418 }
3419
3420 pub fn register_schema(&self, table: &str, schema: SchemaRef) {
3422 self.schemas.write().insert(table.to_owned(), schema);
3423 }
3424
3425 pub fn snapshot(&self, table: &str, tablet: TabletId) -> Vec<RecordBatch> {
3427 self.tables
3428 .read()
3429 .get(&(table.to_owned(), tablet))
3430 .cloned()
3431 .unwrap_or_default()
3432 }
3433
3434 pub fn schema(&self, table: &str) -> Option<SchemaRef> {
3436 self.schemas.read().get(table).cloned()
3437 }
3438}
3439
3440impl FragmentTableSource for InMemoryTableStore {
3441 fn scan(
3442 &self,
3443 table: &str,
3444 tablet: TabletId,
3445 _include_row_id: bool,
3446 _control: Option<&FragmentControl>,
3447 ) -> DistributedResult<Vec<RecordBatch>> {
3448 Ok(self.snapshot(table, tablet))
3449 }
3450
3451 fn schema(&self, table: &str, _tablet: TabletId) -> DistributedResult<Option<SchemaRef>> {
3452 Ok(self.schema(table))
3453 }
3454}
3455
3456pub struct InMemoryFragmentExecutor {
3462 store: Arc<dyn FragmentTableSource>,
3463 topk_emit_batch: Option<usize>,
3467}
3468
3469impl InMemoryFragmentExecutor {
3470 pub fn new(store: Arc<InMemoryTableStore>) -> Self {
3472 Self {
3473 store,
3474 topk_emit_batch: None,
3475 }
3476 }
3477
3478 pub fn with_topk_emit_batch(store: Arc<InMemoryTableStore>, batch: usize) -> Self {
3480 Self {
3481 store,
3482 topk_emit_batch: Some(batch),
3483 }
3484 }
3485
3486 pub fn from_source(store: Arc<dyn FragmentTableSource>) -> Self {
3489 Self {
3490 store,
3491 topk_emit_batch: None,
3492 }
3493 }
3494
3495 fn scan_batches(
3497 &self,
3498 fragment: &PlanFragment,
3499 control: Option<&FragmentControl>,
3500 ) -> DistributedResult<Vec<RecordBatch>> {
3501 let tablet = tablet_of(fragment)?;
3502 let scan = fragment
3503 .operators
3504 .iter()
3505 .find_map(|operator| match operator {
3506 FragmentOperator::TabletScan {
3507 table,
3508 predicate,
3509 projection,
3510 } => Some((table, predicate, projection)),
3511 _ => None,
3512 })
3513 .ok_or_else(|| {
3514 DistributedError::InvalidPlan(format!(
3515 "fragment {} has no tablet scan",
3516 fragment.fragment_id
3517 ))
3518 })?;
3519 if scan.1.is_some() {
3520 return Err(DistributedError::Unsupported(
3521 "tablet predicate execution is not bound to the fragment operator engine"
3522 .to_owned(),
3523 ));
3524 }
3525 let include_row_id = fragment
3526 .operators
3527 .iter()
3528 .any(|operator| matches!(operator, FragmentOperator::DistributedTopK { .. }));
3529 let mut batches = self.store.scan(scan.0, tablet, include_row_id, control)?;
3530 if batches.is_empty() {
3531 if let Some(schema) = self.store.schema(scan.0, tablet)? {
3532 batches = vec![RecordBatch::new_empty(schema)];
3533 }
3534 }
3535 if !scan.2.is_empty() {
3536 batches = project_batches(&batches, scan.2)?;
3537 }
3538 Ok(batches)
3539 }
3540}
3541
3542#[async_trait::async_trait]
3543impl FragmentExecutor for InMemoryFragmentExecutor {
3544 async fn execute(
3545 &self,
3546 fragment: &PlanFragment,
3547 inputs: Vec<FragmentStream>,
3548 control: FragmentControl,
3549 ) -> DistributedResult<FragmentStream> {
3550 let mut batches: Vec<RecordBatch> = Vec::new();
3551 let mut inputs = inputs.into_iter();
3552 let mut bound = ScoreBound::Unknown;
3553 for operator in &fragment.operators {
3554 checkpoint(&control.control)?;
3555 match operator {
3556 FragmentOperator::TabletScan { .. } => {
3557 batches = self.scan_batches(fragment, Some(&control))?;
3558 }
3559 FragmentOperator::RemoteExchangeSource { .. } => {
3560 let input = inputs.next().ok_or_else(|| {
3561 DistributedError::InvalidPlan(format!(
3562 "fragment {} is missing an exchange input stream",
3563 fragment.fragment_id
3564 ))
3565 })?;
3566 let frames = drain_stream(input, &control.control).await?;
3567 batches.extend(frames.into_iter().map(|frame| frame.batch));
3568 }
3569 FragmentOperator::PartialAggregate {
3570 group_by,
3571 aggregates,
3572 } => {
3573 batches = vec![partial_aggregate_batches(&batches, group_by, aggregates)?];
3574 }
3575 FragmentOperator::FinalAggregate {
3576 group_by,
3577 aggregates,
3578 } => {
3579 batches = vec![final_aggregate_batches(&batches, group_by, aggregates)?];
3580 }
3581 FragmentOperator::MergeSort { keys, limit } => {
3582 batches = sort_batches_local(&batches, keys, *limit)?;
3583 }
3584 FragmentOperator::DistributedTopK { k, score } => {
3585 let tablet = tablet_of(fragment)?;
3586 let input = prepare_top_k(&batches, &score.column, tablet)?;
3587 let emit = self.topk_emit_batch.unwrap_or(*k).min(*k);
3588 let (_rows, payload, next) = input.emit(0, emit);
3589 batches = vec![payload];
3590 bound = match next {
3591 Some(next) => ScoreBound::AtMost(next),
3592 None => ScoreBound::Exhausted,
3593 };
3594 }
3595 FragmentOperator::DistributedLimit { limit } => {
3596 batches = limit_batches(&batches, *limit);
3597 }
3598 FragmentOperator::RemoteExchangeSink { .. } => {
3599 }
3602 FragmentOperator::DistributedHashJoin { .. }
3603 | FragmentOperator::BroadcastJoin { .. }
3604 | FragmentOperator::RepartitionJoin { .. } => {
3605 return Err(DistributedError::Unsupported(
3606 "join execution binding lands with the tablet wave".to_owned(),
3607 ));
3608 }
3609 }
3610 }
3611 let mut frames: Vec<BatchFrame> = batches.into_iter().map(BatchFrame::data).collect();
3612 if bound != ScoreBound::Unknown {
3613 if let Some(last) = frames.last_mut() {
3614 last.score_bound = bound;
3615 }
3616 }
3617 Ok(Box::pin(stream::iter(frames.into_iter().map(Ok))))
3618 }
3619
3620 fn refill_top_k(
3621 &self,
3622 fragment: &PlanFragment,
3623 offset: usize,
3624 limit: usize,
3625 control: FragmentControl,
3626 ) -> DistributedResult<TopKRefill> {
3627 let tablet = tablet_of(fragment)?;
3628 let score = fragment
3629 .operators
3630 .iter()
3631 .find_map(|operator| match operator {
3632 FragmentOperator::DistributedTopK { score, .. } => Some(score.clone()),
3633 _ => None,
3634 })
3635 .ok_or_else(|| {
3636 DistributedError::InvalidPlan(format!(
3637 "fragment {} has no distributed top-k operator",
3638 fragment.fragment_id
3639 ))
3640 })?;
3641 let batches = self.scan_batches(fragment, Some(&control))?;
3642 let input = prepare_top_k(&batches, &score.column, tablet)?;
3643 let (rows, payload, unseen_bound) = input.emit(offset, limit);
3644 Ok(TopKRefill {
3645 rows,
3646 payload,
3647 unseen_bound,
3648 })
3649 }
3650}
3651
3652fn checkpoint(control: &ExecutionControl) -> DistributedResult<()> {
3658 control
3659 .checkpoint()
3660 .map_err(|_| DistributedError::Cancelled(control.reason()))
3661}
3662
3663fn tablet_of(fragment: &PlanFragment) -> DistributedResult<TabletId> {
3665 match fragment.assignment {
3666 FragmentAssignment::Tablet(tablet) => Ok(tablet),
3667 FragmentAssignment::Coordinator => Err(DistributedError::InvalidPlan(format!(
3668 "fragment {} operator requires a tablet assignment",
3669 fragment.fragment_id
3670 ))),
3671 }
3672}
3673
3674async fn drain_stream(
3676 mut stream: FragmentStream,
3677 control: &ExecutionControl,
3678) -> DistributedResult<Vec<BatchFrame>> {
3679 let mut frames = Vec::new();
3680 while let Some(item) = stream.next().await {
3681 checkpoint(control)?;
3682 frames.push(item?);
3683 }
3684 Ok(frames)
3685}
3686
3687fn concat_all(batches: &[RecordBatch]) -> DistributedResult<Option<RecordBatch>> {
3689 let Some(first) = batches.first() else {
3690 return Ok(None);
3691 };
3692 if batches.len() == 1 {
3693 return Ok(Some(first.clone()));
3694 }
3695 Ok(Some(concat_batches(&first.schema(), batches)?))
3696}
3697
3698fn project_batches(
3700 batches: &[RecordBatch],
3701 projection: &[String],
3702) -> DistributedResult<Vec<RecordBatch>> {
3703 batches
3704 .iter()
3705 .map(|batch| {
3706 let schema = batch.schema();
3707 let indexes: Vec<usize> = projection
3708 .iter()
3709 .map(|name| {
3710 schema.index_of(name).map_err(|_| {
3711 DistributedError::InvalidPlan(format!(
3712 "projection column `{name}` not in schema"
3713 ))
3714 })
3715 })
3716 .collect::<DistributedResult<Vec<usize>>>()?;
3717 Ok(batch.project(&indexes)?)
3718 })
3719 .collect()
3720}
3721
3722fn row_converter(
3724 schema: &Schema,
3725 keys: &[SortKey],
3726) -> DistributedResult<(RowConverter, Vec<usize>)> {
3727 let mut fields = Vec::with_capacity(keys.len());
3728 let mut indexes = Vec::with_capacity(keys.len());
3729 for key in keys {
3730 let index = schema.index_of(&key.column).map_err(|_| {
3731 DistributedError::InvalidPlan(format!("sort key `{}` not in schema", key.column))
3732 })?;
3733 let options = arrow::compute::SortOptions {
3737 descending: key.descending,
3738 nulls_first: key.descending,
3739 };
3740 fields.push(SortField::new_with_options(
3741 schema.field(index).data_type().clone(),
3742 options,
3743 ));
3744 indexes.push(index);
3745 }
3746 Ok((RowConverter::new(fields)?, indexes))
3747}
3748
3749fn take_rows(batch: &RecordBatch, order: &[usize]) -> DistributedResult<Vec<RecordBatch>> {
3751 let mut out = Vec::new();
3752 for chunk in order.chunks(COORDINATOR_OUTPUT_BATCH_ROWS) {
3753 let indices = UInt32Array::from(
3754 chunk
3755 .iter()
3756 .map(|index| u32::try_from(*index).unwrap_or(u32::MAX))
3757 .collect::<Vec<u32>>(),
3758 );
3759 let mut columns = Vec::with_capacity(batch.num_columns());
3760 for column in batch.columns() {
3761 columns.push(take(column, &indices, None)?);
3762 }
3763 out.push(RecordBatch::try_new(batch.schema(), columns)?);
3764 }
3765 if out.is_empty() {
3766 out.push(RecordBatch::new_empty(batch.schema()));
3767 }
3768 Ok(out)
3769}
3770
3771fn emit_interleaved(
3774 streams: &[RecordBatch],
3775 order: &[(usize, usize)],
3776) -> DistributedResult<Vec<RecordBatch>> {
3777 let Some(first) = streams.first() else {
3778 return Ok(Vec::new());
3779 };
3780 if order.is_empty() {
3781 return Ok(vec![RecordBatch::new_empty(first.schema())]);
3782 }
3783 let schema = first.schema();
3784 let mut out = Vec::new();
3785 for chunk in order.chunks(COORDINATOR_OUTPUT_BATCH_ROWS) {
3786 let mut columns = Vec::with_capacity(schema.fields().len());
3787 for column_index in 0..schema.fields().len() {
3788 let refs: Vec<&dyn Array> = streams
3789 .iter()
3790 .map(|batch| batch.column(column_index).as_ref())
3791 .collect();
3792 columns.push(interleave(&refs, chunk)?);
3793 }
3794 out.push(RecordBatch::try_new(schema.clone(), columns)?);
3795 }
3796 Ok(out)
3797}
3798
3799fn sort_batches_local(
3802 batches: &[RecordBatch],
3803 keys: &[SortKey],
3804 limit: Option<usize>,
3805) -> DistributedResult<Vec<RecordBatch>> {
3806 let Some(batch) = concat_all(batches)? else {
3807 return Ok(Vec::new());
3808 };
3809 if batch.num_rows() == 0 {
3810 return Ok(vec![RecordBatch::new_empty(batch.schema())]);
3811 }
3812 let (converter, indexes) = row_converter(&batch.schema(), keys)?;
3813 let columns: Vec<ArrayRef> = indexes
3814 .iter()
3815 .map(|index| batch.column(*index).clone())
3816 .collect();
3817 let rows = converter.convert_columns(&columns)?;
3818 let mut order: Vec<usize> = (0..batch.num_rows()).collect();
3819 order.sort_by(|left, right| {
3820 rows.row(*left)
3821 .cmp(&rows.row(*right))
3822 .then_with(|| left.cmp(right))
3823 });
3824 if let Some(limit) = limit {
3825 order.truncate(limit);
3826 }
3827 take_rows(&batch, &order)
3828}
3829
3830fn merge_sorted_streams(
3834 streams: &[RecordBatch],
3835 keys: &[SortKey],
3836 limit: Option<usize>,
3837) -> DistributedResult<Vec<RecordBatch>> {
3838 struct MergeItem {
3840 key: Vec<u8>,
3841 stream: usize,
3842 }
3843 impl PartialEq for MergeItem {
3844 fn eq(&self, other: &Self) -> bool {
3845 self.key == other.key && self.stream == other.stream
3846 }
3847 }
3848 impl Eq for MergeItem {}
3849 impl PartialOrd for MergeItem {
3850 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
3851 Some(self.cmp(other))
3852 }
3853 }
3854 impl Ord for MergeItem {
3855 fn cmp(&self, other: &Self) -> Ordering {
3856 self.key
3857 .cmp(&other.key)
3858 .then_with(|| self.stream.cmp(&other.stream))
3859 }
3860 }
3861
3862 let mut per_stream: Vec<Vec<Vec<u8>>> = Vec::with_capacity(streams.len());
3863 for batch in streams {
3864 let (converter, indexes) = row_converter(&batch.schema(), keys)?;
3865 let columns: Vec<ArrayRef> = indexes
3866 .iter()
3867 .map(|index| batch.column(*index).clone())
3868 .collect();
3869 let rows = converter.convert_columns(&columns)?;
3870 per_stream.push(
3871 (0..batch.num_rows())
3872 .map(|row| rows.row(row).as_ref().to_vec())
3873 .collect(),
3874 );
3875 }
3876 let mut cursors: Vec<usize> = vec![0; streams.len()];
3877 let mut heap = BinaryHeap::new();
3878 for (stream, keys) in per_stream.iter().enumerate() {
3879 if let Some(key) = keys.first() {
3880 heap.push(std::cmp::Reverse(MergeItem {
3881 key: key.clone(),
3882 stream,
3883 }));
3884 }
3885 }
3886 let mut order = Vec::new();
3887 while let Some(std::cmp::Reverse(item)) = heap.pop() {
3888 let row = cursors[item.stream];
3889 order.push((item.stream, row));
3890 if limit.is_some_and(|limit| order.len() >= limit) {
3891 break;
3892 }
3893 cursors[item.stream] += 1;
3894 let cursor = cursors[item.stream];
3895 if let Some(key) = per_stream[item.stream].get(cursor) {
3896 heap.push(std::cmp::Reverse(MergeItem {
3897 key: key.clone(),
3898 stream: item.stream,
3899 }));
3900 }
3901 }
3902 emit_interleaved(streams, &order)
3903}
3904
3905fn limit_batches(batches: &[RecordBatch], limit: usize) -> Vec<RecordBatch> {
3907 let mut remaining = limit;
3908 let mut out = Vec::new();
3909 for batch in batches {
3910 if remaining == 0 {
3911 break;
3912 }
3913 let rows = batch.num_rows().min(remaining);
3914 if rows == 0 {
3915 continue;
3916 }
3917 out.push(if rows == batch.num_rows() {
3918 batch.clone()
3919 } else {
3920 batch.slice(0, rows)
3921 });
3922 remaining -= rows;
3923 }
3924 out
3925}
3926
3927fn repartition_frames(
3931 frames: &[BatchFrame],
3932 keys: &[String],
3933 width: usize,
3934 index: usize,
3935) -> DistributedResult<Vec<BatchFrame>> {
3936 let batches: Vec<RecordBatch> = frames.iter().map(|frame| frame.batch.clone()).collect();
3937 let Some(batch) = concat_all(&batches)? else {
3938 return Ok(Vec::new());
3939 };
3940 if batch.num_rows() == 0 || width <= 1 {
3941 return Ok(vec![BatchFrame::data(batch)]);
3942 }
3943 let schema = batch.schema();
3944 let mut key_columns = Vec::with_capacity(keys.len());
3945 for key in keys {
3946 let column_index = schema.index_of(key).map_err(|_| {
3947 DistributedError::InvalidPlan(format!("repartition key `{key}` not in schema"))
3948 })?;
3949 key_columns.push(batch.column(column_index).clone());
3950 }
3951 let converter = RowConverter::new(
3952 key_columns
3953 .iter()
3954 .map(|column| SortField::new(column.data_type().clone()))
3955 .collect::<Vec<SortField>>(),
3956 )?;
3957 let rows = converter.convert_columns(&key_columns)?;
3958 let mut order = Vec::new();
3959 for row in 0..batch.num_rows() {
3960 let bucket = (fnv1a64(rows.row(row).as_ref()) % width as u64) as usize;
3961 if bucket == index {
3962 order.push(row);
3963 }
3964 }
3965 Ok(take_rows(&batch, &order)?
3966 .into_iter()
3967 .map(BatchFrame::data)
3968 .collect())
3969}
3970
3971fn score_key(array: &dyn Array, row: usize) -> DistributedResult<u64> {
3974 if array.is_null(row) {
3975 return Ok(0);
3976 }
3977 match array.data_type() {
3978 DataType::UInt64 => Ok(array
3979 .as_any()
3980 .downcast_ref::<UInt64Array>()
3981 .expect("type checked")
3982 .value(row)),
3983 DataType::Int64 => {
3984 let value = array
3985 .as_any()
3986 .downcast_ref::<Int64Array>()
3987 .expect("type checked")
3988 .value(row);
3989 Ok((value as u64) ^ (1_u64 << 63))
3990 }
3991 DataType::Float64 => {
3992 let bits = array
3993 .as_any()
3994 .downcast_ref::<Float64Array>()
3995 .expect("type checked")
3996 .value(row)
3997 .to_bits();
3998 Ok(if bits & (1_u64 << 63) != 0 {
3999 !bits
4000 } else {
4001 bits | (1_u64 << 63)
4002 })
4003 }
4004 other => Err(DistributedError::Unsupported(format!(
4005 "top-k score column of type {other} is not supported in this wave"
4006 ))),
4007 }
4008}
4009
4010fn row_ids(batch: &RecordBatch) -> DistributedResult<UInt64Array> {
4012 let index = batch.schema().index_of(TOPK_ROWID_COLUMN).map_err(|_| {
4013 DistributedError::InvalidPlan(format!(
4014 "top-k stream is missing the `{TOPK_ROWID_COLUMN}` column"
4015 ))
4016 })?;
4017 let array = batch.column(index);
4018 if array.data_type() != &DataType::UInt64 {
4019 return Err(DistributedError::InvalidPlan(format!(
4020 "`{TOPK_ROWID_COLUMN}` must be UInt64, found {}",
4021 array.data_type()
4022 )));
4023 }
4024 Ok(array
4025 .as_any()
4026 .downcast_ref::<UInt64Array>()
4027 .expect("type checked")
4028 .clone())
4029}
4030
4031struct TopKInput {
4034 batch: RecordBatch,
4035 candidates: Vec<TopKCandidate>,
4037 positions: Vec<usize>,
4039}
4040
4041impl TopKInput {
4042 fn emit(&self, offset: usize, limit: usize) -> (Vec<TopKCandidate>, RecordBatch, Option<u64>) {
4046 let offset = offset.min(self.candidates.len());
4047 let end = (offset + limit).min(self.candidates.len());
4048 let rows = self.candidates[offset..end].to_vec();
4049 let positions = &self.positions[offset..end];
4050 let payload = take_rows(&self.batch, positions)
4051 .unwrap_or_else(|_| vec![RecordBatch::new_empty(self.batch.schema())])
4052 .into_iter()
4053 .next()
4054 .unwrap_or_else(|| RecordBatch::new_empty(self.batch.schema()));
4055 let bound = self.candidates.get(end).map(|candidate| candidate.score);
4056 (rows, payload, bound)
4057 }
4058}
4059
4060fn prepare_top_k(
4062 batches: &[RecordBatch],
4063 score_column: &str,
4064 tablet: TabletId,
4065) -> DistributedResult<TopKInput> {
4066 let Some(batch) = concat_all(batches)? else {
4067 return Err(DistributedError::InvalidPlan(
4068 "top-k input has no batches".to_owned(),
4069 ));
4070 };
4071 let schema = batch.schema();
4072 let score_index = schema.index_of(score_column).map_err(|_| {
4073 DistributedError::InvalidPlan(format!("top-k score column `{score_column}` not in schema"))
4074 })?;
4075 let score_array = batch.column(score_index).clone();
4076 let row_id_array = row_ids(&batch)?;
4077 let mut candidates = Vec::with_capacity(batch.num_rows());
4078 for row in 0..batch.num_rows() {
4079 candidates.push(TopKCandidate {
4080 score: score_key(score_array.as_ref(), row)?,
4081 tablet,
4082 row_id: RowId(row_id_array.value(row)),
4083 });
4084 }
4085 let mut positions: Vec<usize> = (0..candidates.len()).collect();
4086 positions.sort_by(|left, right| topk_cmp(&candidates[*left], &candidates[*right]));
4087 let candidates = positions.iter().map(|index| candidates[*index]).collect();
4088 Ok(TopKInput {
4089 batch,
4090 candidates,
4091 positions,
4092 })
4093}
4094
4095#[derive(Clone, Copy, Debug)]
4102enum AggValue {
4103 I64(i64),
4104 F64(f64),
4105}
4106
4107fn numeric_cell(array: &dyn Array, row: usize) -> DistributedResult<Option<AggValue>> {
4109 if array.is_null(row) {
4110 return Ok(None);
4111 }
4112 match array.data_type() {
4113 DataType::Int64 => Ok(Some(AggValue::I64(
4114 array
4115 .as_any()
4116 .downcast_ref::<Int64Array>()
4117 .expect("type checked")
4118 .value(row),
4119 ))),
4120 DataType::Float64 => Ok(Some(AggValue::F64(
4121 array
4122 .as_any()
4123 .downcast_ref::<Float64Array>()
4124 .expect("type checked")
4125 .value(row),
4126 ))),
4127 other => Err(DistributedError::Unsupported(format!(
4128 "aggregate over {other} is not supported in this wave"
4129 ))),
4130 }
4131}
4132
4133#[derive(Clone, Debug)]
4135enum AggAccum {
4136 Count(i64),
4137 SumI(i128, bool),
4138 SumF(f64, bool),
4139 MinI(i64, bool),
4140 MinF(f64, bool),
4141 MaxI(i64, bool),
4142 MaxF(f64, bool),
4143 Avg { sum: f64, count: i64 },
4144}
4145
4146impl AggAccum {
4147 fn fresh(function: AggregateFunction, float: bool) -> Self {
4150 match function {
4151 AggregateFunction::Count => Self::Count(0),
4152 AggregateFunction::Sum if float => Self::SumF(0.0, false),
4153 AggregateFunction::Sum => Self::SumI(0, false),
4154 AggregateFunction::Min if float => Self::MinF(f64::INFINITY, false),
4155 AggregateFunction::Min => Self::MinI(i64::MAX, false),
4156 AggregateFunction::Max if float => Self::MaxF(f64::NEG_INFINITY, false),
4157 AggregateFunction::Max => Self::MaxI(i64::MIN, false),
4158 AggregateFunction::Avg => Self::Avg { sum: 0.0, count: 0 },
4159 }
4160 }
4161
4162 fn fold_value(&mut self, cell: Option<AggValue>) {
4164 match (self, cell) {
4165 (Self::SumI(sum, seen), Some(AggValue::I64(value))) => {
4166 *sum += i128::from(value);
4167 *seen = true;
4168 }
4169 (Self::SumF(sum, seen), Some(AggValue::F64(value))) => {
4170 *sum += value;
4171 *seen = true;
4172 }
4173 (Self::MinI(min, seen), Some(AggValue::I64(value))) => {
4174 *min = (*min).min(value);
4175 *seen = true;
4176 }
4177 (Self::MinF(min, seen), Some(AggValue::F64(value))) => {
4178 *min = min.min(value);
4179 *seen = true;
4180 }
4181 (Self::MaxI(max, seen), Some(AggValue::I64(value))) => {
4182 *max = (*max).max(value);
4183 *seen = true;
4184 }
4185 (Self::MaxF(max, seen), Some(AggValue::F64(value))) => {
4186 *max = max.max(value);
4187 *seen = true;
4188 }
4189 (Self::Avg { sum, count }, Some(AggValue::I64(value))) => {
4190 *sum += value as f64;
4191 *count += 1;
4192 }
4193 (Self::Avg { sum, count }, Some(AggValue::F64(value))) => {
4194 *sum += value;
4195 *count += 1;
4196 }
4197 _ => {}
4198 }
4199 }
4200}
4201
4202struct GroupEntry {
4204 first_row: usize,
4207 accums: Vec<AggAccum>,
4208}
4209
4210#[derive(Default)]
4212struct GroupFold {
4213 order: Vec<String>,
4214 groups: HashMap<String, GroupEntry>,
4215}
4216
4217impl GroupFold {
4218 fn entry(&mut self, key: String, row: usize, templates: &[AggAccum]) -> &mut GroupEntry {
4219 if !self.groups.contains_key(&key) {
4220 self.order.push(key.clone());
4221 self.groups.insert(
4222 key.clone(),
4223 GroupEntry {
4224 first_row: row,
4225 accums: templates.to_vec(),
4226 },
4227 );
4228 }
4229 self.groups.get_mut(&key).expect("inserted above")
4230 }
4231}
4232
4233fn group_key(batch: &RecordBatch, key_indexes: &[usize], row: usize) -> DistributedResult<String> {
4235 let mut parts = Vec::with_capacity(key_indexes.len());
4236 for index in key_indexes {
4237 parts.push(array_value_to_string(batch.column(*index).as_ref(), row)?);
4238 }
4239 Ok(parts.join("\u{1f}"))
4240}
4241
4242fn resolve_columns(schema: &Schema, columns: &[String]) -> DistributedResult<Vec<usize>> {
4244 columns
4245 .iter()
4246 .map(|column| {
4247 schema.index_of(column).map_err(|_| {
4248 DistributedError::InvalidPlan(format!("group-by column `{column}` not in schema"))
4249 })
4250 })
4251 .collect()
4252}
4253
4254fn aggregate_float(schema: &Schema, aggregate: &AggregateExpr) -> DistributedResult<bool> {
4256 match &aggregate.column {
4257 None => Ok(false),
4258 Some(column) => {
4259 let index = schema.index_of(column).map_err(|_| {
4260 DistributedError::InvalidPlan(format!("aggregate column `{column}` not in schema"))
4261 })?;
4262 match schema.field(index).data_type() {
4263 DataType::Int64 => Ok(false),
4264 DataType::Float64 => Ok(true),
4265 other => Err(DistributedError::Unsupported(format!(
4266 "aggregate over {other} is not supported in this wave"
4267 ))),
4268 }
4269 }
4270 }
4271}
4272
4273fn take_group_keys(
4275 batch: &RecordBatch,
4276 key_indexes: &[usize],
4277 fold: &GroupFold,
4278) -> DistributedResult<Vec<ArrayRef>> {
4279 if key_indexes.is_empty() {
4280 return Ok(Vec::new());
4281 }
4282 let indices = UInt32Array::from(
4283 fold.order
4284 .iter()
4285 .map(|key| u32::try_from(fold.groups[key].first_row).unwrap_or(u32::MAX))
4286 .collect::<Vec<u32>>(),
4287 );
4288 let mut columns = Vec::with_capacity(key_indexes.len());
4289 for index in key_indexes {
4290 columns.push(take(batch.column(*index), &indices, None)?);
4291 }
4292 Ok(columns)
4293}
4294
4295fn emit_int_accum(groups: &GroupFold, index: usize) -> DistributedResult<Vec<Option<i64>>> {
4297 let mut values = Vec::with_capacity(groups.order.len());
4298 for key in &groups.order {
4299 let accum = &groups.groups[key].accums[index];
4300 values.push(match accum {
4301 AggAccum::Count(count) => Some(*count),
4302 AggAccum::SumI(sum, seen) => seen
4303 .then(|| {
4304 i64::try_from(*sum).map_err(|_| {
4305 DistributedError::Unsupported(
4306 "integer sum overflow in this wave".to_owned(),
4307 )
4308 })
4309 })
4310 .transpose()?,
4311 AggAccum::MinI(value, seen) | AggAccum::MaxI(value, seen) => seen.then_some(*value),
4312 other => {
4313 return Err(DistributedError::InvalidPlan(format!(
4314 "internal: accumulator {other:?} is not an integer column"
4315 )))
4316 }
4317 });
4318 }
4319 Ok(values)
4320}
4321
4322fn emit_float_accum(groups: &GroupFold, index: usize) -> DistributedResult<Vec<Option<f64>>> {
4324 let mut values = Vec::with_capacity(groups.order.len());
4325 for key in &groups.order {
4326 let accum = &groups.groups[key].accums[index];
4327 values.push(match accum {
4328 AggAccum::SumF(sum, seen) => seen.then_some(*sum),
4329 AggAccum::MinF(value, seen) | AggAccum::MaxF(value, seen) => seen.then_some(*value),
4330 other => {
4331 return Err(DistributedError::InvalidPlan(format!(
4332 "internal: accumulator {other:?} is not a float column"
4333 )))
4334 }
4335 });
4336 }
4337 Ok(values)
4338}
4339
4340fn partial_aggregate_batches(
4343 batches: &[RecordBatch],
4344 group_by: &[String],
4345 aggregates: &[AggregateExpr],
4346) -> DistributedResult<RecordBatch> {
4347 let Some(batch) = concat_all(batches)? else {
4348 return Err(DistributedError::InvalidPlan(
4349 "aggregate input has no batches".to_owned(),
4350 ));
4351 };
4352 let schema = batch.schema();
4353 let key_indexes = resolve_columns(&schema, group_by)?;
4354 let mut value_indexes = Vec::with_capacity(aggregates.len());
4355 let mut templates = Vec::with_capacity(aggregates.len());
4356 for aggregate in aggregates {
4357 let float = aggregate_float(&schema, aggregate)?;
4358 value_indexes.push(match &aggregate.column {
4359 Some(column) => Some(schema.index_of(column).map_err(|_| {
4360 DistributedError::InvalidPlan(format!("aggregate column `{column}` not in schema"))
4361 })?),
4362 None => None,
4363 });
4364 templates.push(AggAccum::fresh(aggregate.function, float));
4365 }
4366 let mut fold = GroupFold::default();
4367 for row in 0..batch.num_rows() {
4368 let key = group_key(&batch, &key_indexes, row)?;
4369 let entry = fold.entry(key, row, &templates);
4370 for (index, aggregate) in aggregates.iter().enumerate() {
4371 match aggregate.function {
4372 AggregateFunction::Count => {
4373 let counted = match value_indexes[index] {
4374 Some(column) => !batch.column(column).is_null(row),
4375 None => true,
4376 };
4377 if counted {
4378 let AggAccum::Count(count) = &mut entry.accums[index] else {
4379 unreachable!("count template");
4380 };
4381 *count += 1;
4382 }
4383 }
4384 _ => {
4385 let cell = match value_indexes[index] {
4386 Some(column) => numeric_cell(batch.column(column).as_ref(), row)?,
4387 None => None,
4388 };
4389 entry.accums[index].fold_value(cell);
4390 }
4391 }
4392 }
4393 }
4394 let mut columns = take_group_keys(&batch, &key_indexes, &fold)?;
4396 let mut fields: Vec<Field> = key_indexes
4397 .iter()
4398 .map(|index| schema.field(*index).clone())
4399 .collect();
4400 for (index, aggregate) in aggregates.iter().enumerate() {
4401 match aggregate.function {
4402 AggregateFunction::Avg => {
4403 let mut sums = Vec::with_capacity(fold.order.len());
4404 let mut counts = Vec::with_capacity(fold.order.len());
4405 for key in &fold.order {
4406 let AggAccum::Avg { sum, count } = &fold.groups[key].accums[index] else {
4407 unreachable!("avg template");
4408 };
4409 sums.push(Some(*sum));
4410 counts.push(Some(*count));
4411 }
4412 fields.push(Field::new(
4413 format!("__partial_{index}_sum"),
4414 DataType::Float64,
4415 true,
4416 ));
4417 columns.push(Arc::new(Float64Array::from(sums)));
4418 fields.push(Field::new(
4419 format!("__partial_{index}_count"),
4420 DataType::Int64,
4421 true,
4422 ));
4423 columns.push(Arc::new(Int64Array::from(counts)));
4424 }
4425 AggregateFunction::Count => {
4426 fields.push(Field::new(
4427 format!("__partial_{index}"),
4428 DataType::Int64,
4429 true,
4430 ));
4431 columns.push(Arc::new(Int64Array::from(emit_int_accum(&fold, index)?)));
4432 }
4433 _ => match &templates[index] {
4434 AggAccum::SumI(..) | AggAccum::MinI(..) | AggAccum::MaxI(..) => {
4435 fields.push(Field::new(
4436 format!("__partial_{index}"),
4437 DataType::Int64,
4438 true,
4439 ));
4440 columns.push(Arc::new(Int64Array::from(emit_int_accum(&fold, index)?)));
4441 }
4442 AggAccum::SumF(..) | AggAccum::MinF(..) | AggAccum::MaxF(..) => {
4443 fields.push(Field::new(
4444 format!("__partial_{index}"),
4445 DataType::Float64,
4446 true,
4447 ));
4448 columns.push(Arc::new(Float64Array::from(emit_float_accum(
4449 &fold, index,
4450 )?)));
4451 }
4452 other => {
4453 return Err(DistributedError::InvalidPlan(format!(
4454 "internal: unexpected accumulator {other:?}"
4455 )))
4456 }
4457 },
4458 }
4459 }
4460 Ok(RecordBatch::try_new(Schema::new(fields).into(), columns)?)
4461}
4462
4463fn final_aggregate_batches(
4466 batches: &[RecordBatch],
4467 group_by: &[String],
4468 aggregates: &[AggregateExpr],
4469) -> DistributedResult<RecordBatch> {
4470 let Some(batch) = concat_all(batches)? else {
4471 return Err(DistributedError::InvalidPlan(
4472 "aggregate input has no batches".to_owned(),
4473 ));
4474 };
4475 let schema = batch.schema();
4476 let key_indexes = resolve_columns(&schema, group_by)?;
4477 let mut partial_indexes: Vec<(usize, Option<usize>)> = Vec::with_capacity(aggregates.len());
4479 let mut templates = Vec::with_capacity(aggregates.len());
4480 for (index, aggregate) in aggregates.iter().enumerate() {
4481 let value_name = if aggregate.function == AggregateFunction::Avg {
4482 format!("__partial_{index}_sum")
4483 } else {
4484 format!("__partial_{index}")
4485 };
4486 let value_index = schema.index_of(&value_name).map_err(|_| {
4487 DistributedError::InvalidPlan(format!(
4488 "partial aggregate column `{value_name}` not in schema"
4489 ))
4490 })?;
4491 let float = match schema.field(value_index).data_type() {
4492 DataType::Int64 => false,
4493 DataType::Float64 => true,
4494 other => {
4495 return Err(DistributedError::Unsupported(format!(
4496 "aggregate partial of type {other} is not supported in this wave"
4497 )))
4498 }
4499 };
4500 templates.push(AggAccum::fresh(aggregate.function, float));
4501 let count_index = if aggregate.function == AggregateFunction::Avg {
4502 let count_name = format!("__partial_{index}_count");
4503 Some(schema.index_of(&count_name).map_err(|_| {
4504 DistributedError::InvalidPlan(format!(
4505 "partial aggregate column `{count_name}` not in schema"
4506 ))
4507 })?)
4508 } else {
4509 None
4510 };
4511 partial_indexes.push((value_index, count_index));
4512 }
4513 let mut fold = GroupFold::default();
4514 for row in 0..batch.num_rows() {
4515 let key = group_key(&batch, &key_indexes, row)?;
4516 let entry = fold.entry(key, row, &templates);
4517 for (index, aggregate) in aggregates.iter().enumerate() {
4518 let (value_index, count_index) = partial_indexes[index];
4519 match aggregate.function {
4520 AggregateFunction::Count => {
4521 if let Some(AggValue::I64(value)) =
4522 numeric_cell(batch.column(value_index).as_ref(), row)?
4523 {
4524 let AggAccum::Count(count) = &mut entry.accums[index] else {
4525 unreachable!("count template");
4526 };
4527 *count += value;
4528 }
4529 }
4530 AggregateFunction::Avg => {
4531 let sum = numeric_cell(batch.column(value_index).as_ref(), row)?;
4532 let count = match count_index {
4533 Some(count_index) => numeric_cell(batch.column(count_index).as_ref(), row)?,
4534 None => None,
4535 };
4536 let AggAccum::Avg {
4537 sum: total,
4538 count: rows,
4539 } = &mut entry.accums[index]
4540 else {
4541 unreachable!("avg template");
4542 };
4543 match sum {
4544 Some(AggValue::F64(value)) => *total += value,
4545 Some(AggValue::I64(value)) => *total += value as f64,
4546 None => {}
4547 }
4548 if let Some(AggValue::I64(value)) = count {
4549 *rows += value;
4550 }
4551 }
4552 _ => {
4553 let cell = numeric_cell(batch.column(value_index).as_ref(), row)?;
4554 entry.accums[index].fold_value(cell);
4555 }
4556 }
4557 }
4558 }
4559 if fold.order.is_empty() && group_by.is_empty() {
4561 fold.entry(String::new(), 0, &templates);
4562 }
4563 let mut columns = take_group_keys(&batch, &key_indexes, &fold)?;
4565 let mut fields: Vec<Field> = key_indexes
4566 .iter()
4567 .map(|index| schema.field(*index).clone())
4568 .collect();
4569 for (index, aggregate) in aggregates.iter().enumerate() {
4570 let name = aggregate_output_name(aggregate);
4571 match &templates[index] {
4572 AggAccum::Count(_) | AggAccum::SumI(..) | AggAccum::MinI(..) | AggAccum::MaxI(..) => {
4573 fields.push(Field::new(name, DataType::Int64, true));
4574 columns.push(Arc::new(Int64Array::from(emit_int_accum(&fold, index)?)));
4575 }
4576 AggAccum::SumF(..) | AggAccum::MinF(..) | AggAccum::MaxF(..) => {
4577 fields.push(Field::new(name, DataType::Float64, true));
4578 columns.push(Arc::new(Float64Array::from(emit_float_accum(
4579 &fold, index,
4580 )?)));
4581 }
4582 AggAccum::Avg { .. } => {
4583 let values: Vec<Option<f64>> = fold
4584 .order
4585 .iter()
4586 .map(|key| match &fold.groups[key].accums[index] {
4587 AggAccum::Avg { sum, count } => {
4588 (*count > 0).then_some(*sum / *count as f64)
4589 }
4590 _ => unreachable!("avg template"),
4591 })
4592 .collect();
4593 fields.push(Field::new(name, DataType::Float64, true));
4594 columns.push(Arc::new(Float64Array::from(values)));
4595 }
4596 }
4597 }
4598 Ok(RecordBatch::try_new(Schema::new(fields).into(), columns)?)
4599}
4600
4601#[derive(Debug)]
4608pub struct ResourceLedger {
4609 state: Mutex<LedgerState>,
4610 max_fragments: usize,
4611 max_bytes: u64,
4612}
4613
4614#[derive(Default, Debug)]
4615struct LedgerState {
4616 fragments: usize,
4617 bytes: u64,
4618}
4619
4620#[derive(Debug)]
4622pub struct ResourcePermit {
4623 ledger: Arc<ResourceLedger>,
4624 bytes: u64,
4625}
4626
4627impl Drop for ResourcePermit {
4628 fn drop(&mut self) {
4629 let mut state = self.ledger.state.lock();
4630 state.fragments = state.fragments.saturating_sub(1);
4631 state.bytes = state.bytes.saturating_sub(self.bytes);
4632 }
4633}
4634
4635impl ResourceLedger {
4636 pub fn new(max_fragments: usize, max_bytes: u64) -> Self {
4639 Self {
4640 state: Mutex::new(LedgerState::default()),
4641 max_fragments,
4642 max_bytes,
4643 }
4644 }
4645
4646 pub fn reserve(self: &Arc<Self>, fragment: &PlanFragment) -> DistributedResult<ResourcePermit> {
4649 let mut state = self.state.lock();
4650 if state.fragments >= self.max_fragments {
4651 return Err(DistributedError::Reservation {
4652 fragment_id: fragment.fragment_id,
4653 reason: format!("fragment concurrency limit {} reached", self.max_fragments),
4654 });
4655 }
4656 if state.bytes.saturating_add(fragment.estimated_bytes) > self.max_bytes {
4657 return Err(DistributedError::Reservation {
4658 fragment_id: fragment.fragment_id,
4659 reason: format!(
4660 "estimated bytes {} exceed the {} byte budget",
4661 state.bytes.saturating_add(fragment.estimated_bytes),
4662 self.max_bytes
4663 ),
4664 });
4665 }
4666 state.fragments += 1;
4667 state.bytes = state.bytes.saturating_add(fragment.estimated_bytes);
4668 Ok(ResourcePermit {
4669 ledger: Arc::clone(self),
4670 bytes: fragment.estimated_bytes,
4671 })
4672 }
4673
4674 pub fn reserved_fragments(&self) -> usize {
4676 self.state.lock().fragments
4677 }
4678
4679 pub fn reserved_bytes(&self) -> u64 {
4681 self.state.lock().bytes
4682 }
4683}
4684
4685#[derive(Default)]
4689pub struct LeaseLedger {
4690 leases: Mutex<HashMap<TabletId, Instant>>,
4691}
4692
4693impl LeaseLedger {
4694 pub fn renew(&self, worker: TabletId, expiry: Instant) {
4696 self.leases.lock().insert(worker, expiry);
4697 }
4698
4699 pub fn expiry(&self, worker: &TabletId) -> Option<Instant> {
4701 self.leases.lock().get(worker).copied()
4702 }
4703
4704 pub fn sweep(&self, now: Instant) -> Vec<TabletId> {
4707 let mut leases = self.leases.lock();
4708 let expired: Vec<TabletId> = leases
4709 .iter()
4710 .filter(|(_, expiry)| **expiry <= now)
4711 .map(|(worker, _)| *worker)
4712 .collect();
4713 for worker in &expired {
4714 leases.remove(worker);
4715 }
4716 expired
4717 }
4718}
4719
4720struct InFlight {
4722 worker: Option<TabletId>,
4723 control: ExecutionControl,
4724 _permit: ResourcePermit,
4725}
4726
4727struct ExecutionState {
4729 query_id: QueryId,
4730 in_flight: Mutex<HashMap<FragmentId, InFlight>>,
4731}
4732
4733impl ExecutionState {
4734 fn new(query_id: QueryId) -> Self {
4735 Self {
4736 query_id,
4737 in_flight: Mutex::new(HashMap::new()),
4738 }
4739 }
4740}
4741
4742struct ProducerInput {
4744 fragment_id: FragmentId,
4745 tablet: Option<TabletId>,
4746 frames: Vec<BatchFrame>,
4747}
4748
4749enum RootData {
4753 Streams(Vec<ProducerInput>),
4754 Batches(Vec<RecordBatch>),
4755}
4756
4757impl RootData {
4758 fn flatten(&self) -> Vec<RecordBatch> {
4760 match self {
4761 Self::Streams(inputs) => inputs
4762 .iter()
4763 .flat_map(|input| input.frames.iter().map(|frame| frame.batch.clone()))
4764 .collect(),
4765 Self::Batches(batches) => batches.clone(),
4766 }
4767 }
4768}
4769
4770pub struct Coordinator {
4777 transport: Arc<dyn FragmentTransport>,
4778 registry: Arc<SqlQueryRegistry>,
4779 resources: Arc<ResourceLedger>,
4780 leases: LeaseLedger,
4781 lease_ttl: Duration,
4782 executions: Mutex<HashMap<QueryId, Arc<ExecutionState>>>,
4783}
4784
4785impl Coordinator {
4786 pub fn new(transport: Arc<dyn FragmentTransport>, registry: Arc<SqlQueryRegistry>) -> Self {
4789 Self::with_limits(
4790 transport,
4791 registry,
4792 1_024,
4793 16 * 1024 * 1024 * 1024,
4794 Duration::from_secs(30),
4795 )
4796 }
4797
4798 pub fn with_limits(
4800 transport: Arc<dyn FragmentTransport>,
4801 registry: Arc<SqlQueryRegistry>,
4802 max_fragments: usize,
4803 max_bytes: u64,
4804 lease_ttl: Duration,
4805 ) -> Self {
4806 Self {
4807 transport,
4808 registry,
4809 resources: Arc::new(ResourceLedger::new(max_fragments, max_bytes)),
4810 leases: LeaseLedger::default(),
4811 lease_ttl,
4812 executions: Mutex::new(HashMap::new()),
4813 }
4814 }
4815
4816 pub fn resources(&self) -> &Arc<ResourceLedger> {
4818 &self.resources
4819 }
4820
4821 pub async fn execute(&self, plan: &DistributedPlan) -> DistributedResult<Vec<RecordBatch>> {
4835 self.execute_with_authorization(plan, &[]).await
4836 }
4837
4838 pub async fn execute_with_authorization(
4841 &self,
4842 plan: &DistributedPlan,
4843 authorization_context: &[u8],
4844 ) -> DistributedResult<Vec<RecordBatch>> {
4845 if authorization_context.len() > MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES {
4846 return Err(DistributedError::InvalidPlan(format!(
4847 "fragment authorization context is {} bytes; limit is {}",
4848 authorization_context.len(),
4849 MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES
4850 )));
4851 }
4852 let registry_id = to_registry_query_id(&plan.query_id)?;
4853 let registered = self
4854 .registry
4855 .register(SqlQueryOptions {
4856 query_id: Some(registry_id),
4857 ..Default::default()
4858 })
4859 .map_err(|error| {
4860 DistributedError::InvalidPlan(format!("query registration failed: {error}"))
4861 })?;
4862 let result = self
4863 .execute_registered(plan, ®istered, authorization_context.into())
4864 .await;
4865 match &result {
4866 Ok(_) => {
4867 let _ = registered.try_complete();
4868 }
4869 Err(_) => registered.fail(),
4870 }
4871 result
4872 }
4873
4874 pub fn cancel_query(&self, query_id: &QueryId) -> DistributedResult<bool> {
4880 let outcome = self.registry.cancel(to_registry_query_id(query_id)?);
4881 let state = self.executions.lock().get(query_id).cloned();
4882 if let Some(state) = state {
4883 let in_flight = state.in_flight.lock();
4884 for (fragment_id, inflight) in in_flight.iter() {
4885 inflight.control.cancel(CancellationReason::ClientRequest);
4886 let _ = self.transport.cancel_fragment(*query_id, *fragment_id);
4887 }
4888 }
4889 Ok(matches!(
4890 outcome,
4891 CancelOutcome::Accepted | CancelOutcome::AlreadyCancelling
4892 ))
4893 }
4894
4895 pub fn sweep_expired_leases(&self, now: Instant) -> usize {
4902 let expired = self.leases.sweep(now);
4903 if expired.is_empty() {
4904 return 0;
4905 }
4906 let states: Vec<Arc<ExecutionState>> = self.executions.lock().values().cloned().collect();
4907 let mut cleaned = 0;
4908 for state in states {
4909 let victims: Vec<FragmentId> = {
4910 let in_flight = state.in_flight.lock();
4911 in_flight
4912 .iter()
4913 .filter(|(_, inflight)| {
4914 inflight
4915 .worker
4916 .is_some_and(|worker| expired.contains(&worker))
4917 })
4918 .map(|(fragment_id, _)| *fragment_id)
4919 .collect()
4920 };
4921 for fragment_id in victims {
4922 let inflight = state.in_flight.lock().remove(&fragment_id);
4923 if let Some(inflight) = inflight {
4924 inflight.control.cancel(CancellationReason::ServerShutdown);
4925 drop(inflight);
4926 let _ = self.transport.cancel_fragment(state.query_id, fragment_id);
4927 cleaned += 1;
4928 }
4929 }
4930 }
4931 cleaned
4932 }
4933
4934 async fn execute_registered(
4935 &self,
4936 plan: &DistributedPlan,
4937 registered: &RegisteredSqlQuery,
4938 authorization_context: Arc<[u8]>,
4939 ) -> DistributedResult<Vec<RecordBatch>> {
4940 validate_plan_shape(plan)?;
4941 let root = plan
4942 .root_fragment_id()
4943 .ok_or_else(|| DistributedError::InvalidPlan("plan has no root fragment".to_owned()))?;
4944 let state = Arc::new(ExecutionState::new(plan.query_id));
4945 self.executions
4946 .lock()
4947 .insert(plan.query_id, Arc::clone(&state));
4948 let result = self
4949 .run_plan(plan, root, registered, &state, authorization_context)
4950 .await;
4951 self.executions.lock().remove(&plan.query_id);
4952 result
4953 }
4954
4955 async fn run_plan(
4956 &self,
4957 plan: &DistributedPlan,
4958 root: FragmentId,
4959 registered: &RegisteredSqlQuery,
4960 state: &Arc<ExecutionState>,
4961 authorization_context: Arc<[u8]>,
4962 ) -> DistributedResult<Vec<RecordBatch>> {
4963 let parent = registered.control().clone();
4964 let layers = fragment_layers(plan, root)?;
4965 let mut outputs: HashMap<FragmentId, Vec<BatchFrame>> = HashMap::new();
4966 for layer in &layers {
4967 checkpoint(&parent)?;
4968 let now = Instant::now();
4969 for &fragment_id in layer {
4970 if let FragmentAssignment::Tablet(tablet) =
4971 plan.fragments[fragment_id as usize].assignment
4972 {
4973 self.leases.renew(tablet, now + self.lease_ttl);
4974 }
4975 }
4976 let mut tasks = FuturesUnordered::new();
4977 let mut spawn_error: Option<DistributedError> = None;
4978 for &fragment_id in layer {
4979 let fragment = &plan.fragments[fragment_id as usize];
4980 let reserved = match self.resources.reserve(fragment) {
4981 Ok(permit) => permit,
4982 Err(error) => {
4983 spawn_error = Some(error);
4984 break;
4985 }
4986 };
4987 let inputs = match build_inputs(plan, fragment_id, &outputs) {
4988 Ok(inputs) => inputs,
4989 Err(error) => {
4990 spawn_error = Some(error);
4991 break;
4992 }
4993 };
4994 let control = parent.child_with_deadline(None);
4995 let worker = match fragment.assignment {
4996 FragmentAssignment::Tablet(tablet) => Some(tablet),
4997 FragmentAssignment::Coordinator => None,
4998 };
4999 state.in_flight.lock().insert(
5000 fragment_id,
5001 InFlight {
5002 worker,
5003 control: control.clone(),
5004 _permit: reserved,
5005 },
5006 );
5007 let fragment = fragment.clone();
5008 let fragment_control = FragmentControl {
5009 control,
5010 max_spill_bytes: fragment.max_spill_bytes,
5011 authorization_context: Arc::clone(&authorization_context),
5012 };
5013 let transport = Arc::clone(&self.transport);
5014 tasks.push(async move {
5015 let worker = worker_label(&fragment);
5016 let drain_control = fragment_control.control.clone();
5017 let result = async {
5018 let stream = transport
5019 .execute_fragment(plan.query_id, &fragment, inputs, fragment_control)
5020 .await?;
5021 drain_stream(stream, &drain_control).await
5022 }
5023 .await;
5024 (fragment.fragment_id, worker, result)
5025 });
5026 }
5027 if let Some(error) = spawn_error {
5028 self.abort(plan, state);
5029 while let Some((fragment_id, _, _)) = tasks.next().await {
5030 state.in_flight.lock().remove(&fragment_id);
5031 }
5032 return Err(error);
5033 }
5034 let mut failure: Option<DistributedError> = None;
5035 while let Some((fragment_id, worker, result)) = tasks.next().await {
5036 state.in_flight.lock().remove(&fragment_id);
5037 match result {
5038 Ok(frames) => {
5039 outputs.insert(fragment_id, frames);
5040 }
5041 Err(error) => {
5042 if failure.is_none() {
5043 failure = Some(match error {
5044 DistributedError::Cancelled(reason) => {
5045 DistributedError::Cancelled(reason)
5046 }
5047 other => DistributedError::FragmentExecution {
5048 fragment_id,
5049 worker,
5050 message: other.to_string(),
5051 },
5052 });
5053 }
5054 }
5055 }
5056 }
5057 if let Some(error) = failure {
5058 self.abort(plan, state);
5059 return Err(error);
5060 }
5061 }
5062 checkpoint(&parent)?;
5063 self.run_root(plan, root, &outputs, &parent, authorization_context)
5064 .await
5065 }
5066
5067 fn abort(&self, plan: &DistributedPlan, state: &Arc<ExecutionState>) {
5070 {
5071 let in_flight = state.in_flight.lock();
5072 for inflight in in_flight.values() {
5073 inflight.control.cancel(CancellationReason::ClientRequest);
5074 }
5075 }
5076 for fragment in &plan.fragments {
5077 let _ = self
5078 .transport
5079 .cancel_fragment(plan.query_id, fragment.fragment_id);
5080 }
5081 }
5082
5083 async fn run_root(
5085 &self,
5086 plan: &DistributedPlan,
5087 root: FragmentId,
5088 outputs: &HashMap<FragmentId, Vec<BatchFrame>>,
5089 parent: &ExecutionControl,
5090 authorization_context: Arc<[u8]>,
5091 ) -> DistributedResult<Vec<RecordBatch>> {
5092 let fragment = &plan.fragments[root as usize];
5093 let mut inputs: Vec<ProducerInput> = Vec::new();
5094 for operator in &fragment.operators {
5095 if let FragmentOperator::RemoteExchangeSource { exchange } = operator {
5096 let edge = plan.exchanges.get(*exchange as usize).ok_or_else(|| {
5097 DistributedError::InvalidPlan(format!("unknown exchange {exchange}"))
5098 })?;
5099 let producer = plan.fragments.get(edge.producer as usize).ok_or_else(|| {
5100 DistributedError::InvalidPlan(format!(
5101 "unknown producer fragment {}",
5102 edge.producer
5103 ))
5104 })?;
5105 let tablet = match producer.assignment {
5106 FragmentAssignment::Tablet(tablet) => Some(tablet),
5107 FragmentAssignment::Coordinator => None,
5108 };
5109 let frames = outputs.get(&edge.producer).cloned().ok_or_else(|| {
5110 DistributedError::InvalidPlan(format!(
5111 "missing output of producer fragment {}",
5112 edge.producer
5113 ))
5114 })?;
5115 let frames = route_frames(plan, edge, frames)?;
5116 inputs.push(ProducerInput {
5117 fragment_id: edge.producer,
5118 tablet,
5119 frames,
5120 });
5121 }
5122 }
5123 let mut current = RootData::Streams(inputs);
5124 for operator in &fragment.operators {
5125 checkpoint(parent)?;
5126 match operator {
5127 FragmentOperator::RemoteExchangeSource { .. } => {}
5128 FragmentOperator::FinalAggregate {
5129 group_by,
5130 aggregates,
5131 } => {
5132 let batches = current.flatten();
5133 current = RootData::Batches(vec![final_aggregate_batches(
5134 &batches, group_by, aggregates,
5135 )?]);
5136 }
5137 FragmentOperator::MergeSort { keys, limit } => {
5138 current = RootData::Batches(match ¤t {
5139 RootData::Streams(_) => {
5140 let streams = per_stream_batches(¤t);
5141 merge_sorted_streams(&streams, keys, *limit)?
5142 }
5143 RootData::Batches(batches) => sort_batches_local(batches, keys, *limit)?,
5144 });
5145 }
5146 FragmentOperator::DistributedTopK { k, score } => {
5147 current = RootData::Batches(
5148 self.coordinator_top_k(
5149 plan,
5150 ¤t,
5151 *k,
5152 score,
5153 parent,
5154 &authorization_context,
5155 )
5156 .await?,
5157 );
5158 }
5159 FragmentOperator::DistributedLimit { limit } => {
5160 let batches = current.flatten();
5161 current = RootData::Batches(limit_batches(&batches, *limit));
5162 }
5163 other => {
5164 return Err(DistributedError::Unsupported(format!(
5165 "root fragment operator {other:?} is not coordinator-executable in this wave"
5166 )))
5167 }
5168 }
5169 }
5170 Ok(current.flatten())
5171 }
5172
5173 async fn coordinator_top_k(
5178 &self,
5179 plan: &DistributedPlan,
5180 data: &RootData,
5181 k: usize,
5182 score: &SortKey,
5183 control: &ExecutionControl,
5184 authorization_context: &[u8],
5185 ) -> DistributedResult<Vec<RecordBatch>> {
5186 let synthetic;
5189 let inputs: &[ProducerInput] = match data {
5190 RootData::Streams(inputs) => inputs,
5191 RootData::Batches(batches) => {
5192 synthetic = ProducerInput {
5193 fragment_id: u32::MAX,
5194 tablet: Some(TabletId::ZERO),
5195 frames: batches.iter().cloned().map(BatchFrame::data).collect(),
5196 };
5197 std::slice::from_ref(&synthetic)
5198 }
5199 };
5200 let mut all_batches: Vec<RecordBatch> = Vec::new();
5201 let mut batch_tablet: Vec<TabletId> = Vec::new();
5202 let mut shards: BTreeMap<TabletId, TabletTopK> = BTreeMap::new();
5203 let mut fragment_of: HashMap<TabletId, FragmentId> = HashMap::new();
5204 for input in inputs {
5205 let tablet = input.tablet.ok_or_else(|| {
5206 DistributedError::InvalidPlan(
5207 "distributed top-k inputs must be tablet fragments".to_owned(),
5208 )
5209 })?;
5210 fragment_of.insert(tablet, input.fragment_id);
5211 let mut rows = Vec::new();
5212 let mut bound = None;
5213 for frame in &input.frames {
5214 let batch = &frame.batch;
5215 if batch.num_rows() > 0 {
5216 let score_index = batch.schema().index_of(&score.column).map_err(|_| {
5217 DistributedError::InvalidPlan(format!(
5218 "top-k score column `{}` not in schema",
5219 score.column
5220 ))
5221 })?;
5222 let score_array = batch.column(score_index).clone();
5223 let row_id_array = row_ids(batch)?;
5224 for row in 0..batch.num_rows() {
5225 rows.push(TopKCandidate {
5226 score: score_key(score_array.as_ref(), row)?,
5227 tablet,
5228 row_id: RowId(row_id_array.value(row)),
5229 });
5230 }
5231 }
5232 batch_tablet.push(tablet);
5233 all_batches.push(batch.clone());
5234 match frame.score_bound {
5235 ScoreBound::AtMost(next) => bound = Some(next),
5236 ScoreBound::Exhausted => bound = None,
5237 ScoreBound::Unknown => {}
5240 }
5241 }
5242 shards.insert(
5243 tablet,
5244 TabletTopK {
5245 tablet,
5246 rows,
5247 unseen_bound: bound,
5248 },
5249 );
5250 }
5251 let winners = loop {
5252 let ordered: Vec<TabletTopK> = shards.values().cloned().collect();
5253 let merge = merge_top_k(&ordered, k);
5254 if merge.refill.is_empty() {
5255 break merge.winners;
5256 }
5257 for tablet in merge.refill {
5258 let fragment_id = fragment_of[&tablet];
5259 let already = shards[&tablet].rows.len();
5260 let refill = self
5261 .transport
5262 .refill_top_k(
5263 plan.query_id,
5264 &plan.fragments[fragment_id as usize],
5265 already,
5266 k,
5267 FragmentControl {
5268 control: control.child_with_deadline(None),
5269 max_spill_bytes: plan.fragments[fragment_id as usize].max_spill_bytes,
5270 authorization_context: authorization_context.into(),
5271 },
5272 )
5273 .await?;
5274 let entry = shards.get_mut(&tablet).expect("shard exists");
5275 if refill.rows.is_empty() && refill.unseen_bound == entry.unseen_bound {
5276 return Err(DistributedError::InvalidPlan(format!(
5277 "top-k refill for fragment {fragment_id} made no progress"
5278 )));
5279 }
5280 batch_tablet.push(tablet);
5281 all_batches.push(refill.payload);
5282 entry.rows.extend(refill.rows);
5283 entry.unseen_bound = refill.unseen_bound;
5284 }
5285 };
5286 if winners.is_empty() {
5287 return Ok(Vec::new());
5288 }
5289 let mut locations: HashMap<(TabletId, u64), (usize, usize)> = HashMap::new();
5291 for (batch_index, batch) in all_batches.iter().enumerate() {
5292 if batch.num_rows() == 0 {
5293 continue;
5294 }
5295 let row_id_array = row_ids(batch)?;
5296 let tablet = batch_tablet[batch_index];
5297 for row in 0..batch.num_rows() {
5298 locations.insert((tablet, row_id_array.value(row)), (batch_index, row));
5299 }
5300 }
5301 let mut order = Vec::with_capacity(winners.len());
5302 for winner in &winners {
5303 let location = locations
5304 .get(&(winner.tablet, winner.row_id.0))
5305 .ok_or_else(|| {
5306 DistributedError::InvalidPlan(format!(
5307 "top-k winner {:?} has no payload row",
5308 winner.row_id
5309 ))
5310 })?;
5311 order.push(*location);
5312 }
5313 let merged = emit_interleaved(&all_batches, &order)?;
5314 let schema = merged.first().map(|batch| batch.schema()).ok_or_else(|| {
5316 DistributedError::InvalidPlan("top-k merge produced no output".to_owned())
5317 })?;
5318 let keep: Vec<usize> = schema
5319 .fields()
5320 .iter()
5321 .enumerate()
5322 .filter(|(_, field)| field.name() != TOPK_ROWID_COLUMN)
5323 .map(|(index, _)| index)
5324 .collect();
5325 if keep.len() == schema.fields().len() {
5326 return Ok(merged);
5327 }
5328 merged
5329 .iter()
5330 .map(|batch| Ok(batch.project(&keep)?))
5331 .collect()
5332 }
5333}
5334
5335fn per_stream_batches(data: &RootData) -> Vec<RecordBatch> {
5337 match data {
5338 RootData::Streams(inputs) => inputs
5339 .iter()
5340 .filter_map(|input| {
5341 let batches: Vec<RecordBatch> = input
5342 .frames
5343 .iter()
5344 .map(|frame| frame.batch.clone())
5345 .collect();
5346 concat_all(&batches).ok().flatten()
5347 })
5348 .collect(),
5349 RootData::Batches(batches) => batches.clone(),
5350 }
5351}
5352
5353fn worker_label(fragment: &PlanFragment) -> String {
5355 match fragment.assignment {
5356 FragmentAssignment::Tablet(tablet) => format!("tablet {tablet}"),
5357 FragmentAssignment::Coordinator => "coordinator".to_owned(),
5358 }
5359}
5360
5361fn validate_plan_shape(plan: &DistributedPlan) -> DistributedResult<()> {
5363 if plan.fragments.is_empty() {
5364 return Err(DistributedError::InvalidPlan(
5365 "plan has no fragments".to_owned(),
5366 ));
5367 }
5368 for (index, fragment) in plan.fragments.iter().enumerate() {
5369 if fragment.fragment_id as usize != index {
5370 return Err(DistributedError::InvalidPlan(
5371 "fragment ids must equal their vector index".to_owned(),
5372 ));
5373 }
5374 }
5375 for edge in &plan.exchanges {
5376 if edge.producer as usize >= plan.fragments.len()
5377 || edge.consumer as usize >= plan.fragments.len()
5378 {
5379 return Err(DistributedError::InvalidPlan(format!(
5380 "exchange {} references a missing fragment",
5381 edge.exchange_id
5382 )));
5383 }
5384 }
5385 let roots = plan
5386 .fragments
5387 .iter()
5388 .filter(|fragment| {
5389 !plan
5390 .exchanges
5391 .iter()
5392 .any(|edge| edge.producer == fragment.fragment_id)
5393 })
5394 .count();
5395 if roots != 1 {
5396 return Err(DistributedError::InvalidPlan(format!(
5397 "plan must have exactly one root fragment, found {roots}"
5398 )));
5399 }
5400 if let Some(root) = plan.root_fragment_id() {
5401 for fragment in &plan.fragments {
5402 if fragment.fragment_id != root
5403 && fragment.assignment == FragmentAssignment::Coordinator
5404 {
5405 return Err(DistributedError::InvalidPlan(
5406 "only the root fragment may be coordinator-assigned".to_owned(),
5407 ));
5408 }
5409 }
5410 }
5411 Ok(())
5412}
5413
5414fn fragment_layers(
5416 plan: &DistributedPlan,
5417 root: FragmentId,
5418) -> DistributedResult<Vec<Vec<FragmentId>>> {
5419 fn depth(
5420 plan: &DistributedPlan,
5421 fragment: FragmentId,
5422 memo: &mut [Option<usize>],
5423 visiting: &mut [bool],
5424 ) -> DistributedResult<usize> {
5425 if let Some(depth) = memo[fragment as usize] {
5426 return Ok(depth);
5427 }
5428 if visiting[fragment as usize] {
5429 return Err(DistributedError::InvalidPlan(
5430 "exchange graph has a cycle".to_owned(),
5431 ));
5432 }
5433 visiting[fragment as usize] = true;
5434 let mut best = 0;
5435 for edge in plan.exchanges_into(fragment) {
5436 best = best.max(depth(plan, edge.producer, memo, visiting)? + 1);
5437 }
5438 visiting[fragment as usize] = false;
5439 memo[fragment as usize] = Some(best);
5440 Ok(best)
5441 }
5442
5443 let mut memo = vec![None; plan.fragments.len()];
5444 let mut visiting = vec![false; plan.fragments.len()];
5445 let mut layers: Vec<Vec<FragmentId>> = Vec::new();
5446 for fragment in &plan.fragments {
5447 if fragment.fragment_id == root {
5448 continue;
5449 }
5450 let depth = depth(plan, fragment.fragment_id, &mut memo, &mut visiting)?;
5451 if layers.len() <= depth {
5452 layers.resize(depth + 1, Vec::new());
5453 }
5454 layers[depth].push(fragment.fragment_id);
5455 }
5456 Ok(layers)
5457}
5458
5459fn build_inputs(
5462 plan: &DistributedPlan,
5463 consumer: FragmentId,
5464 outputs: &HashMap<FragmentId, Vec<BatchFrame>>,
5465) -> DistributedResult<Vec<FragmentStream>> {
5466 let mut inputs = Vec::new();
5467 for edge in plan.exchanges_into(consumer) {
5468 let frames = outputs.get(&edge.producer).cloned().ok_or_else(|| {
5469 DistributedError::InvalidPlan(format!(
5470 "missing output of producer fragment {}",
5471 edge.producer
5472 ))
5473 })?;
5474 let frames = route_frames(plan, edge, frames)?;
5475 inputs.push(Box::pin(stream::iter(frames.into_iter().map(Ok))) as FragmentStream);
5476 }
5477 Ok(inputs)
5478}
5479
5480fn route_frames(
5483 plan: &DistributedPlan,
5484 edge: &ExchangeDescriptor,
5485 frames: Vec<BatchFrame>,
5486) -> DistributedResult<Vec<BatchFrame>> {
5487 match &edge.kind {
5488 ExchangeKind::Merge | ExchangeKind::Broadcast => Ok(frames),
5489 ExchangeKind::HashRepartition { keys } => {
5490 let mut siblings: Vec<&ExchangeDescriptor> = plan
5491 .exchanges
5492 .iter()
5493 .filter(|candidate| {
5494 candidate.producer == edge.producer
5495 && matches!(candidate.kind, ExchangeKind::HashRepartition { .. })
5496 })
5497 .collect();
5498 siblings.sort_by_key(|candidate| candidate.consumer);
5499 let width = siblings.len();
5500 let index = siblings
5501 .iter()
5502 .position(|candidate| candidate.consumer == edge.consumer)
5503 .ok_or_else(|| {
5504 DistributedError::InvalidPlan(format!(
5505 "exchange {} is missing from its repartition boundary",
5506 edge.exchange_id
5507 ))
5508 })?;
5509 repartition_frames(&frames, keys, width, index)
5510 }
5511 }
5512}
5513
5514fn to_registry_query_id(query_id: &QueryId) -> DistributedResult<crate::query_registry::QueryId> {
5516 query_id
5517 .to_hex()
5518 .parse()
5519 .map_err(|error| DistributedError::InvalidPlan(format!("query id bridge failed: {error}")))
5520}
5521
5522#[cfg(test)]
5527mod tests {
5528 use super::*;
5529 use arrow::datatypes::Field;
5530
5531 fn tablet(n: u8) -> TabletId {
5533 let mut bytes = [0u8; 16];
5534 bytes[15] = n;
5535 TabletId::from_bytes(bytes)
5536 }
5537
5538 struct SplitMix64(u64);
5540
5541 impl SplitMix64 {
5542 fn next(&mut self) -> u64 {
5543 self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
5544 let mut z = self.0;
5545 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
5546 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
5547 z ^ (z >> 31)
5548 }
5549
5550 fn below(&mut self, n: u64) -> u64 {
5551 self.next() % n
5552 }
5553 }
5554
5555 #[derive(Default)]
5556 struct StaticLocator {
5557 tablets: HashMap<String, Vec<TabletId>>,
5558 specs: HashMap<String, PartitionSpec>,
5559 }
5560
5561 impl TabletLocator for StaticLocator {
5562 fn tablets_for_table(&self, table: &str) -> DistributedResult<Vec<TabletId>> {
5563 self.tablets
5564 .get(table)
5565 .cloned()
5566 .ok_or_else(|| DistributedError::UnknownTable(table.to_owned()))
5567 }
5568
5569 fn partitioning(&self, table: &str) -> DistributedResult<PartitionSpec> {
5570 self.specs
5571 .get(table)
5572 .cloned()
5573 .ok_or_else(|| DistributedError::UnknownTable(table.to_owned()))
5574 }
5575 }
5576
5577 struct StaticMetadata {
5578 version: MetadataVersion,
5579 stats: HashMap<String, TableStats>,
5580 }
5581
5582 impl ClusterMetadata for StaticMetadata {
5583 fn metadata_version(&self) -> MetadataVersion {
5584 self.version
5585 }
5586
5587 fn table_stats(&self, table: &str) -> DistributedResult<TableStats> {
5588 self.stats
5589 .get(table)
5590 .copied()
5591 .ok_or_else(|| DistributedError::UnknownTable(table.to_owned()))
5592 }
5593 }
5594
5595 fn world(
5597 entries: &[(&str, &[TabletId], PartitionSpec, TableStats)],
5598 ) -> (StaticLocator, StaticMetadata) {
5599 let mut locator = StaticLocator::default();
5600 let mut stats = HashMap::new();
5601 for (table, tablets, spec, stat) in entries {
5602 locator
5603 .tablets
5604 .insert((*table).to_owned(), tablets.to_vec());
5605 locator.specs.insert((*table).to_owned(), spec.clone());
5606 stats.insert((*table).to_owned(), *stat);
5607 }
5608 (
5609 locator,
5610 StaticMetadata {
5611 version: MetadataVersion::new(7),
5612 stats,
5613 },
5614 )
5615 }
5616
5617 fn scan(table: &str) -> LogicalPlanLite {
5618 LogicalPlanLite::Scan {
5619 table: table.to_owned(),
5620 predicate: None,
5621 projection: Vec::new(),
5622 }
5623 }
5624
5625 fn desc(root: LogicalPlanLite) -> PlanDescription {
5626 PlanDescription {
5627 query_id: QueryId::new_random(),
5628 root,
5629 options: PlannerOptions::default(),
5630 }
5631 }
5632
5633 fn hash_spec(column: &str) -> PartitionSpec {
5634 PartitionSpec::Hash {
5635 columns: vec![column.to_owned()],
5636 buckets: 16,
5637 }
5638 }
5639
5640 fn stats(rows: u64, bytes: u64) -> TableStats {
5641 TableStats {
5642 row_count: rows,
5643 total_bytes: bytes,
5644 }
5645 }
5646
5647 fn i64_batch(columns: &[(&str, Vec<i64>)]) -> RecordBatch {
5648 let schema = Schema::new(
5649 columns
5650 .iter()
5651 .map(|(name, _)| Field::new(*name, DataType::Int64, true))
5652 .collect::<Vec<_>>(),
5653 );
5654 let arrays: Vec<ArrayRef> = columns
5655 .iter()
5656 .map(|(_, values)| Arc::new(Int64Array::from(values.clone())) as ArrayRef)
5657 .collect();
5658 RecordBatch::try_new(schema.into(), arrays).unwrap()
5659 }
5660
5661 fn score_batch(scores: Vec<u64>, row_ids: Vec<u64>, payloads: Vec<i64>) -> RecordBatch {
5662 let schema = Schema::new(vec![
5663 Field::new("score", DataType::UInt64, true),
5664 Field::new(TOPK_ROWID_COLUMN, DataType::UInt64, false),
5665 Field::new("payload", DataType::Int64, true),
5666 ]);
5667 RecordBatch::try_new(
5668 schema.into(),
5669 vec![
5670 Arc::new(UInt64Array::from(scores)),
5671 Arc::new(UInt64Array::from(row_ids)),
5672 Arc::new(Int64Array::from(payloads)),
5673 ],
5674 )
5675 .unwrap()
5676 }
5677
5678 fn collect_i64(batches: &[RecordBatch], column: &str) -> Vec<i64> {
5679 let mut values = Vec::new();
5680 for batch in batches {
5681 let index = batch.schema().index_of(column).unwrap();
5682 let array = batch
5683 .column(index)
5684 .as_any()
5685 .downcast_ref::<Int64Array>()
5686 .unwrap();
5687 for row in 0..batch.num_rows() {
5688 assert!(
5689 !array.is_null(row),
5690 "column {column} has an unexpected null"
5691 );
5692 values.push(array.value(row));
5693 }
5694 }
5695 values
5696 }
5697
5698 fn collect_u64(batches: &[RecordBatch], column: &str) -> Vec<u64> {
5699 let mut values = Vec::new();
5700 for batch in batches {
5701 let index = batch.schema().index_of(column).unwrap();
5702 let array = batch
5703 .column(index)
5704 .as_any()
5705 .downcast_ref::<UInt64Array>()
5706 .unwrap();
5707 for row in 0..batch.num_rows() {
5708 values.push(array.value(row));
5709 }
5710 }
5711 values
5712 }
5713
5714 fn collect_f64(batches: &[RecordBatch], column: &str) -> Vec<Option<f64>> {
5715 let mut values = Vec::new();
5716 for batch in batches {
5717 let index = batch.schema().index_of(column).unwrap();
5718 let array = batch
5719 .column(index)
5720 .as_any()
5721 .downcast_ref::<Float64Array>()
5722 .unwrap();
5723 for row in 0..batch.num_rows() {
5724 values.push((!array.is_null(row)).then(|| array.value(row)));
5725 }
5726 }
5727 values
5728 }
5729
5730 fn operators(plan: &DistributedPlan, fragment_id: FragmentId) -> &[FragmentOperator] {
5731 &plan.fragments[fragment_id as usize].operators
5732 }
5733
5734 #[test]
5739 fn scan_plan_shape_estimates_and_spill() {
5740 let tablets = [tablet(1), tablet(2), tablet(3)];
5741 let (locator, metadata) = world(&[(
5742 "t",
5743 &tablets,
5744 hash_spec("id"),
5745 stats(3_000, 3 * 1024 * 1024),
5746 )]);
5747 let description = desc(scan("t"));
5748 let plan = distribute(&description, &locator, &metadata).unwrap();
5749 assert_eq!(plan.query_id, description.query_id);
5750 assert_eq!(plan.metadata_version, MetadataVersion::new(7));
5751 assert_eq!(
5752 plan.fragments.len(),
5753 4,
5754 "3 scan fragments + coordinator gather"
5755 );
5756 for (index, id) in tablets.iter().enumerate() {
5757 let fragment = &plan.fragments[index];
5758 assert_eq!(fragment.assignment, FragmentAssignment::Tablet(*id));
5759 assert!(
5760 matches!(
5761 operators(&plan, fragment.fragment_id)[0],
5762 FragmentOperator::TabletScan { .. }
5763 ),
5764 "fragment {index} starts with a tablet scan"
5765 );
5766 assert!(
5767 matches!(
5768 operators(&plan, fragment.fragment_id).last().unwrap(),
5769 FragmentOperator::RemoteExchangeSink { .. }
5770 ),
5771 "fragment {index} ends with a sink"
5772 );
5773 assert_eq!(fragment.estimated_rows, 1_000);
5774 assert_eq!(fragment.estimated_bytes, 1024 * 1024);
5775 assert_eq!(
5776 fragment.max_spill_bytes,
5777 DEFAULT_MAX_SPILL_BYTES_PER_FRAGMENT
5778 );
5779 }
5780 let root = plan.root_fragment_id().unwrap();
5781 assert_eq!(root, 3);
5782 assert_eq!(
5783 plan.fragments[3].assignment,
5784 FragmentAssignment::Coordinator
5785 );
5786 assert_eq!(plan.exchanges.len(), 3);
5787 for edge in &plan.exchanges {
5788 assert_eq!(edge.kind, ExchangeKind::Merge);
5789 assert_eq!(edge.consumer, root);
5790 assert_eq!(
5791 edge.schema_fingerprint,
5792 plan.exchanges[0].schema_fingerprint
5793 );
5794 }
5795 let mut custom = desc(scan("t"));
5797 custom.options.max_spill_bytes_per_fragment = 1_234;
5798 let plan = distribute(&custom, &locator, &metadata).unwrap();
5799 assert!(plan
5800 .fragments
5801 .iter()
5802 .all(|fragment| fragment.max_spill_bytes == 1_234));
5803 }
5804
5805 #[test]
5806 fn fragment_control_begin_spill_opens_core_spill_session() {
5807 use mongreldb_core::ExecutionControl;
5808 use mongreldb_types::ids::QueryId;
5809
5810 let dir = tempfile::tempdir().unwrap();
5811 let db = mongreldb_core::Database::create(dir.path()).unwrap();
5813 let control = FragmentControl {
5814 control: ExecutionControl::new(None),
5815 max_spill_bytes: 64 * 1024,
5816 authorization_context: Arc::from([]),
5817 };
5818 let session = control
5819 .begin_spill(db.spill_manager(), QueryId::from_bytes([0xAB; 16]))
5820 .expect("begin_spill binds planner allowance to core SpillManager");
5821 let stats = db.spill_manager().stats();
5822 assert_eq!(
5823 stats.global_budget_bytes,
5824 db.spill_manager().config().global_bytes
5825 );
5826 drop(session);
5827 }
5828
5829 #[test]
5830 fn aggregate_plan_shape_grouped_and_ungrouped() {
5831 let tablets = [tablet(1), tablet(2), tablet(3)];
5832 let (locator, metadata) = world(&[("t", &tablets, hash_spec("id"), stats(900, 900_000))]);
5833 let aggregates = vec![
5834 AggregateExpr {
5835 function: AggregateFunction::Count,
5836 column: None,
5837 },
5838 AggregateExpr {
5839 function: AggregateFunction::Sum,
5840 column: Some("v".to_owned()),
5841 },
5842 ];
5843 let grouped = LogicalPlanLite::Aggregate {
5846 input: Box::new(scan("t")),
5847 group_by: vec!["g".to_owned()],
5848 aggregates: aggregates.clone(),
5849 };
5850 let plan = distribute(&desc(grouped), &locator, &metadata).unwrap();
5851 assert_eq!(plan.fragments.len(), 4);
5852 for index in 0..3 {
5853 assert!(matches!(
5854 operators(&plan, index as FragmentId)[1],
5855 FragmentOperator::PartialAggregate { .. }
5856 ));
5857 }
5858 assert_eq!(plan.exchanges.len(), 3);
5859 for edge in &plan.exchanges {
5860 assert_eq!(
5861 edge.kind,
5862 ExchangeKind::HashRepartition {
5863 keys: vec!["g".to_owned()]
5864 }
5865 );
5866 }
5867 let root = plan.root_fragment_id().unwrap();
5868 assert!(matches!(
5869 operators(&plan, root).last().unwrap(),
5870 FragmentOperator::FinalAggregate { .. }
5871 ));
5872 let ungrouped = LogicalPlanLite::Aggregate {
5874 input: Box::new(scan("t")),
5875 group_by: Vec::new(),
5876 aggregates: aggregates[..1].to_vec(),
5877 };
5878 let plan = distribute(&desc(ungrouped), &locator, &metadata).unwrap();
5879 assert!(plan
5880 .exchanges
5881 .iter()
5882 .all(|edge| edge.kind == ExchangeKind::Merge));
5883 let root = plan.root_fragment_id().unwrap();
5884 assert_eq!(plan.fragments[root as usize].estimated_rows, 1);
5885 }
5886
5887 #[test]
5888 fn colocated_join_plan_fuses_scans_without_exchange() {
5889 let tablets = [tablet(1), tablet(2), tablet(3)];
5890 let (locator, metadata) = world(&[
5891 (
5892 "a",
5893 &tablets,
5894 hash_spec("id"),
5895 stats(6_000, 6 * 1024 * 1024),
5896 ),
5897 (
5898 "b",
5899 &tablets,
5900 hash_spec("id"),
5901 stats(3_000, 3 * 1024 * 1024),
5902 ),
5903 ]);
5904 let join = LogicalPlanLite::Join {
5905 left: Box::new(scan("a")),
5906 right: Box::new(scan("b")),
5907 on: vec![JoinKey {
5908 left: "id".to_owned(),
5909 right: "id".to_owned(),
5910 }],
5911 };
5912 let plan = distribute(&desc(join), &locator, &metadata).unwrap();
5913 assert_eq!(plan.fragments.len(), 4, "3 fused fragments + gather");
5914 for index in 0..3 {
5915 let ops = operators(&plan, index as FragmentId);
5916 assert!(
5917 matches!(&ops[0], FragmentOperator::TabletScan { table, .. } if table == "a"),
5918 "left scan first: {ops:?}"
5919 );
5920 assert!(
5921 matches!(&ops[1], FragmentOperator::TabletScan { table, .. } if table == "b"),
5922 "right scan second: {ops:?}"
5923 );
5924 assert!(
5925 matches!(&ops[2], FragmentOperator::DistributedHashJoin { .. }),
5926 "fused hash join: {ops:?}"
5927 );
5928 }
5929 assert!(
5930 plan.exchanges
5931 .iter()
5932 .all(|edge| edge.kind == ExchangeKind::Merge),
5933 "a colocated join has no shuffle: {:?}",
5934 plan.exchanges
5935 );
5936 assert_eq!(plan.exchanges.len(), 3, "only the gather edges remain");
5937 }
5938
5939 #[test]
5940 fn broadcast_join_plan_replicates_the_small_side() {
5941 let big_tablets = [tablet(1), tablet(2), tablet(3)];
5942 let small_tablets = [tablet(4), tablet(5)];
5943 let (locator, metadata) = world(&[
5944 (
5945 "big",
5946 &big_tablets,
5947 hash_spec("id"),
5948 stats(8_000, 64 * 1024 * 1024),
5949 ),
5950 (
5951 "small",
5952 &small_tablets,
5953 hash_spec("key"),
5954 stats(100, 1024 * 1024),
5955 ),
5956 ]);
5957 let join = LogicalPlanLite::Join {
5958 left: Box::new(scan("big")),
5959 right: Box::new(scan("small")),
5960 on: vec![JoinKey {
5961 left: "id".to_owned(),
5962 right: "key".to_owned(),
5963 }],
5964 };
5965 let plan = distribute(&desc(join), &locator, &metadata).unwrap();
5966 assert_eq!(plan.fragments.len(), 6);
5968 let root = plan.root_fragment_id().unwrap();
5969 let broadcast_edges: Vec<&ExchangeDescriptor> = plan
5970 .exchanges
5971 .iter()
5972 .filter(|edge| edge.kind == ExchangeKind::Broadcast)
5973 .collect();
5974 assert_eq!(
5975 broadcast_edges.len(),
5976 6,
5977 "each small producer x each big fragment"
5978 );
5979 for edge in &broadcast_edges {
5980 assert!(edge.producer >= 3, "small side produces the broadcast");
5981 assert!(edge.consumer < 3, "big side consumes the broadcast");
5982 }
5983 for index in 0..3 {
5984 let ops = operators(&plan, index as FragmentId);
5985 let last_transform = ops
5986 .iter()
5987 .rfind(|op| !matches!(op, FragmentOperator::RemoteExchangeSink { .. }))
5988 .unwrap();
5989 assert!(matches!(
5990 last_transform,
5991 FragmentOperator::BroadcastJoin {
5992 build_side: BuildSide::Right,
5993 ..
5994 }
5995 ));
5996 let sources = ops
5997 .iter()
5998 .filter(|op| matches!(op, FragmentOperator::RemoteExchangeSource { .. }))
5999 .count();
6000 assert_eq!(sources, 2, "one source per small producer");
6001 }
6002 assert!(plan
6003 .exchanges
6004 .iter()
6005 .filter(|edge| edge.consumer == root)
6006 .all(|edge| edge.kind == ExchangeKind::Merge && edge.producer < 3));
6007 }
6008
6009 #[test]
6010 fn repartition_join_plan_shuffles_both_sides() {
6011 let left_tablets = [tablet(1), tablet(2)];
6012 let right_tablets = [tablet(3), tablet(4), tablet(5)];
6013 let (locator, metadata) = world(&[
6014 (
6015 "l",
6016 &left_tablets,
6017 hash_spec("a"),
6018 stats(8_000, 64 * 1024 * 1024),
6019 ),
6020 (
6021 "r",
6022 &right_tablets,
6023 hash_spec("b"),
6024 stats(9_000, 96 * 1024 * 1024),
6025 ),
6026 ]);
6027 let join = LogicalPlanLite::Join {
6028 left: Box::new(scan("l")),
6029 right: Box::new(scan("r")),
6030 on: vec![JoinKey {
6031 left: "a".to_owned(),
6032 right: "b".to_owned(),
6033 }],
6034 };
6035 let plan = distribute(&desc(join), &locator, &metadata).unwrap();
6036 assert_eq!(plan.fragments.len(), 9);
6038 let root = plan.root_fragment_id().unwrap();
6039 let shuffles: Vec<&ExchangeDescriptor> = plan
6040 .exchanges
6041 .iter()
6042 .filter(|edge| matches!(edge.kind, ExchangeKind::HashRepartition { .. }))
6043 .collect();
6044 assert_eq!(shuffles.len(), 2 * 3 + 3 * 3);
6045 let left_keys: Vec<&ExchangeDescriptor> = shuffles
6046 .iter()
6047 .filter(|edge| edge.producer < 2)
6048 .copied()
6049 .collect();
6050 assert!(
6051 left_keys
6052 .iter()
6053 .all(|edge| matches!(&edge.kind, ExchangeKind::HashRepartition { keys } if keys == &vec!["a".to_owned()]))
6054 );
6055 for join_fragment in 5..8 {
6056 let fragment = &plan.fragments[join_fragment];
6057 assert_eq!(
6058 fragment.assignment,
6059 FragmentAssignment::Tablet(right_tablets[(join_fragment - 5) % 3]),
6060 "join fragments round-robin over the larger side's tablets"
6061 );
6062 let ops = operators(&plan, fragment.fragment_id);
6063 let sources = ops
6064 .iter()
6065 .filter(|op| matches!(op, FragmentOperator::RemoteExchangeSource { .. }))
6066 .count();
6067 assert_eq!(sources, 5, "2 left + 3 right sources: {ops:?}");
6068 let last_transform = ops
6069 .iter()
6070 .rfind(|op| !matches!(op, FragmentOperator::RemoteExchangeSink { .. }))
6071 .unwrap();
6072 assert!(matches!(
6073 last_transform,
6074 FragmentOperator::RepartitionJoin { .. }
6075 ));
6076 }
6077 assert!(plan
6078 .exchanges
6079 .iter()
6080 .filter(|edge| edge.consumer == root)
6081 .all(|edge| edge.kind == ExchangeKind::Merge));
6082 }
6083
6084 #[test]
6085 fn sort_and_limit_plan_shapes() {
6086 let tablets = [tablet(1), tablet(2), tablet(3)];
6087 let (locator, metadata) = world(&[("t", &tablets, hash_spec("id"), stats(900, 900_000))]);
6088
6089 let topk = LogicalPlanLite::Sort {
6091 input: Box::new(scan("t")),
6092 keys: vec![SortKey {
6093 column: "score".to_owned(),
6094 descending: true,
6095 }],
6096 limit: Some(10),
6097 };
6098 let plan = distribute(&desc(topk), &locator, &metadata).unwrap();
6099 for index in 0..3 {
6100 assert!(matches!(
6101 operators(&plan, index as FragmentId)[1],
6102 FragmentOperator::DistributedTopK { k: 10, .. }
6103 ));
6104 }
6105 let root = plan.root_fragment_id().unwrap();
6106 assert!(matches!(
6107 operators(&plan, root).last().unwrap(),
6108 FragmentOperator::DistributedTopK { k: 10, .. }
6109 ));
6110 assert!(plan
6111 .exchanges
6112 .iter()
6113 .all(|edge| edge.kind == ExchangeKind::Merge));
6114
6115 let sort = LogicalPlanLite::Sort {
6117 input: Box::new(scan("t")),
6118 keys: vec![SortKey {
6119 column: "x".to_owned(),
6120 descending: false,
6121 }],
6122 limit: None,
6123 };
6124 let plan = distribute(&desc(sort), &locator, &metadata).unwrap();
6125 for index in 0..3 {
6126 assert!(matches!(
6127 operators(&plan, index as FragmentId)[1],
6128 FragmentOperator::MergeSort { limit: None, .. }
6129 ));
6130 }
6131 let root = plan.root_fragment_id().unwrap();
6132 assert!(matches!(
6133 operators(&plan, root).last().unwrap(),
6134 FragmentOperator::MergeSort { limit: None, .. }
6135 ));
6136
6137 let multi = LogicalPlanLite::Sort {
6140 input: Box::new(scan("t")),
6141 keys: vec![
6142 SortKey {
6143 column: "a".to_owned(),
6144 descending: false,
6145 },
6146 SortKey {
6147 column: "b".to_owned(),
6148 descending: true,
6149 },
6150 ],
6151 limit: Some(5),
6152 };
6153 let plan = distribute(&desc(multi), &locator, &metadata).unwrap();
6154 for index in 0..3 {
6155 assert!(matches!(
6156 operators(&plan, index as FragmentId)[1],
6157 FragmentOperator::MergeSort { limit: Some(5), .. }
6158 ));
6159 }
6160 let root = plan.root_fragment_id().unwrap();
6161 assert!(matches!(
6162 operators(&plan, root).last().unwrap(),
6163 FragmentOperator::MergeSort { limit: Some(5), .. }
6164 ));
6165
6166 let limit = LogicalPlanLite::Limit {
6168 input: Box::new(scan("t")),
6169 limit: 7,
6170 };
6171 let plan = distribute(&desc(limit), &locator, &metadata).unwrap();
6172 for index in 0..3 {
6173 assert!(matches!(
6174 operators(&plan, index as FragmentId)[1],
6175 FragmentOperator::DistributedLimit { limit: 7 }
6176 ));
6177 }
6178 let root = plan.root_fragment_id().unwrap();
6179 assert!(matches!(
6180 operators(&plan, root).last().unwrap(),
6181 FragmentOperator::DistributedLimit { limit: 7 }
6182 ));
6183
6184 let chained = LogicalPlanLite::Limit {
6187 input: Box::new(LogicalPlanLite::Aggregate {
6188 input: Box::new(scan("t")),
6189 group_by: Vec::new(),
6190 aggregates: vec![AggregateExpr {
6191 function: AggregateFunction::Count,
6192 column: None,
6193 }],
6194 }),
6195 limit: 5,
6196 };
6197 let plan = distribute(&desc(chained), &locator, &metadata).unwrap();
6198 assert_eq!(plan.fragments.len(), 4);
6199 let root = plan.root_fragment_id().unwrap();
6200 let ops = operators(&plan, root);
6201 assert!(matches!(
6202 ops[ops.len() - 2],
6203 FragmentOperator::FinalAggregate { .. }
6204 ));
6205 assert!(matches!(
6206 ops[ops.len() - 1],
6207 FragmentOperator::DistributedLimit { limit: 5 }
6208 ));
6209 }
6210
6211 #[test]
6212 fn planner_rejects_invalid_inputs() {
6213 let tablets = [tablet(1)];
6214 let (locator, metadata) = world(&[("t", &tablets, hash_spec("id"), stats(10, 100))]);
6215 let error = distribute(&desc(scan("nope")), &locator, &metadata).unwrap_err();
6217 assert!(
6218 matches!(error, DistributedError::UnknownTable(_)),
6219 "{error}"
6220 );
6221 let (locator, metadata) = world(&[("e", &[], hash_spec("id"), stats(0, 0))]);
6223 let error = distribute(&desc(scan("e")), &locator, &metadata).unwrap_err();
6224 assert!(
6225 matches!(error, DistributedError::EmptyLayout { .. }),
6226 "{error}"
6227 );
6228 let (locator, metadata) = world(&[("t", &tablets, hash_spec("id"), stats(10, 100))]);
6229 let join = LogicalPlanLite::Join {
6231 left: Box::new(scan("t")),
6232 right: Box::new(scan("t")),
6233 on: Vec::new(),
6234 };
6235 let error = distribute(&desc(join), &locator, &metadata).unwrap_err();
6236 assert!(matches!(error, DistributedError::InvalidPlan(_)), "{error}");
6237 let aggregate = LogicalPlanLite::Aggregate {
6239 input: Box::new(scan("t")),
6240 group_by: Vec::new(),
6241 aggregates: vec![AggregateExpr {
6242 function: AggregateFunction::Sum,
6243 column: None,
6244 }],
6245 };
6246 let error = distribute(&desc(aggregate), &locator, &metadata).unwrap_err();
6247 assert!(matches!(error, DistributedError::InvalidPlan(_)), "{error}");
6248 let sort = LogicalPlanLite::Sort {
6250 input: Box::new(scan("t")),
6251 keys: Vec::new(),
6252 limit: None,
6253 };
6254 let error = distribute(&desc(sort), &locator, &metadata).unwrap_err();
6255 assert!(matches!(error, DistributedError::InvalidPlan(_)), "{error}");
6256 }
6257
6258 fn candidate(score: u64, tablet_n: u8, row_id: u64) -> TopKCandidate {
6263 TopKCandidate {
6264 score,
6265 tablet: tablet(tablet_n),
6266 row_id: RowId(row_id),
6267 }
6268 }
6269
6270 #[test]
6271 fn topk_tie_break_is_exact() {
6272 let mut rows = vec![
6274 candidate(5, 2, 0),
6275 candidate(5, 1, 2),
6276 candidate(7, 3, 0),
6277 candidate(5, 1, 0),
6278 candidate(3, 1, 0),
6279 ];
6280 rows.sort_by(topk_cmp);
6281 assert_eq!(
6282 rows,
6283 vec![
6284 candidate(7, 3, 0),
6285 candidate(5, 1, 0),
6286 candidate(5, 1, 2),
6287 candidate(5, 2, 0),
6288 candidate(3, 1, 0),
6289 ]
6290 );
6291 }
6292
6293 #[test]
6294 fn merge_top_k_needs_no_refill_when_tablets_are_exhausted() {
6295 let shards = vec![
6296 TabletTopK {
6297 tablet: tablet(1),
6298 rows: vec![candidate(100, 1, 0), candidate(90, 1, 1)],
6299 unseen_bound: None,
6300 },
6301 TabletTopK {
6302 tablet: tablet(2),
6303 rows: vec![candidate(96, 2, 0)],
6304 unseen_bound: None,
6305 },
6306 ];
6307 let merge = merge_top_k(&shards, 2);
6308 assert_eq!(
6309 merge.winners,
6310 vec![candidate(100, 1, 0), candidate(96, 2, 0)]
6311 );
6312 assert!(merge.refill.is_empty());
6313 }
6314
6315 #[test]
6316 fn merge_top_k_refill_rules_follow_the_bounds() {
6317 let shards = vec![
6319 TabletTopK {
6320 tablet: tablet(1),
6321 rows: vec![candidate(100, 1, 0), candidate(90, 1, 1)],
6322 unseen_bound: Some(95),
6323 },
6324 TabletTopK {
6325 tablet: tablet(2),
6326 rows: vec![candidate(96, 2, 0)],
6327 unseen_bound: None,
6328 },
6329 ];
6330 let merge = merge_top_k(&shards, 2);
6331 assert!(
6332 merge.refill.is_empty(),
6333 "unseen scores of tablet 1 are at most 95 < 96: {:?}",
6334 merge.refill
6335 );
6336 let shards = vec![
6338 TabletTopK {
6339 tablet: tablet(1),
6340 rows: vec![candidate(100, 1, 0), candidate(90, 1, 1)],
6341 unseen_bound: Some(97),
6342 },
6343 TabletTopK {
6344 tablet: tablet(2),
6345 rows: vec![candidate(96, 2, 0)],
6346 unseen_bound: None,
6347 },
6348 ];
6349 let merge = merge_top_k(&shards, 2);
6350 assert_eq!(merge.refill, vec![tablet(1)]);
6351 let shards = vec![
6354 TabletTopK {
6355 tablet: tablet(1),
6356 rows: vec![candidate(100, 1, 0)],
6357 unseen_bound: Some(96),
6358 },
6359 TabletTopK {
6360 tablet: tablet(2),
6361 rows: vec![candidate(96, 2, 9)],
6362 unseen_bound: None,
6363 },
6364 ];
6365 let merge = merge_top_k(&shards, 2);
6366 assert_eq!(
6367 merge.refill,
6368 vec![tablet(1)],
6369 "(96, tablet 1, min row id) ranks better than (96, tablet 2, 9)"
6370 );
6371 let shards = vec![
6373 TabletTopK {
6374 tablet: tablet(1),
6375 rows: vec![candidate(5, 1, 0)],
6376 unseen_bound: Some(1),
6377 },
6378 TabletTopK {
6379 tablet: tablet(2),
6380 rows: Vec::new(),
6381 unseen_bound: None,
6382 },
6383 ];
6384 let merge = merge_top_k(&shards, 3);
6385 assert_eq!(merge.winners, vec![candidate(5, 1, 0)]);
6386 assert_eq!(merge.refill, vec![tablet(1)]);
6387 let merge = merge_top_k(&shards, 0);
6389 assert!(merge.winners.is_empty() && merge.refill.is_empty());
6390 }
6391
6392 #[test]
6393 fn exact_top_k_fills_winners_via_adaptive_refill() {
6394 let tablet_one_rows = vec![
6396 candidate(100, 1, 0),
6397 candidate(99, 1, 1),
6398 candidate(98, 1, 2),
6399 candidate(97, 1, 3),
6400 ];
6401 let shards = vec![
6402 TabletTopK {
6403 tablet: tablet(1),
6404 rows: tablet_one_rows[..2].to_vec(),
6405 unseen_bound: Some(98),
6406 },
6407 TabletTopK {
6408 tablet: tablet(2),
6409 rows: vec![candidate(96, 2, 0), candidate(95, 2, 1)],
6410 unseen_bound: None,
6411 },
6412 ];
6413 let remaining = tablet_one_rows.clone();
6414 let result = exact_top_k(3, shards, move |tablet_id| {
6415 assert_eq!(tablet_id, tablet(1));
6416 TabletTopK {
6417 tablet: tablet_id,
6418 rows: remaining[2..].to_vec(),
6419 unseen_bound: None,
6420 }
6421 })
6422 .unwrap();
6423 assert_eq!(
6424 result,
6425 vec![
6426 candidate(100, 1, 0),
6427 candidate(99, 1, 1),
6428 candidate(98, 1, 2),
6429 ]
6430 );
6431 }
6432
6433 #[test]
6434 fn exact_top_k_rejects_a_stuck_refill() {
6435 let shards = vec![TabletTopK {
6436 tablet: tablet(1),
6437 rows: Vec::new(),
6438 unseen_bound: Some(10),
6439 }];
6440 let error = exact_top_k(1, shards, |tablet| TabletTopK {
6441 tablet,
6442 rows: Vec::new(),
6443 unseen_bound: Some(10),
6444 })
6445 .unwrap_err();
6446 assert!(matches!(error, DistributedError::InvalidPlan(_)), "{error}");
6447 }
6448
6449 #[test]
6450 fn exact_top_k_matches_single_node_oracle_across_1000_sharded_datasets() {
6451 let mut total_refills = 0usize;
6452 for seed in 0..1_000u64 {
6453 let mut rng = SplitMix64(seed.wrapping_mul(0x9E37_79B9_7F4A_7C15).wrapping_add(1));
6454 let tablet_count = 1 + rng.below(8) as usize;
6455 let k = rng.below(30) as usize;
6456 let batch = 1 + rng.below(4) as usize;
6457 let mut oracle_rows = Vec::new();
6458 let mut per_tablet: HashMap<TabletId, Vec<TopKCandidate>> = HashMap::new();
6459 for index in 0..tablet_count {
6460 let tablet = tablet(index as u8 + 1);
6461 let row_count = rng.below(40) as usize;
6462 let mut rows: Vec<TopKCandidate> = (0..row_count)
6463 .map(|row| TopKCandidate {
6464 score: rng.below(12),
6465 tablet,
6466 row_id: RowId(row as u64),
6467 })
6468 .collect();
6469 rows.sort_by(topk_cmp);
6470 oracle_rows.extend(rows.iter().copied());
6471 per_tablet.insert(tablet, rows);
6472 }
6473 let mut oracle = oracle_rows;
6474 oracle.sort_by(topk_cmp);
6475 oracle.truncate(k);
6476 let contribution = |tablet: TabletId, offset: usize| -> TabletTopK {
6477 let rows = &per_tablet[&tablet];
6478 let start = offset.min(rows.len());
6479 let end = (offset + batch).min(rows.len());
6480 TabletTopK {
6481 tablet,
6482 rows: rows[start..end].to_vec(),
6483 unseen_bound: rows.get(end).map(|candidate| candidate.score),
6484 }
6485 };
6486 let initial: Vec<TabletTopK> = (0..tablet_count)
6487 .map(|index| contribution(tablet(index as u8 + 1), 0))
6488 .collect();
6489 let mut returned: HashMap<TabletId, usize> = (0..tablet_count)
6490 .map(|index| (tablet(index as u8 + 1), batch))
6491 .collect();
6492 let result = exact_top_k(k, initial, |tablet| {
6493 total_refills += 1;
6494 let offset = returned[&tablet];
6495 returned.insert(tablet, offset + batch);
6496 contribution(tablet, offset)
6497 })
6498 .unwrap_or_else(|error| panic!("seed {seed}: {error}"));
6499 assert_eq!(result, oracle, "seed {seed}: k={k} batch={batch}");
6500 }
6501 assert!(
6502 total_refills > 0,
6503 "the property run must actually exercise adaptive refill"
6504 );
6505 }
6506
6507 fn coordinator_with(
6514 executor: Arc<dyn FragmentExecutor>,
6515 ) -> (
6516 Arc<Coordinator>,
6517 Arc<InMemoryTransport>,
6518 Arc<SqlQueryRegistry>,
6519 ) {
6520 let transport = Arc::new(InMemoryTransport::new(executor));
6521 let registry = Arc::new(SqlQueryRegistry::default());
6522 let coordinator = Arc::new(Coordinator::new(
6523 Arc::clone(&transport) as Arc<dyn FragmentTransport>,
6524 Arc::clone(®istry),
6525 ));
6526 (coordinator, transport, registry)
6527 }
6528
6529 struct BlockingExecutor {
6532 barrier: Arc<tokio::sync::Barrier>,
6533 }
6534
6535 #[async_trait::async_trait]
6536 impl FragmentExecutor for BlockingExecutor {
6537 async fn execute(
6538 &self,
6539 _fragment: &PlanFragment,
6540 _inputs: Vec<FragmentStream>,
6541 control: FragmentControl,
6542 ) -> DistributedResult<FragmentStream> {
6543 self.barrier.wait().await;
6544 control.control.cancelled().await;
6545 Err(DistributedError::Cancelled(control.control.reason()))
6546 }
6547 }
6548
6549 fn scan_plan(table: &str) -> (DistributedPlan, Vec<TabletId>) {
6552 let tablets = [tablet(1), tablet(2), tablet(3)];
6553 let (locator, metadata) = world(&[(
6554 table,
6555 &tablets,
6556 hash_spec("id"),
6557 stats(3_000, 3 * 1024 * 1024),
6558 )]);
6559 let plan = distribute(&desc(scan(table)), &locator, &metadata).unwrap();
6560 (plan, tablets.to_vec())
6561 }
6562
6563 #[tokio::test]
6564 async fn scan_gather_returns_all_rows() {
6565 let store = Arc::new(InMemoryTableStore::new());
6566 let tablets = [tablet(1), tablet(2), tablet(3)];
6567 store.insert("t", tablets[0], i64_batch(&[("v", vec![1, 2])]));
6568 store.insert("t", tablets[1], i64_batch(&[("v", vec![3])]));
6569 let (plan, _) = scan_plan("t");
6571 let (coordinator, _, _) = coordinator_with(Arc::new(InMemoryFragmentExecutor::new(store)));
6572 let batches = coordinator.execute(&plan).await.unwrap();
6573 let mut values = collect_i64(&batches, "v");
6574 values.sort_unstable();
6575 assert_eq!(values, vec![1, 2, 3]);
6576 }
6577
6578 #[tokio::test]
6579 async fn remote_arrow_ipc_transport_gathers_with_pull_backpressure() {
6580 let store = Arc::new(InMemoryTableStore::new());
6581 let tablets = [tablet(1), tablet(2), tablet(3)];
6582 store.insert("t", tablets[0], i64_batch(&[("v", vec![1, 2])]));
6583 store.insert("t", tablets[1], i64_batch(&[("v", vec![3])]));
6584 let executor: Arc<dyn FragmentExecutor> = Arc::new(InMemoryFragmentExecutor::new(store));
6585 let endpoint = Arc::new(RemoteFragmentEndpoint::new(executor));
6586 let client: Arc<dyn FragmentRpcClient> =
6587 Arc::new(LoopbackFragmentRpcClient::new(Arc::clone(&endpoint)));
6588 let transport: Arc<dyn FragmentTransport> = Arc::new(RemoteFragmentTransport::new(client));
6589 let coordinator = Coordinator::new(transport, Arc::new(SqlQueryRegistry::default()));
6590 let (plan, _) = scan_plan("t");
6591
6592 let batches = coordinator.execute(&plan).await.unwrap();
6593 let mut values = collect_i64(&batches, "v");
6594 values.sort_unstable();
6595 assert_eq!(values, vec![1, 2, 3]);
6596 assert_eq!(
6597 endpoint.active_executions(),
6598 0,
6599 "terminal pulls release every worker cursor"
6600 );
6601 let metrics = endpoint.lifecycle_metrics();
6603 assert!(metrics.starts >= 1, "starts={}", metrics.starts);
6604 assert!(metrics.pulls >= 1, "pulls={}", metrics.pulls);
6605 assert!(metrics.completes >= 1, "completes={}", metrics.completes);
6606 assert!(metrics.bytes_in > 0, "bytes_in={}", metrics.bytes_in);
6607 assert!(metrics.bytes_out > 0, "bytes_out={}", metrics.bytes_out);
6608 assert_eq!(metrics.active_executions, 0);
6609 }
6610
6611 #[tokio::test]
6612 async fn merge_sort_matches_single_node_sort() {
6613 let store = Arc::new(InMemoryTableStore::new());
6614 let tablets = [tablet(1), tablet(2), tablet(3)];
6615 store.insert("s", tablets[0], i64_batch(&[("x", vec![5, 1, 9])]));
6616 store.insert("s", tablets[1], i64_batch(&[("x", vec![3, 7])]));
6617 store.insert("s", tablets[2], i64_batch(&[("x", vec![8, 2, 6, 4])]));
6618 let (locator, metadata) = world(&[("s", &tablets, hash_spec("id"), stats(9, 900))]);
6619 let (coordinator, _, _) = coordinator_with(Arc::new(InMemoryFragmentExecutor::new(store)));
6620
6621 let sort = LogicalPlanLite::Sort {
6623 input: Box::new(scan("s")),
6624 keys: vec![SortKey {
6625 column: "x".to_owned(),
6626 descending: false,
6627 }],
6628 limit: None,
6629 };
6630 let plan = distribute(&desc(sort), &locator, &metadata).unwrap();
6631 let batches = coordinator.execute(&plan).await.unwrap();
6632 assert_eq!(collect_i64(&batches, "x"), vec![1, 2, 3, 4, 5, 6, 7, 8, 9]);
6633
6634 let sort = LogicalPlanLite::Sort {
6636 input: Box::new(scan("s")),
6637 keys: vec![SortKey {
6638 column: "x".to_owned(),
6639 descending: false,
6640 }],
6641 limit: Some(4),
6642 };
6643 let plan = distribute(&desc(sort), &locator, &metadata).unwrap();
6644 let batches = coordinator.execute(&plan).await.unwrap();
6645 assert_eq!(collect_i64(&batches, "x"), vec![1, 2, 3, 4]);
6646 }
6647
6648 #[tokio::test]
6649 async fn final_aggregate_matches_single_node_aggregate() {
6650 let store = Arc::new(InMemoryTableStore::new());
6651 let tablets = [tablet(1), tablet(2), tablet(3)];
6652 store.insert(
6653 "t",
6654 tablets[0],
6655 i64_batch(&[("g", vec![1, 2, 1, 3]), ("v", vec![10, 20, 30, 40])]),
6656 );
6657 store.insert(
6658 "t",
6659 tablets[1],
6660 i64_batch(&[("g", vec![2, 1, 3]), ("v", vec![50, 60, 70])]),
6661 );
6662 store.insert(
6663 "t",
6664 tablets[2],
6665 i64_batch(&[("g", vec![3, 2]), ("v", vec![80, 90])]),
6666 );
6667 let (locator, metadata) = world(&[("t", &tablets, hash_spec("id"), stats(9, 900))]);
6668 let (coordinator, _, _) = coordinator_with(Arc::new(InMemoryFragmentExecutor::new(store)));
6669
6670 let aggregates = vec![
6671 AggregateExpr {
6672 function: AggregateFunction::Count,
6673 column: None,
6674 },
6675 AggregateExpr {
6676 function: AggregateFunction::Sum,
6677 column: Some("v".to_owned()),
6678 },
6679 AggregateExpr {
6680 function: AggregateFunction::Min,
6681 column: Some("v".to_owned()),
6682 },
6683 AggregateExpr {
6684 function: AggregateFunction::Max,
6685 column: Some("v".to_owned()),
6686 },
6687 AggregateExpr {
6688 function: AggregateFunction::Avg,
6689 column: Some("v".to_owned()),
6690 },
6691 ];
6692 let grouped = LogicalPlanLite::Aggregate {
6693 input: Box::new(scan("t")),
6694 group_by: vec!["g".to_owned()],
6695 aggregates,
6696 };
6697 let plan = distribute(&desc(grouped), &locator, &metadata).unwrap();
6698 let batches = coordinator.execute(&plan).await.unwrap();
6699 let groups = collect_i64(&batches, "g");
6700 let counts = collect_i64(&batches, "count_star");
6701 let sums = collect_i64(&batches, "sum_v");
6702 let mins = collect_i64(&batches, "min_v");
6703 let maxs = collect_i64(&batches, "max_v");
6704 let avgs = collect_f64(&batches, "avg_v");
6705 let mut oracle: HashMap<i64, (i64, i64, i64, i64)> = HashMap::new();
6707 for (g, v) in [
6708 (1, 10),
6709 (2, 20),
6710 (1, 30),
6711 (3, 40),
6712 (2, 50),
6713 (1, 60),
6714 (3, 70),
6715 (3, 80),
6716 (2, 90),
6717 ] {
6718 let entry = oracle.entry(g).or_insert((0, 0, i64::MAX, i64::MIN));
6719 entry.0 += 1;
6720 entry.1 += v;
6721 entry.2 = entry.2.min(v);
6722 entry.3 = entry.3.max(v);
6723 }
6724 assert_eq!(groups.len(), 3);
6725 for index in 0..groups.len() {
6726 let (count, sum, min, max) = oracle[&groups[index]];
6727 assert_eq!(counts[index], count, "count for g={}", groups[index]);
6728 assert_eq!(sums[index], sum, "sum for g={}", groups[index]);
6729 assert_eq!(mins[index], min, "min for g={}", groups[index]);
6730 assert_eq!(maxs[index], max, "max for g={}", groups[index]);
6731 let expected_avg = sum as f64 / count as f64;
6732 let actual_avg = avgs[index].expect("avg is set");
6733 assert!(
6734 (actual_avg - expected_avg).abs() < 1e-12,
6735 "avg for g={}: {actual_avg} vs {expected_avg}",
6736 groups[index]
6737 );
6738 }
6739
6740 let scalar = LogicalPlanLite::Aggregate {
6742 input: Box::new(scan("t")),
6743 group_by: Vec::new(),
6744 aggregates: vec![
6745 AggregateExpr {
6746 function: AggregateFunction::Count,
6747 column: None,
6748 },
6749 AggregateExpr {
6750 function: AggregateFunction::Sum,
6751 column: Some("v".to_owned()),
6752 },
6753 ],
6754 };
6755 let plan = distribute(&desc(scalar), &locator, &metadata).unwrap();
6756 let batches = coordinator.execute(&plan).await.unwrap();
6757 assert_eq!(collect_i64(&batches, "count_star"), vec![9]);
6758 assert_eq!(collect_i64(&batches, "sum_v"), vec![450]);
6759 }
6760
6761 #[tokio::test]
6762 async fn distributed_top_k_matches_oracle_with_and_without_refill() {
6763 let store = Arc::new(InMemoryTableStore::new());
6766 let tablets = [tablet(1), tablet(2), tablet(3)];
6767 store.insert(
6768 "k",
6769 tablets[0],
6770 score_batch(vec![100, 90, 80, 70], vec![0, 1, 2, 3], vec![0, 1, 2, 3]),
6771 );
6772 store.insert(
6773 "k",
6774 tablets[1],
6775 score_batch(vec![90, 80, 60], vec![0, 1, 2], vec![0, 1, 2]),
6776 );
6777 store.insert(
6778 "k",
6779 tablets[2],
6780 score_batch(vec![90, 80, 50], vec![0, 1, 2], vec![0, 1, 2]),
6781 );
6782 let (locator, metadata) = world(&[("k", &tablets, hash_spec("id"), stats(10, 1_000))]);
6783 let topk = LogicalPlanLite::Sort {
6784 input: Box::new(scan("k")),
6785 keys: vec![SortKey {
6786 column: "score".to_owned(),
6787 descending: true,
6788 }],
6789 limit: Some(5),
6790 };
6791 let mut oracle: Vec<TopKCandidate> = Vec::new();
6793 for (tablet_n, rows) in [
6794 (1u8, vec![(100u64, 0u64), (90, 1), (80, 2), (70, 3)]),
6795 (2u8, vec![(90, 0), (80, 1), (60, 2)]),
6796 (3u8, vec![(90, 0), (80, 1), (50, 2)]),
6797 ] {
6798 for (score, row_id) in rows {
6799 oracle.push(candidate(score, tablet_n, row_id));
6800 }
6801 }
6802 oracle.sort_by(topk_cmp);
6803 oracle.truncate(5);
6804 let expected: Vec<(u64, i64)> = oracle
6805 .iter()
6806 .map(|winner| (winner.score, winner.row_id.0 as i64))
6807 .collect();
6808
6809 let run = async |emit_batch: Option<usize>| {
6810 let executor = match emit_batch {
6811 Some(batch) => InMemoryFragmentExecutor::with_topk_emit_batch(store.clone(), batch),
6812 None => InMemoryFragmentExecutor::new(store.clone()),
6813 };
6814 let (coordinator, transport, _) = coordinator_with(Arc::new(executor));
6815 let plan = distribute(&desc(topk.clone()), &locator, &metadata).unwrap();
6816 let batches = coordinator.execute(&plan).await.unwrap();
6817 (batches, transport)
6818 };
6819
6820 let (batches, transport) = run(None).await;
6822 let scores = collect_u64(&batches, "score");
6823 let payloads = collect_i64(&batches, "payload");
6824 let actual: Vec<(u64, i64)> = scores.into_iter().zip(payloads).collect();
6825 assert_eq!(actual, expected);
6826 assert!(
6827 transport.refill_log().is_empty(),
6828 "exact local top-k never needs refill"
6829 );
6830 assert!(
6831 batches
6832 .iter()
6833 .all(|batch| batch.schema().index_of(TOPK_ROWID_COLUMN).is_err()),
6834 "the internal row-id column is stripped"
6835 );
6836
6837 let (batches, transport) = run(Some(2)).await;
6840 let scores = collect_u64(&batches, "score");
6841 let payloads = collect_i64(&batches, "payload");
6842 let actual: Vec<(u64, i64)> = scores.into_iter().zip(payloads).collect();
6843 assert_eq!(actual, expected);
6844 assert!(
6845 !transport.refill_log().is_empty(),
6846 "bounded emission must trigger adaptive refill"
6847 );
6848
6849 let executor: Arc<dyn FragmentExecutor> = Arc::new(
6852 InMemoryFragmentExecutor::with_topk_emit_batch(Arc::clone(&store), 2),
6853 );
6854 let endpoint = Arc::new(RemoteFragmentEndpoint::new(executor));
6855 let client: Arc<dyn FragmentRpcClient> =
6856 Arc::new(LoopbackFragmentRpcClient::new(Arc::clone(&endpoint)));
6857 let coordinator = Coordinator::new(
6858 Arc::new(RemoteFragmentTransport::new(client)),
6859 Arc::new(SqlQueryRegistry::default()),
6860 );
6861 let plan = distribute(&desc(topk), &locator, &metadata).unwrap();
6862 let batches = coordinator.execute(&plan).await.unwrap();
6863 let actual = collect_u64(&batches, "score")
6864 .into_iter()
6865 .zip(collect_i64(&batches, "payload"))
6866 .collect::<Vec<_>>();
6867 assert_eq!(actual, expected);
6868 assert_eq!(endpoint.active_executions(), 0);
6869 }
6870
6871 #[tokio::test]
6872 async fn cancellation_fans_out_to_every_fragment() {
6873 let barrier = Arc::new(tokio::sync::Barrier::new(4));
6874 let executor = Arc::new(BlockingExecutor {
6875 barrier: Arc::clone(&barrier),
6876 });
6877 let (coordinator, transport, _registry) = coordinator_with(executor);
6878 let (plan, _) = scan_plan("t");
6879 let task = {
6880 let coordinator = Arc::clone(&coordinator);
6881 let plan = plan.clone();
6882 tokio::spawn(async move { coordinator.execute(&plan).await })
6883 };
6884 tokio::time::timeout(Duration::from_secs(30), barrier.wait())
6885 .await
6886 .expect("all fragments started");
6887 assert!(coordinator.cancel_query(&plan.query_id).unwrap());
6888 let result = task.await.unwrap();
6889 assert!(
6890 matches!(
6891 result,
6892 Err(DistributedError::Cancelled(
6893 CancellationReason::ClientRequest
6894 ))
6895 ),
6896 "cancellation surfaces as ClientRequest: {result:?}"
6897 );
6898 let cancelled = transport.cancelled_fragments();
6899 for fragment_id in 0..3 {
6900 assert!(
6901 cancelled.contains(&fragment_id),
6902 "fragment {fragment_id} received the transport cancel: {cancelled:?}"
6903 );
6904 let control = transport.control_for(fragment_id).unwrap();
6905 assert_eq!(control.reason(), CancellationReason::ClientRequest);
6906 }
6907 }
6908
6909 #[tokio::test]
6910 async fn remote_cancellation_reaches_worker_during_fragment_start() {
6911 let barrier = Arc::new(tokio::sync::Barrier::new(4));
6912 let executor: Arc<dyn FragmentExecutor> = Arc::new(BlockingExecutor {
6913 barrier: Arc::clone(&barrier),
6914 });
6915 let endpoint = Arc::new(RemoteFragmentEndpoint::new(executor));
6916 let client: Arc<dyn FragmentRpcClient> =
6917 Arc::new(LoopbackFragmentRpcClient::new(Arc::clone(&endpoint)));
6918 let coordinator = Arc::new(Coordinator::new(
6919 Arc::new(RemoteFragmentTransport::new(client)),
6920 Arc::new(SqlQueryRegistry::default()),
6921 ));
6922 let (plan, _) = scan_plan("t");
6923 let task = {
6924 let coordinator = Arc::clone(&coordinator);
6925 let plan = plan.clone();
6926 tokio::spawn(async move { coordinator.execute(&plan).await })
6927 };
6928 tokio::time::timeout(Duration::from_secs(30), barrier.wait())
6929 .await
6930 .expect("all remote workers entered fragment start");
6931 assert!(coordinator.cancel_query(&plan.query_id).unwrap());
6932 let result = tokio::time::timeout(Duration::from_secs(30), task)
6933 .await
6934 .expect("remote cancellation must not hang")
6935 .unwrap();
6936 assert!(matches!(result, Err(DistributedError::Cancelled(_))));
6937 assert_eq!(endpoint.active_executions(), 0);
6938 }
6939
6940 #[tokio::test]
6941 async fn registry_cancel_reaches_all_fragments() {
6942 let barrier = Arc::new(tokio::sync::Barrier::new(4));
6943 let executor = Arc::new(BlockingExecutor {
6944 barrier: Arc::clone(&barrier),
6945 });
6946 let (coordinator, transport, registry) = coordinator_with(executor);
6947 let (plan, _) = scan_plan("t");
6948 let task = {
6949 let coordinator = Arc::clone(&coordinator);
6950 let plan = plan.clone();
6951 tokio::spawn(async move { coordinator.execute(&plan).await })
6952 };
6953 tokio::time::timeout(Duration::from_secs(30), barrier.wait())
6954 .await
6955 .expect("all fragments started");
6956 let bridged: crate::query_registry::QueryId = plan.query_id.to_hex().parse().unwrap();
6958 assert_eq!(registry.cancel(bridged), CancelOutcome::Accepted);
6959 let result = task.await.unwrap();
6960 assert!(
6961 matches!(result, Err(DistributedError::Cancelled(_))),
6962 "registry cancellation aborts the distributed query: {result:?}"
6963 );
6964 for fragment_id in 0..3 {
6965 let control = transport.control_for(fragment_id).unwrap();
6966 assert_eq!(
6967 control.reason(),
6968 CancellationReason::ClientRequest,
6969 "fragment {fragment_id} observed the registry cancel"
6970 );
6971 }
6972 }
6973
6974 #[tokio::test]
6975 async fn lease_expiry_reclaims_abandoned_fragments() {
6976 let barrier = Arc::new(tokio::sync::Barrier::new(4));
6977 let executor = Arc::new(BlockingExecutor {
6978 barrier: Arc::clone(&barrier),
6979 });
6980 let transport = Arc::new(InMemoryTransport::new(executor));
6981 let registry = Arc::new(SqlQueryRegistry::default());
6982 let coordinator = Arc::new(Coordinator::with_limits(
6983 Arc::clone(&transport) as Arc<dyn FragmentTransport>,
6984 registry,
6985 1_024,
6986 16 * 1024 * 1024 * 1024,
6987 Duration::from_secs(30),
6988 ));
6989 let (plan, _) = scan_plan("t");
6990 let task = {
6991 let coordinator = Arc::clone(&coordinator);
6992 let plan = plan.clone();
6993 tokio::spawn(async move { coordinator.execute(&plan).await })
6994 };
6995 tokio::time::timeout(Duration::from_secs(30), barrier.wait())
6996 .await
6997 .expect("all fragments started");
6998 assert_eq!(coordinator.resources().reserved_fragments(), 3);
6999 let cleaned = coordinator.sweep_expired_leases(Instant::now() + Duration::from_secs(3_600));
7001 assert_eq!(cleaned, 3, "every abandoned fragment is reclaimed");
7002 assert_eq!(
7003 coordinator.resources().reserved_fragments(),
7004 0,
7005 "reclaimed fragments release their reservations"
7006 );
7007 for fragment_id in 0..3 {
7008 let control = transport.control_for(fragment_id).unwrap();
7009 assert_eq!(
7010 control.reason(),
7011 CancellationReason::ServerShutdown,
7012 "fragment {fragment_id} observes the worker loss"
7013 );
7014 }
7015 let result = task.await.unwrap();
7016 assert!(
7017 matches!(
7018 result,
7019 Err(DistributedError::Cancelled(
7020 CancellationReason::ServerShutdown
7021 ))
7022 ),
7023 "the query fails with the worker-loss reason: {result:?}"
7024 );
7025 }
7026
7027 #[test]
7028 fn resource_reservation_denies_then_releases() {
7029 let ledger = Arc::new(ResourceLedger::new(1, u64::MAX));
7030 let fragment = PlanFragment {
7031 fragment_id: 0,
7032 assignment: FragmentAssignment::Coordinator,
7033 operators: Vec::new(),
7034 estimated_rows: 1,
7035 estimated_bytes: 100,
7036 max_spill_bytes: 0,
7037 };
7038 let permit = ledger.reserve(&fragment).unwrap();
7039 assert_eq!(ledger.reserved_fragments(), 1);
7040 assert_eq!(ledger.reserved_bytes(), 100);
7041 let denied = ledger.reserve(&fragment).unwrap_err();
7042 assert!(
7043 matches!(denied, DistributedError::Reservation { fragment_id: 0, .. }),
7044 "{denied}"
7045 );
7046 drop(permit);
7047 assert_eq!(ledger.reserved_fragments(), 0);
7048 assert_eq!(ledger.reserved_bytes(), 0);
7049 ledger.reserve(&fragment).unwrap();
7050 let tight = Arc::new(ResourceLedger::new(8, 50));
7052 let denied = tight.reserve(&fragment).unwrap_err();
7053 assert!(
7054 matches!(denied, DistributedError::Reservation { .. }),
7055 "{denied}"
7056 );
7057 }
7058
7059 #[test]
7060 fn repartition_routes_every_row_exactly_once() {
7061 let batch = i64_batch(&[("k", (0..100).collect()), ("v", (0..100).collect())]);
7062 let frames = vec![BatchFrame::data(batch)];
7063 let keys = vec!["k".to_owned()];
7064 let mut partitions = Vec::new();
7065 for index in 0..3 {
7066 partitions.push(repartition_frames(&frames, &keys, 3, index).unwrap());
7067 }
7068 let total: usize = partitions
7069 .iter()
7070 .map(|frames| {
7071 frames
7072 .iter()
7073 .map(|frame| frame.batch.num_rows())
7074 .sum::<usize>()
7075 })
7076 .sum();
7077 assert_eq!(total, 100, "every row lands in exactly one partition");
7078 for (index, partition) in partitions.iter().enumerate() {
7081 for other in 0..3 {
7082 let rerouted = repartition_frames(partition, &keys, 3, other).unwrap();
7083 let rows: usize = rerouted.iter().map(|frame| frame.batch.num_rows()).sum();
7084 if other == index {
7085 let expected: usize =
7086 partition.iter().map(|frame| frame.batch.num_rows()).sum();
7087 assert_eq!(rows, expected, "partition {index} rows stay in {index}");
7088 } else {
7089 assert_eq!(rows, 0, "partition {index} leaks rows into {other}");
7090 }
7091 }
7092 }
7093 }
7094
7095 #[test]
7096 fn score_key_mapping_preserves_order() {
7097 let ints = Int64Array::from(vec![i64::MIN, -5, 0, 5, i64::MAX]);
7098 let keys: Vec<u64> = (0..5).map(|row| score_key(&ints, row).unwrap()).collect();
7099 let mut sorted = keys.clone();
7100 sorted.sort_unstable();
7101 assert_eq!(keys, sorted, "int64 mapping is order-preserving");
7102 let floats = Float64Array::from(vec![f64::MIN, -1.5, 0.0, 2.5, f64::MAX]);
7103 let keys: Vec<u64> = (0..5).map(|row| score_key(&floats, row).unwrap()).collect();
7104 let mut sorted = keys.clone();
7105 sorted.sort_unstable();
7106 assert_eq!(keys, sorted, "float64 mapping is order-preserving");
7107 }
7108
7109 fn df_table_source(
7114 columns: &[(&str, DataType)],
7115 ) -> Arc<dyn datafusion::logical_expr::TableSource> {
7116 use datafusion::logical_expr::logical_plan::builder::LogicalTableSource;
7117 let fields = columns
7118 .iter()
7119 .map(|(name, dtype)| Field::new(*name, dtype.clone(), true))
7120 .collect::<Vec<_>>();
7121 Arc::new(LogicalTableSource::new(Arc::new(Schema::new(fields))))
7122 }
7123
7124 fn df_scan(table: &str, columns: &[(&str, DataType)]) -> datafusion::logical_expr::LogicalPlan {
7125 use datafusion::logical_expr::LogicalPlanBuilder;
7126 LogicalPlanBuilder::scan(table, df_table_source(columns), None)
7127 .unwrap()
7128 .build()
7129 .unwrap()
7130 }
7131
7132 #[test]
7133 fn datafusion_planner_lowers_scan_filter_agg_sort_limit() {
7134 use datafusion::functions_aggregate::expr_fn::sum;
7135 use datafusion::logical_expr::{col, lit, LogicalPlanBuilder};
7136
7137 let plan = LogicalPlanBuilder::from(df_scan(
7138 "orders",
7139 &[("region", DataType::Utf8), ("amount", DataType::Int64)],
7140 ))
7141 .filter(col("amount").gt(lit(10i64)))
7142 .unwrap()
7143 .aggregate(vec![col("region")], vec![sum(col("amount"))])
7144 .unwrap()
7145 .sort(vec![col("region").sort(true, true)])
7146 .unwrap()
7147 .limit(0, Some(5))
7148 .unwrap()
7149 .build()
7150 .unwrap();
7151
7152 let planner = DataFusionDistributedPlanner::new(QueryId::new_random());
7153 let lite = planner.to_lite(&plan).unwrap();
7154 match &lite {
7155 LogicalPlanLite::Limit { limit, input } => {
7156 assert_eq!(*limit, 5);
7157 match input.as_ref() {
7158 LogicalPlanLite::Sort { keys, input, .. } => {
7159 assert_eq!(keys.len(), 1);
7160 assert!(!keys[0].descending);
7161 match input.as_ref() {
7162 LogicalPlanLite::Aggregate {
7163 group_by,
7164 aggregates,
7165 input,
7166 } => {
7167 assert_eq!(group_by, &vec!["region".to_owned()]);
7168 assert_eq!(aggregates.len(), 1);
7169 assert_eq!(aggregates[0].function, AggregateFunction::Sum);
7170 match input.as_ref() {
7171 LogicalPlanLite::Scan {
7172 table, predicate, ..
7173 } => {
7174 assert_eq!(table, "orders");
7175 assert!(
7176 predicate
7177 .as_ref()
7178 .is_some_and(|p| p.contains("amount")),
7179 "predicate={predicate:?}"
7180 );
7181 }
7182 other => panic!("expected scan under aggregate, got {other:?}"),
7183 }
7184 }
7185 other => panic!("expected aggregate under sort, got {other:?}"),
7186 }
7187 }
7188 other => panic!("expected sort under limit, got {other:?}"),
7189 }
7190 }
7191 other => panic!("expected limit root, got {other:?}"),
7192 }
7193
7194 let t1 = TabletId::from_bytes([1; 16]);
7195 let t2 = TabletId::from_bytes([2; 16]);
7196 let (locator, metadata) = world(&[(
7197 "orders",
7198 &[t1, t2],
7199 hash_spec("region"),
7200 stats(1_000, 64_000),
7201 )]);
7202 let distributed = planner.lower(&plan, &locator, &metadata).unwrap();
7203 assert!(!distributed.fragments.is_empty());
7204 assert!(distributed.root_fragment_id().is_some());
7205 assert!(distributed.fragments.iter().any(|fragment| fragment
7206 .operators
7207 .iter()
7208 .any(|op| matches!(op, FragmentOperator::TabletScan { .. }))));
7209 }
7210
7211 #[test]
7212 fn datafusion_planner_lowers_join_and_union() {
7213 use datafusion::logical_expr::{JoinType, LogicalPlanBuilder};
7214
7215 let left = df_scan(
7216 "orders",
7217 &[("id", DataType::Int64), ("uid", DataType::Int64)],
7218 );
7219 let right = df_scan(
7220 "users",
7221 &[("id", DataType::Int64), ("name", DataType::Utf8)],
7222 );
7223 let join = LogicalPlanBuilder::from(left)
7224 .join(
7225 LogicalPlanBuilder::from(right).build().unwrap(),
7226 JoinType::Inner,
7227 (vec!["uid"], vec!["id"]),
7228 None,
7229 )
7230 .unwrap()
7231 .build()
7232 .unwrap();
7233
7234 let planner = DataFusionDistributedPlanner::new(QueryId::new_random());
7235 let lite = planner.to_lite(&join).unwrap();
7236 match lite {
7237 LogicalPlanLite::Join { on, left, right } => {
7238 assert_eq!(on.len(), 1);
7239 assert_eq!(on[0].left, "uid");
7240 assert_eq!(on[0].right, "id");
7241 assert!(
7242 matches!(left.as_ref(), LogicalPlanLite::Scan { table, .. } if table == "orders")
7243 );
7244 assert!(
7245 matches!(right.as_ref(), LogicalPlanLite::Scan { table, .. } if table == "users")
7246 );
7247 }
7248 other => panic!("expected join, got {other:?}"),
7249 }
7250
7251 let a = df_scan("a", &[("x", DataType::Int64)]);
7252 let b = df_scan("b", &[("x", DataType::Int64)]);
7253 let union = LogicalPlanBuilder::from(a)
7254 .union(LogicalPlanBuilder::from(b).build().unwrap())
7255 .unwrap()
7256 .build()
7257 .unwrap();
7258 let lite = planner.to_lite(&union).unwrap();
7259 match lite {
7260 LogicalPlanLite::Union { inputs } => {
7261 assert_eq!(inputs.len(), 2);
7262 }
7263 other => panic!("expected union, got {other:?}"),
7264 }
7265
7266 let t = TabletId::from_bytes([9; 16]);
7267 let (locator, metadata) = world(&[
7268 ("a", &[t], PartitionSpec::Unpartitioned, stats(10, 100)),
7269 ("b", &[t], PartitionSpec::Unpartitioned, stats(10, 100)),
7270 ("orders", &[t], hash_spec("uid"), stats(100, 1_000)),
7271 ("users", &[t], hash_spec("id"), stats(100, 1_000)),
7272 ]);
7273 planner.lower(&join, &locator, &metadata).unwrap();
7274 planner.lower(&union, &locator, &metadata).unwrap();
7275 }
7276
7277 #[test]
7278 fn datafusion_planner_rejects_unsupported_operators() {
7279 use datafusion::logical_expr::{col, lit, LogicalPlanBuilder};
7280
7281 let plan = LogicalPlanBuilder::from(df_scan("t", &[("v", DataType::Int64)]))
7282 .filter(col("v").gt(lit(1i64)))
7283 .unwrap()
7284 .distinct()
7285 .unwrap()
7286 .build()
7287 .unwrap();
7288 let planner = DataFusionDistributedPlanner::new(QueryId::new_random());
7289 let err = planner.to_lite(&plan).unwrap_err();
7290 assert!(
7291 matches!(err, DistributedError::Unsupported(_)),
7292 "distinct must be rejected: {err:?}"
7293 );
7294
7295 let values = LogicalPlanBuilder::values(vec![vec![lit(1i64)]])
7297 .unwrap()
7298 .build()
7299 .unwrap();
7300 let err = planner.to_lite(&values).unwrap_err();
7301 assert!(
7302 matches!(err, DistributedError::Unsupported(_)),
7303 "values must be rejected: {err:?}"
7304 );
7305 }
7306
7307 #[test]
7308 fn logical_plan_lite_union_plans_to_coordinator_gather() {
7309 let t1 = TabletId::from_bytes([1; 16]);
7310 let t2 = TabletId::from_bytes([2; 16]);
7311 let (locator, metadata) = world(&[
7312 ("a", &[t1], PartitionSpec::Unpartitioned, stats(10, 100)),
7313 ("b", &[t2], PartitionSpec::Unpartitioned, stats(20, 200)),
7314 ]);
7315 let plan = distribute(
7316 &desc(LogicalPlanLite::Union {
7317 inputs: vec![scan("a"), scan("b")],
7318 }),
7319 &locator,
7320 &metadata,
7321 )
7322 .unwrap();
7323 assert!(plan.root_fragment_id().is_some());
7324 assert!(plan
7325 .fragments
7326 .iter()
7327 .any(|f| f.assignment == FragmentAssignment::Coordinator));
7328 assert_eq!(
7329 plan.fragments
7330 .iter()
7331 .filter(|f| matches!(f.assignment, FragmentAssignment::Tablet(_)))
7332 .count(),
7333 2
7334 );
7335 }
7336
7337 #[tokio::test]
7338 async fn plan_sql_distributed_public_entry_lowers_real_sql() {
7339 let schema = Arc::new(Schema::new(vec![
7341 Field::new("region", DataType::Utf8, true),
7342 Field::new("amount", DataType::Int64, true),
7343 ]));
7344 let catalog = PlanningTableCatalog::new().with_table("orders", schema);
7345 let t1 = TabletId::from_bytes([1; 16]);
7346 let t2 = TabletId::from_bytes([2; 16]);
7347 let (locator, metadata) = world(&[(
7348 "orders",
7349 &[t1, t2],
7350 hash_spec("region"),
7351 stats(1_000, 64_000),
7352 )]);
7353
7354 let plan = plan_sql_distributed(
7357 "SELECT region, amount FROM orders WHERE amount > 10 LIMIT 5",
7358 &catalog,
7359 &locator,
7360 &metadata,
7361 )
7362 .await
7363 .expect("plan_sql_distributed must lower real SQL");
7364
7365 assert!(!plan.fragments.is_empty());
7366 assert!(plan.root_fragment_id().is_some());
7367 assert!(
7368 plan.fragments.iter().any(|fragment| fragment
7369 .operators
7370 .iter()
7371 .any(|op| matches!(op, FragmentOperator::TabletScan { table, .. } if table == "orders"))),
7372 "expected tablet scan of orders: {plan:?}"
7373 );
7374 assert_eq!(plan.metadata_version, metadata.metadata_version());
7375
7376 let ctx = datafusion::prelude::SessionContext::new();
7379 let provider = datafusion::datasource::MemTable::try_new(
7380 Arc::clone(catalog.schema("orders").unwrap()),
7381 vec![vec![RecordBatch::new_empty(Arc::clone(
7382 catalog.schema("orders").unwrap(),
7383 ))]],
7384 )
7385 .unwrap();
7386 ctx.register_table("orders", Arc::new(provider)).unwrap();
7387 let df = ctx
7388 .sql("SELECT region FROM orders WHERE amount > 0")
7389 .await
7390 .unwrap();
7391 let from_logical =
7392 plan_logical_distributed(df.logical_plan(), &locator, &metadata).unwrap();
7393 assert!(!from_logical.fragments.is_empty());
7394 }
7395
7396 #[tokio::test]
7397 async fn plan_sql_distributed_rejects_unknown_table() {
7398 let catalog = PlanningTableCatalog::new();
7399 let (locator, metadata) = world(&[]);
7400 let err = plan_sql_distributed("SELECT 1 FROM missing", &catalog, &locator, &metadata)
7401 .await
7402 .unwrap_err();
7403 assert!(
7404 matches!(err, DistributedError::InvalidPlan(_)),
7405 "unknown table must fail planning: {err:?}"
7406 );
7407 }
7408}