1use std::cmp::Ordering;
47use std::collections::{BTreeMap, BinaryHeap, HashMap};
48use std::io::Cursor;
49use std::sync::Arc;
50use std::time::{Duration, Instant};
51
52use arrow::array::{
53 Array, ArrayRef, Float64Array, Int64Array, RecordBatch, UInt32Array, UInt64Array,
54};
55use arrow::compute::{concat_batches, interleave, take};
56use arrow::datatypes::{DataType, Field, Schema, SchemaRef};
57use arrow::ipc::reader::StreamReader;
58use arrow::ipc::writer::StreamWriter;
59use arrow::row::{RowConverter, SortField};
60use arrow::util::display::array_value_to_string;
61use bincode::Options;
62use futures::stream::{self, BoxStream, FuturesUnordered, StreamExt};
63use mongreldb_core::{CancellationReason, ExecutionControl, RowId};
64use mongreldb_types::ids::{MetadataVersion, QueryId, TabletId};
65use parking_lot::Mutex;
66use serde::{Deserialize, Serialize};
67
68use crate::query_registry::{CancelOutcome, RegisteredSqlQuery, SqlQueryOptions, SqlQueryRegistry};
69
70pub const DEFAULT_BROADCAST_THRESHOLD_BYTES: u64 = 8 * 1024 * 1024;
73
74pub const DEFAULT_MAX_SPILL_BYTES_PER_FRAGMENT: u64 = 256 * 1024 * 1024;
78pub const MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES: usize = 64 * 1024;
80
81const COORDINATOR_OUTPUT_BATCH_ROWS: usize = 8_192;
83
84pub const TOPK_ROWID_COLUMN: &str = "__rowid";
88
89#[non_exhaustive]
91#[derive(Debug, thiserror::Error)]
92pub enum DistributedError {
93 #[error("unknown table `{0}`")]
95 UnknownTable(String),
96 #[error("table `{table}` has no tablets in its layout")]
98 EmptyLayout { table: String },
99 #[error("invalid plan: {0}")]
101 InvalidPlan(String),
102 #[error("fragment {fragment_id} resource reservation denied: {reason}")]
104 Reservation { fragment_id: u32, reason: String },
105 #[error("fragment {fragment_id} failed on worker {worker}: {message}")]
107 FragmentExecution {
108 fragment_id: u32,
109 worker: String,
110 message: String,
111 },
112 #[error("distributed query cancelled: {0:?}")]
114 Cancelled(CancellationReason),
115 #[error("unsupported in this wave: {0}")]
117 Unsupported(String),
118 #[error("arrow error: {0}")]
120 Arrow(String),
121 #[error("remote fragment transport error: {0}")]
123 RemoteTransport(String),
124 #[error("remote fragment protocol error: {0}")]
126 RemoteProtocol(String),
127}
128
129pub type DistributedResult<T> = Result<T, DistributedError>;
131
132impl From<arrow::error::ArrowError> for DistributedError {
133 fn from(error: arrow::error::ArrowError) -> Self {
134 Self::Arrow(error.to_string())
135 }
136}
137
138fn fnv1a64(bytes: &[u8]) -> u64 {
140 let mut hash = 0xcbf2_9ce4_8422_2325_u64;
141 for byte in bytes {
142 hash ^= u64::from(*byte);
143 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
144 }
145 hash
146}
147
148#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
156pub enum PartitionSpec {
157 Hash { columns: Vec<String>, buckets: u32 },
159 Range { columns: Vec<String> },
161 Tenant {
163 column: String,
164 buckets_per_tenant: u32,
165 },
166 TimeRange { column: String },
168 Unpartitioned,
170}
171
172impl PartitionSpec {
173 pub fn partition_columns(&self) -> Vec<&str> {
175 match self {
176 Self::Hash { columns, .. } | Self::Range { columns } => {
177 columns.iter().map(String::as_str).collect()
178 }
179 Self::Tenant { column, .. } | Self::TimeRange { column } => vec![column.as_str()],
180 Self::Unpartitioned => Vec::new(),
181 }
182 }
183
184 pub fn colocated_with(&self, other: &PartitionSpec) -> bool {
189 match (self, other) {
190 (Self::Unpartitioned, Self::Unpartitioned) => true,
191 (Self::Unpartitioned, _) | (_, Self::Unpartitioned) => false,
192 _ => self == other,
193 }
194 }
195}
196
197#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
199pub struct TableStats {
200 pub row_count: u64,
202 pub total_bytes: u64,
204}
205
206pub trait TabletLocator: Send + Sync {
211 fn tablets_for_table(&self, table: &str) -> DistributedResult<Vec<TabletId>>;
213 fn partitioning(&self, table: &str) -> DistributedResult<PartitionSpec>;
215}
216
217pub trait ClusterMetadata: Send + Sync {
220 fn metadata_version(&self) -> MetadataVersion;
222 fn table_stats(&self, table: &str) -> DistributedResult<TableStats>;
224}
225
226#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
232pub struct JoinKey {
233 pub left: String,
235 pub right: String,
237}
238
239#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
241pub struct SortKey {
242 pub column: String,
244 pub descending: bool,
246}
247
248#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
250pub struct AggregateExpr {
251 pub function: AggregateFunction,
253 pub column: Option<String>,
255}
256
257#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
259pub enum AggregateFunction {
260 Count,
262 Sum,
264 Min,
266 Max,
268 Avg,
270}
271
272impl AggregateFunction {
273 fn name(self) -> &'static str {
274 match self {
275 Self::Count => "count",
276 Self::Sum => "sum",
277 Self::Min => "min",
278 Self::Max => "max",
279 Self::Avg => "avg",
280 }
281 }
282}
283
284#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
290pub enum LogicalPlanLite {
291 Scan {
293 table: String,
295 predicate: Option<String>,
298 projection: Vec<String>,
300 },
301 Aggregate {
303 input: Box<LogicalPlanLite>,
305 group_by: Vec<String>,
307 aggregates: Vec<AggregateExpr>,
309 },
310 Join {
312 left: Box<LogicalPlanLite>,
314 right: Box<LogicalPlanLite>,
316 on: Vec<JoinKey>,
318 },
319 Sort {
321 input: Box<LogicalPlanLite>,
323 keys: Vec<SortKey>,
325 limit: Option<usize>,
327 },
328 Limit {
330 input: Box<LogicalPlanLite>,
332 limit: usize,
334 },
335}
336
337pub type FragmentId = u32;
343pub type ExchangeId = u32;
345
346#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
348pub enum FragmentAssignment {
349 Tablet(TabletId),
351 Coordinator,
353}
354
355#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
357pub enum BuildSide {
358 Left,
360 Right,
362}
363
364#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
366pub enum FragmentOperator {
367 TabletScan {
369 table: String,
371 predicate: Option<String>,
373 projection: Vec<String>,
375 },
376 RemoteExchangeSource {
378 exchange: ExchangeId,
380 },
381 RemoteExchangeSink {
383 exchange: ExchangeId,
385 },
386 PartialAggregate {
388 group_by: Vec<String>,
390 aggregates: Vec<AggregateExpr>,
392 },
393 FinalAggregate {
395 group_by: Vec<String>,
397 aggregates: Vec<AggregateExpr>,
399 },
400 DistributedHashJoin {
402 on: Vec<JoinKey>,
404 },
405 BroadcastJoin {
408 on: Vec<JoinKey>,
410 build_side: BuildSide,
412 },
413 RepartitionJoin {
415 on: Vec<JoinKey>,
417 },
418 MergeSort {
421 keys: Vec<SortKey>,
423 limit: Option<usize>,
425 },
426 DistributedTopK {
430 k: usize,
432 score: SortKey,
434 },
435 DistributedLimit {
437 limit: usize,
439 },
440}
441
442#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
444pub enum ExchangeKind {
445 HashRepartition {
448 keys: Vec<String>,
450 },
451 Broadcast,
453 Merge,
455}
456
457#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
459pub struct ExchangeDescriptor {
460 pub exchange_id: ExchangeId,
462 pub producer: FragmentId,
464 pub consumer: FragmentId,
466 pub kind: ExchangeKind,
468 pub schema_fingerprint: u64,
471}
472
473#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
475pub struct PlanFragment {
476 pub fragment_id: FragmentId,
478 pub assignment: FragmentAssignment,
480 pub operators: Vec<FragmentOperator>,
482 pub estimated_rows: u64,
484 pub estimated_bytes: u64,
486 pub max_spill_bytes: u64,
488}
489
490#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
493pub struct DistributedPlan {
494 pub query_id: QueryId,
496 pub metadata_version: MetadataVersion,
498 pub fragments: Vec<PlanFragment>,
500 pub exchanges: Vec<ExchangeDescriptor>,
502}
503
504impl DistributedPlan {
505 pub fn root_fragment_id(&self) -> Option<FragmentId> {
507 self.fragments
508 .iter()
509 .map(|fragment| fragment.fragment_id)
510 .find(|id| !self.exchanges.iter().any(|edge| edge.producer == *id))
511 }
512
513 pub fn fragment(&self, fragment_id: FragmentId) -> Option<&PlanFragment> {
515 self.fragments.get(fragment_id as usize)
516 }
517
518 pub fn exchanges_from(
520 &self,
521 producer: FragmentId,
522 ) -> impl Iterator<Item = &ExchangeDescriptor> {
523 self.exchanges
524 .iter()
525 .filter(move |edge| edge.producer == producer)
526 }
527
528 pub fn exchanges_into(&self, consumer: FragmentId) -> Vec<&ExchangeDescriptor> {
530 let mut edges: Vec<&ExchangeDescriptor> = self
531 .exchanges
532 .iter()
533 .filter(|edge| edge.consumer == consumer)
534 .collect();
535 edges.sort_by_key(|edge| edge.producer);
536 edges
537 }
538}
539
540#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
546pub struct PlanDescription {
547 pub query_id: QueryId,
549 pub root: LogicalPlanLite,
551 pub options: PlannerOptions,
553}
554
555#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
557pub struct PlannerOptions {
558 pub broadcast_threshold_bytes: u64,
560 pub max_spill_bytes_per_fragment: u64,
562}
563
564impl Default for PlannerOptions {
565 fn default() -> Self {
566 Self {
567 broadcast_threshold_bytes: DEFAULT_BROADCAST_THRESHOLD_BYTES,
568 max_spill_bytes_per_fragment: DEFAULT_MAX_SPILL_BYTES_PER_FRAGMENT,
569 }
570 }
571}
572
573pub fn distribute(
591 description: &PlanDescription,
592 locator: &dyn TabletLocator,
593 metadata: &dyn ClusterMetadata,
594) -> DistributedResult<DistributedPlan> {
595 validate_lite(&description.root)?;
596 let mut planner = Planner {
597 locator,
598 metadata,
599 options: description.options,
600 fragments: Vec::new(),
601 exchanges: Vec::new(),
602 };
603 let stage = planner.plan_node(&description.root)?;
604 let root = match stage.producers.as_slice() {
605 [only]
606 if planner.fragments[*only as usize].assignment == FragmentAssignment::Coordinator =>
607 {
608 *only
609 }
610 _ => {
611 let gather = planner.push_fragment(
612 FragmentAssignment::Coordinator,
613 Vec::new(),
614 stage.estimated_rows,
615 stage.estimated_bytes,
616 );
617 planner.wire(&stage.producers, &[gather], ExchangeKind::Merge);
618 gather
619 }
620 };
621 debug_assert_eq!(
622 planner
623 .fragments
624 .iter()
625 .filter(|fragment| {
626 !planner
627 .exchanges
628 .iter()
629 .any(|edge| edge.producer == fragment.fragment_id)
630 })
631 .count(),
632 1,
633 "exactly one root fragment"
634 );
635 let _ = root;
636 Ok(DistributedPlan {
637 query_id: description.query_id,
638 metadata_version: metadata.metadata_version(),
639 fragments: planner.fragments,
640 exchanges: planner.exchanges,
641 })
642}
643
644fn validate_lite(node: &LogicalPlanLite) -> DistributedResult<()> {
646 match node {
647 LogicalPlanLite::Scan { table, .. } => {
648 if table.is_empty() {
649 return Err(DistributedError::InvalidPlan(
650 "scan needs a table name".to_owned(),
651 ));
652 }
653 }
654 LogicalPlanLite::Aggregate {
655 input, aggregates, ..
656 } => {
657 for aggregate in aggregates {
658 if aggregate.function != AggregateFunction::Count && aggregate.column.is_none() {
659 return Err(DistributedError::InvalidPlan(format!(
660 "{} needs a column",
661 aggregate.function.name()
662 )));
663 }
664 }
665 validate_lite(input)?;
666 }
667 LogicalPlanLite::Join { left, right, on } => {
668 if on.is_empty() {
669 return Err(DistributedError::InvalidPlan(
670 "join needs at least one key".to_owned(),
671 ));
672 }
673 validate_lite(left)?;
674 validate_lite(right)?;
675 }
676 LogicalPlanLite::Sort { input, keys, .. } => {
677 if keys.is_empty() {
678 return Err(DistributedError::InvalidPlan(
679 "sort needs at least one key".to_owned(),
680 ));
681 }
682 validate_lite(input)?;
683 }
684 LogicalPlanLite::Limit { input, .. } => validate_lite(input)?,
685 }
686 Ok(())
687}
688
689struct Stage {
691 producers: Vec<FragmentId>,
693 estimated_rows: u64,
694 estimated_bytes: u64,
695 partitioning: Option<PartitionSpec>,
697 tablets: Vec<TabletId>,
699}
700
701struct Planner<'a> {
702 locator: &'a dyn TabletLocator,
703 metadata: &'a dyn ClusterMetadata,
704 options: PlannerOptions,
705 fragments: Vec<PlanFragment>,
706 exchanges: Vec<ExchangeDescriptor>,
707}
708
709impl Planner<'_> {
710 fn push_fragment(
711 &mut self,
712 assignment: FragmentAssignment,
713 operators: Vec<FragmentOperator>,
714 estimated_rows: u64,
715 estimated_bytes: u64,
716 ) -> FragmentId {
717 let fragment_id = self.fragments.len() as FragmentId;
718 self.fragments.push(PlanFragment {
719 fragment_id,
720 assignment,
721 operators,
722 estimated_rows,
723 estimated_bytes,
724 max_spill_bytes: self.options.max_spill_bytes_per_fragment,
725 });
726 fragment_id
727 }
728
729 fn wire(&mut self, producers: &[FragmentId], consumers: &[FragmentId], kind: ExchangeKind) {
733 for &producer in producers {
734 let schema_fingerprint = self.schema_fingerprint(producer);
735 for &consumer in consumers {
736 let exchange_id = self.exchanges.len() as ExchangeId;
737 self.exchanges.push(ExchangeDescriptor {
738 exchange_id,
739 producer,
740 consumer,
741 kind: kind.clone(),
742 schema_fingerprint,
743 });
744 self.fragments[producer as usize].operators.push(
745 FragmentOperator::RemoteExchangeSink {
746 exchange: exchange_id,
747 },
748 );
749 self.fragments[consumer as usize].operators.push(
750 FragmentOperator::RemoteExchangeSource {
751 exchange: exchange_id,
752 },
753 );
754 }
755 }
756 }
757
758 fn schema_fingerprint(&self, fragment_id: FragmentId) -> u64 {
760 let columns = fragment_output_columns(&self.fragments[fragment_id as usize]);
761 let mut bytes = Vec::new();
762 for column in &columns {
763 bytes.extend_from_slice(&(column.len() as u32).to_le_bytes());
764 bytes.extend_from_slice(column.as_bytes());
765 }
766 fnv1a64(&bytes)
767 }
768
769 fn metadata_stats(&self, table: &str) -> DistributedResult<TableStats> {
770 self.metadata.table_stats(table)
771 }
772
773 fn plan_node(&mut self, node: &LogicalPlanLite) -> DistributedResult<Stage> {
774 match node {
775 LogicalPlanLite::Scan {
776 table,
777 predicate,
778 projection,
779 } => self.plan_scan(table, predicate, projection),
780 LogicalPlanLite::Aggregate {
781 input,
782 group_by,
783 aggregates,
784 } => self.plan_aggregate(input, group_by, aggregates),
785 LogicalPlanLite::Join { left, right, on } => self.plan_join(left, right, on),
786 LogicalPlanLite::Sort { input, keys, limit } => self.plan_sort(input, keys, *limit),
787 LogicalPlanLite::Limit { input, limit } => self.plan_limit(input, *limit),
788 }
789 }
790
791 fn plan_scan(
792 &mut self,
793 table: &str,
794 predicate: &Option<String>,
795 projection: &[String],
796 ) -> DistributedResult<Stage> {
797 let tablets = self.locator.tablets_for_table(table)?;
798 if tablets.is_empty() {
799 return Err(DistributedError::EmptyLayout {
800 table: table.to_owned(),
801 });
802 }
803 let spec = self.locator.partitioning(table)?;
804 let stats = self.metadata_stats(table)?;
805 let count = tablets.len() as u64;
806 let per_rows = stats.row_count.div_ceil(count);
807 let per_bytes = stats.total_bytes.div_ceil(count);
808 let mut producers = Vec::with_capacity(tablets.len());
809 for tablet in &tablets {
810 producers.push(self.push_fragment(
811 FragmentAssignment::Tablet(*tablet),
812 vec![FragmentOperator::TabletScan {
813 table: table.to_owned(),
814 predicate: predicate.clone(),
815 projection: projection.to_vec(),
816 }],
817 per_rows,
818 per_bytes,
819 ));
820 }
821 Ok(Stage {
822 producers,
823 estimated_rows: stats.row_count,
824 estimated_bytes: stats.total_bytes,
825 partitioning: Some(spec),
826 tablets,
827 })
828 }
829
830 fn plan_aggregate(
831 &mut self,
832 input: &LogicalPlanLite,
833 group_by: &[String],
834 aggregates: &[AggregateExpr],
835 ) -> DistributedResult<Stage> {
836 let child = self.plan_node(input)?;
837 let estimated_rows = if group_by.is_empty() {
838 1
839 } else {
840 (child.estimated_rows / 2).max(1)
841 };
842 let per_row = child
843 .estimated_bytes
844 .checked_div(child.estimated_rows.max(1))
845 .unwrap_or(0)
846 .max(1);
847 let estimated_bytes = estimated_rows.saturating_mul(per_row);
848 if let [only] = child.producers.as_slice() {
849 if self.fragments[*only as usize].assignment == FragmentAssignment::Coordinator {
850 self.fragments[*only as usize].operators.extend([
852 FragmentOperator::PartialAggregate {
853 group_by: group_by.to_vec(),
854 aggregates: aggregates.to_vec(),
855 },
856 FragmentOperator::FinalAggregate {
857 group_by: group_by.to_vec(),
858 aggregates: aggregates.to_vec(),
859 },
860 ]);
861 return Ok(Stage {
862 estimated_rows,
863 estimated_bytes,
864 ..child
865 });
866 }
867 }
868 for &producer in &child.producers {
869 self.fragments[producer as usize]
870 .operators
871 .push(FragmentOperator::PartialAggregate {
872 group_by: group_by.to_vec(),
873 aggregates: aggregates.to_vec(),
874 });
875 }
876 let consumer = self.push_fragment(
877 FragmentAssignment::Coordinator,
878 Vec::new(),
879 estimated_rows,
880 estimated_bytes,
881 );
882 let kind = if group_by.is_empty() {
883 ExchangeKind::Merge
884 } else {
885 ExchangeKind::HashRepartition {
886 keys: group_by.to_vec(),
887 }
888 };
889 self.wire(&child.producers, &[consumer], kind);
890 self.fragments[consumer as usize]
891 .operators
892 .push(FragmentOperator::FinalAggregate {
893 group_by: group_by.to_vec(),
894 aggregates: aggregates.to_vec(),
895 });
896 Ok(Stage {
897 producers: vec![consumer],
898 estimated_rows,
899 estimated_bytes,
900 partitioning: None,
901 tablets: Vec::new(),
902 })
903 }
904
905 fn plan_join(
906 &mut self,
907 left: &LogicalPlanLite,
908 right: &LogicalPlanLite,
909 on: &[JoinKey],
910 ) -> DistributedResult<Stage> {
911 if let (
913 LogicalPlanLite::Scan {
914 table: left_table, ..
915 },
916 LogicalPlanLite::Scan {
917 table: right_table, ..
918 },
919 ) = (left, right)
920 {
921 let left_tablets = self.locator.tablets_for_table(left_table)?;
922 if left_tablets.is_empty() {
923 return Err(DistributedError::EmptyLayout {
924 table: left_table.clone(),
925 });
926 }
927 let right_tablets = self.locator.tablets_for_table(right_table)?;
928 if right_tablets.is_empty() {
929 return Err(DistributedError::EmptyLayout {
930 table: right_table.clone(),
931 });
932 }
933 let left_spec = self.locator.partitioning(left_table)?;
934 let right_spec = self.locator.partitioning(right_table)?;
935 if left_spec.colocated_with(&right_spec) && left_tablets == right_tablets {
936 return self.plan_colocated_join(left, right, on, &left_tablets, &left_spec);
937 }
938 }
939 let left_stage = self.plan_node(left)?;
940 let right_stage = self.plan_node(right)?;
941 if left_stage.estimated_bytes.min(right_stage.estimated_bytes)
942 <= self.options.broadcast_threshold_bytes
943 {
944 self.plan_broadcast_join(left_stage, right_stage, on)
945 } else {
946 self.plan_repartition_join(left_stage, right_stage, on)
947 }
948 }
949
950 fn plan_colocated_join(
953 &mut self,
954 left: &LogicalPlanLite,
955 right: &LogicalPlanLite,
956 on: &[JoinKey],
957 tablets: &[TabletId],
958 spec: &PartitionSpec,
959 ) -> DistributedResult<Stage> {
960 let (
961 LogicalPlanLite::Scan {
962 table: left_table,
963 predicate: left_predicate,
964 projection: left_projection,
965 },
966 LogicalPlanLite::Scan {
967 table: right_table,
968 predicate: right_predicate,
969 projection: right_projection,
970 },
971 ) = (left, right)
972 else {
973 return Err(DistributedError::InvalidPlan(
974 "colocated join needs scan inputs".to_owned(),
975 ));
976 };
977 let left_stats = self.metadata_stats(left_table)?;
978 let right_stats = self.metadata_stats(right_table)?;
979 let estimated_rows = left_stats.row_count.max(right_stats.row_count);
980 let estimated_bytes = left_stats.total_bytes.max(right_stats.total_bytes);
981 let count = tablets.len() as u64;
982 let mut producers = Vec::with_capacity(tablets.len());
983 for tablet in tablets {
984 producers.push(self.push_fragment(
985 FragmentAssignment::Tablet(*tablet),
986 vec![
987 FragmentOperator::TabletScan {
988 table: left_table.clone(),
989 predicate: left_predicate.clone(),
990 projection: left_projection.clone(),
991 },
992 FragmentOperator::TabletScan {
993 table: right_table.clone(),
994 predicate: right_predicate.clone(),
995 projection: right_projection.clone(),
996 },
997 FragmentOperator::DistributedHashJoin { on: on.to_vec() },
998 ],
999 estimated_rows.div_ceil(count),
1000 estimated_bytes.div_ceil(count),
1001 ));
1002 }
1003 Ok(Stage {
1004 producers,
1005 estimated_rows,
1006 estimated_bytes,
1007 partitioning: Some(spec.clone()),
1008 tablets: tablets.to_vec(),
1009 })
1010 }
1011
1012 fn plan_broadcast_join(
1015 &mut self,
1016 left_stage: Stage,
1017 right_stage: Stage,
1018 on: &[JoinKey],
1019 ) -> DistributedResult<Stage> {
1020 let (big, small, build_side) = if right_stage.estimated_bytes <= left_stage.estimated_bytes
1021 {
1022 (left_stage, right_stage, BuildSide::Right)
1023 } else {
1024 (right_stage, left_stage, BuildSide::Left)
1025 };
1026 self.wire(&small.producers, &big.producers, ExchangeKind::Broadcast);
1027 for &producer in &big.producers {
1028 self.fragments[producer as usize]
1029 .operators
1030 .push(FragmentOperator::BroadcastJoin {
1031 on: on.to_vec(),
1032 build_side,
1033 });
1034 }
1035 let estimated_rows = big.estimated_rows;
1036 let estimated_bytes = big.estimated_bytes.saturating_add(small.estimated_bytes);
1037 Ok(Stage {
1038 producers: big.producers,
1039 estimated_rows,
1040 estimated_bytes,
1041 partitioning: big.partitioning,
1042 tablets: big.tablets,
1043 })
1044 }
1045
1046 fn plan_repartition_join(
1049 &mut self,
1050 left_stage: Stage,
1051 right_stage: Stage,
1052 on: &[JoinKey],
1053 ) -> DistributedResult<Stage> {
1054 let join_tablets = if left_stage.tablets.len() >= right_stage.tablets.len() {
1055 left_stage.tablets.clone()
1056 } else {
1057 right_stage.tablets.clone()
1058 };
1059 if join_tablets.is_empty() {
1060 return Err(DistributedError::InvalidPlan(
1061 "repartition join needs at least one tablet-backed input".to_owned(),
1062 ));
1063 }
1064 let width = left_stage.producers.len().max(right_stage.producers.len());
1065 let estimated_rows = left_stage.estimated_rows.max(right_stage.estimated_rows);
1066 let estimated_bytes = left_stage
1067 .estimated_bytes
1068 .saturating_add(right_stage.estimated_bytes);
1069 let mut consumers = Vec::with_capacity(width);
1070 for index in 0..width {
1071 consumers.push(self.push_fragment(
1072 FragmentAssignment::Tablet(join_tablets[index % join_tablets.len()]),
1073 Vec::new(),
1074 estimated_rows.div_ceil(width as u64),
1075 estimated_bytes.div_ceil(width as u64),
1076 ));
1077 }
1078 let left_keys: Vec<String> = on.iter().map(|key| key.left.clone()).collect();
1079 let right_keys: Vec<String> = on.iter().map(|key| key.right.clone()).collect();
1080 self.wire(
1081 &left_stage.producers,
1082 &consumers,
1083 ExchangeKind::HashRepartition { keys: left_keys },
1084 );
1085 self.wire(
1086 &right_stage.producers,
1087 &consumers,
1088 ExchangeKind::HashRepartition { keys: right_keys },
1089 );
1090 for &consumer in &consumers {
1091 self.fragments[consumer as usize]
1092 .operators
1093 .push(FragmentOperator::RepartitionJoin { on: on.to_vec() });
1094 }
1095 Ok(Stage {
1096 producers: consumers,
1097 estimated_rows,
1098 estimated_bytes,
1099 partitioning: None,
1100 tablets: join_tablets,
1101 })
1102 }
1103
1104 fn plan_sort(
1105 &mut self,
1106 input: &LogicalPlanLite,
1107 keys: &[SortKey],
1108 limit: Option<usize>,
1109 ) -> DistributedResult<Stage> {
1110 let child = self.plan_node(input)?;
1111 let top_k = match (limit, keys) {
1114 (Some(k), [score]) if score.descending => Some((k, score.clone())),
1115 _ => None,
1116 };
1117 let estimated_rows = limit.map_or(child.estimated_rows, |limit| {
1118 child.estimated_rows.min(limit as u64)
1119 });
1120 let estimated_bytes =
1121 scaled_bytes(child.estimated_bytes, child.estimated_rows, estimated_rows);
1122 let local_op = match &top_k {
1123 Some((k, score)) => FragmentOperator::DistributedTopK {
1124 k: *k,
1125 score: score.clone(),
1126 },
1127 None => FragmentOperator::MergeSort {
1128 keys: keys.to_vec(),
1129 limit,
1130 },
1131 };
1132 if let [only] = child.producers.as_slice() {
1133 if self.fragments[*only as usize].assignment == FragmentAssignment::Coordinator {
1134 self.fragments[*only as usize].operators.push(local_op);
1135 return Ok(Stage {
1136 estimated_rows,
1137 estimated_bytes,
1138 ..child
1139 });
1140 }
1141 }
1142 for &producer in &child.producers {
1143 self.fragments[producer as usize]
1144 .operators
1145 .push(local_op.clone());
1146 }
1147 let consumer = self.push_fragment(
1148 FragmentAssignment::Coordinator,
1149 Vec::new(),
1150 estimated_rows,
1151 estimated_bytes,
1152 );
1153 self.wire(&child.producers, &[consumer], ExchangeKind::Merge);
1154 let root_op = match &top_k {
1155 Some((k, score)) => FragmentOperator::DistributedTopK {
1156 k: *k,
1157 score: score.clone(),
1158 },
1159 None => FragmentOperator::MergeSort {
1160 keys: keys.to_vec(),
1161 limit,
1162 },
1163 };
1164 self.fragments[consumer as usize].operators.push(root_op);
1165 Ok(Stage {
1166 producers: vec![consumer],
1167 estimated_rows,
1168 estimated_bytes,
1169 partitioning: None,
1170 tablets: Vec::new(),
1171 })
1172 }
1173
1174 fn plan_limit(&mut self, input: &LogicalPlanLite, limit: usize) -> DistributedResult<Stage> {
1175 let child = self.plan_node(input)?;
1176 let estimated_rows = child.estimated_rows.min(limit as u64);
1177 let estimated_bytes =
1178 scaled_bytes(child.estimated_bytes, child.estimated_rows, estimated_rows);
1179 if let [only] = child.producers.as_slice() {
1180 if self.fragments[*only as usize].assignment == FragmentAssignment::Coordinator {
1181 self.fragments[*only as usize]
1182 .operators
1183 .push(FragmentOperator::DistributedLimit { limit });
1184 return Ok(Stage {
1185 estimated_rows,
1186 estimated_bytes,
1187 ..child
1188 });
1189 }
1190 }
1191 for &producer in &child.producers {
1192 self.fragments[producer as usize]
1193 .operators
1194 .push(FragmentOperator::DistributedLimit { limit });
1195 }
1196 let consumer = self.push_fragment(
1197 FragmentAssignment::Coordinator,
1198 Vec::new(),
1199 estimated_rows,
1200 estimated_bytes,
1201 );
1202 self.wire(&child.producers, &[consumer], ExchangeKind::Merge);
1203 self.fragments[consumer as usize]
1204 .operators
1205 .push(FragmentOperator::DistributedLimit { limit });
1206 Ok(Stage {
1207 producers: vec![consumer],
1208 estimated_rows,
1209 estimated_bytes,
1210 partitioning: None,
1211 tablets: Vec::new(),
1212 })
1213 }
1214}
1215
1216fn scaled_bytes(bytes: u64, rows: u64, new_rows: u64) -> u64 {
1218 if rows == 0 {
1219 return 0;
1220 }
1221 bytes
1222 .saturating_mul(new_rows)
1223 .checked_div(rows)
1224 .unwrap_or(bytes)
1225}
1226
1227fn fragment_output_columns(fragment: &PlanFragment) -> Vec<String> {
1229 let mut columns = Vec::new();
1230 for operator in &fragment.operators {
1231 match operator {
1232 FragmentOperator::TabletScan {
1233 table, projection, ..
1234 } => {
1235 if projection.is_empty() {
1236 columns = vec![format!("{table}.*")];
1237 } else {
1238 columns = projection
1239 .iter()
1240 .map(|column| format!("{table}.{column}"))
1241 .collect();
1242 }
1243 }
1244 FragmentOperator::PartialAggregate {
1245 group_by,
1246 aggregates,
1247 } => {
1248 columns = group_by.to_vec();
1249 columns.extend(partial_column_names(aggregates));
1250 }
1251 FragmentOperator::FinalAggregate {
1252 group_by,
1253 aggregates,
1254 } => {
1255 columns = group_by.to_vec();
1256 columns.extend(aggregates.iter().map(aggregate_output_name));
1257 }
1258 FragmentOperator::DistributedHashJoin { .. }
1259 | FragmentOperator::BroadcastJoin { .. }
1260 | FragmentOperator::RepartitionJoin { .. } => {
1261 columns = vec!["*join*".to_owned()];
1262 }
1263 FragmentOperator::RemoteExchangeSource { .. }
1264 | FragmentOperator::RemoteExchangeSink { .. }
1265 | FragmentOperator::MergeSort { .. }
1266 | FragmentOperator::DistributedTopK { .. }
1267 | FragmentOperator::DistributedLimit { .. } => {}
1268 }
1269 }
1270 columns
1271}
1272
1273fn partial_column_names(aggregates: &[AggregateExpr]) -> Vec<String> {
1276 let mut names = Vec::new();
1277 for (index, aggregate) in aggregates.iter().enumerate() {
1278 if aggregate.function == AggregateFunction::Avg {
1279 names.push(format!("__partial_{index}_sum"));
1280 names.push(format!("__partial_{index}_count"));
1281 } else {
1282 names.push(format!("__partial_{index}"));
1283 }
1284 }
1285 names
1286}
1287
1288fn aggregate_output_name(aggregate: &AggregateExpr) -> String {
1290 format!(
1291 "{}_{}",
1292 aggregate.function.name(),
1293 aggregate.column.as_deref().unwrap_or("star")
1294 )
1295}
1296
1297#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1305pub struct TopKCandidate {
1306 pub score: u64,
1308 pub tablet: TabletId,
1310 pub row_id: RowId,
1312}
1313
1314pub fn topk_cmp(a: &TopKCandidate, b: &TopKCandidate) -> Ordering {
1318 b.score
1319 .cmp(&a.score)
1320 .then_with(|| a.tablet.cmp(&b.tablet))
1321 .then_with(|| a.row_id.cmp(&b.row_id))
1322}
1323
1324#[derive(Clone, Debug)]
1327pub struct TabletTopK {
1328 pub tablet: TabletId,
1330 pub rows: Vec<TopKCandidate>,
1332 pub unseen_bound: Option<u64>,
1335}
1336
1337#[derive(Clone, Debug, PartialEq, Eq)]
1339pub struct TopKMerge {
1340 pub winners: Vec<TopKCandidate>,
1342 pub refill: Vec<TabletId>,
1345}
1346
1347pub fn merge_top_k(shards: &[TabletTopK], k: usize) -> TopKMerge {
1370 if k == 0 {
1371 return TopKMerge {
1372 winners: Vec::new(),
1373 refill: Vec::new(),
1374 };
1375 }
1376 let mut received: Vec<TopKCandidate> = shards
1377 .iter()
1378 .flat_map(|shard| shard.rows.iter().copied())
1379 .collect();
1380 received.sort_by(topk_cmp);
1381 received.truncate(k);
1382 let mut refill = Vec::new();
1383 if received.len() < k {
1384 for shard in shards {
1385 if shard.unseen_bound.is_some() {
1386 refill.push(shard.tablet);
1387 }
1388 }
1389 } else {
1390 let threshold = received[k - 1];
1391 for shard in shards {
1392 let Some(bound) = shard.unseen_bound else {
1393 continue;
1394 };
1395 let optimistic = TopKCandidate {
1396 score: bound,
1397 tablet: shard.tablet,
1398 row_id: RowId::MIN,
1399 };
1400 if topk_cmp(&optimistic, &threshold) != Ordering::Greater {
1401 refill.push(shard.tablet);
1402 }
1403 }
1404 }
1405 refill.sort();
1406 refill.dedup();
1407 TopKMerge {
1408 winners: received,
1409 refill,
1410 }
1411}
1412
1413pub fn exact_top_k(
1425 k: usize,
1426 initial: Vec<TabletTopK>,
1427 mut refill_batch: impl FnMut(TabletId) -> TabletTopK,
1428) -> DistributedResult<Vec<TopKCandidate>> {
1429 let mut shards: BTreeMap<TabletId, TabletTopK> = initial
1430 .into_iter()
1431 .map(|shard| (shard.tablet, shard))
1432 .collect();
1433 loop {
1434 let ordered: Vec<TabletTopK> = shards.values().cloned().collect();
1435 let merge = merge_top_k(&ordered, k);
1436 if merge.refill.is_empty() {
1437 return Ok(merge.winners);
1438 }
1439 for tablet in merge.refill {
1440 let batch = refill_batch(tablet);
1441 let entry = shards.get_mut(&tablet).ok_or_else(|| {
1442 DistributedError::InvalidPlan(format!(
1443 "top-k refill requested for unknown tablet {tablet}"
1444 ))
1445 })?;
1446 if batch.rows.is_empty() && batch.unseen_bound == entry.unseen_bound {
1447 return Err(DistributedError::InvalidPlan(format!(
1448 "top-k refill for tablet {tablet} made no progress"
1449 )));
1450 }
1451 entry.rows.extend(batch.rows);
1452 entry.unseen_bound = batch.unseen_bound;
1453 }
1454 }
1455}
1456
1457#[derive(Debug, Clone)]
1465pub struct FragmentControl {
1466 pub control: ExecutionControl,
1469 pub max_spill_bytes: u64,
1471 pub authorization_context: Arc<[u8]>,
1474}
1475
1476impl FragmentControl {
1477 pub fn begin_spill(
1481 &self,
1482 manager: &mongreldb_core::SpillManager,
1483 query_id: mongreldb_types::ids::QueryId,
1484 ) -> Result<mongreldb_core::SpillSession, mongreldb_core::SpillError> {
1485 manager.begin_query(query_id, self.max_spill_bytes)
1486 }
1487}
1488
1489#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1493pub enum ScoreBound {
1494 Unknown,
1497 AtMost(u64),
1499 Exhausted,
1501}
1502
1503#[derive(Debug, Clone)]
1505pub struct BatchFrame {
1506 pub batch: RecordBatch,
1508 pub score_bound: ScoreBound,
1510}
1511
1512impl BatchFrame {
1513 pub fn data(batch: RecordBatch) -> Self {
1515 Self {
1516 batch,
1517 score_bound: ScoreBound::Unknown,
1518 }
1519 }
1520}
1521
1522pub type FragmentStream = BoxStream<'static, DistributedResult<BatchFrame>>;
1524
1525#[derive(Debug)]
1528pub struct TopKRefill {
1529 pub rows: Vec<TopKCandidate>,
1532 pub payload: RecordBatch,
1534 pub unseen_bound: Option<u64>,
1536}
1537
1538#[async_trait::async_trait]
1542pub trait FragmentExecutor: Send + Sync {
1543 async fn execute(
1546 &self,
1547 fragment: &PlanFragment,
1548 inputs: Vec<FragmentStream>,
1549 control: FragmentControl,
1550 ) -> DistributedResult<FragmentStream>;
1551
1552 fn refill_top_k(
1556 &self,
1557 fragment: &PlanFragment,
1558 offset: usize,
1559 limit: usize,
1560 control: FragmentControl,
1561 ) -> DistributedResult<TopKRefill> {
1562 let _ = (fragment, offset, limit, control);
1563 Err(DistributedError::Unsupported(
1564 "top-k refill is not implemented by this executor".to_owned(),
1565 ))
1566 }
1567}
1568
1569#[async_trait::async_trait]
1574pub trait FragmentTransport: Send + Sync {
1575 async fn execute_fragment(
1578 &self,
1579 query_id: QueryId,
1580 fragment: &PlanFragment,
1581 inputs: Vec<FragmentStream>,
1582 control: FragmentControl,
1583 ) -> DistributedResult<FragmentStream>;
1584
1585 fn cancel_fragment(&self, query_id: QueryId, fragment_id: FragmentId) -> DistributedResult<()>;
1587
1588 async fn refill_top_k(
1591 &self,
1592 query_id: QueryId,
1593 fragment: &PlanFragment,
1594 offset: usize,
1595 limit: usize,
1596 control: FragmentControl,
1597 ) -> DistributedResult<TopKRefill> {
1598 let _ = (query_id, fragment, offset, limit, control);
1599 Err(DistributedError::Unsupported(
1600 "top-k refill over this transport is not bound in this wave".to_owned(),
1601 ))
1602 }
1603}
1604
1605pub struct InMemoryTransport {
1609 default_executor: Arc<dyn FragmentExecutor>,
1610 executors: parking_lot::RwLock<HashMap<TabletId, Arc<dyn FragmentExecutor>>>,
1611 started: Mutex<Vec<FragmentId>>,
1612 cancelled: Mutex<Vec<FragmentId>>,
1613 controls: Mutex<HashMap<FragmentId, ExecutionControl>>,
1614 refills: Mutex<Vec<(FragmentId, usize, usize)>>,
1615}
1616
1617impl InMemoryTransport {
1618 pub fn new(default_executor: Arc<dyn FragmentExecutor>) -> Self {
1620 Self {
1621 default_executor,
1622 executors: parking_lot::RwLock::new(HashMap::new()),
1623 started: Mutex::new(Vec::new()),
1624 cancelled: Mutex::new(Vec::new()),
1625 controls: Mutex::new(HashMap::new()),
1626 refills: Mutex::new(Vec::new()),
1627 }
1628 }
1629
1630 pub fn with_executor(self, tablet: TabletId, executor: Arc<dyn FragmentExecutor>) -> Self {
1632 self.executors.write().insert(tablet, executor);
1633 self
1634 }
1635
1636 fn executor_for(&self, assignment: &FragmentAssignment) -> Arc<dyn FragmentExecutor> {
1637 match assignment {
1638 FragmentAssignment::Tablet(tablet) => self
1639 .executors
1640 .read()
1641 .get(tablet)
1642 .cloned()
1643 .unwrap_or_else(|| Arc::clone(&self.default_executor)),
1644 FragmentAssignment::Coordinator => Arc::clone(&self.default_executor),
1645 }
1646 }
1647
1648 pub fn started_fragments(&self) -> Vec<FragmentId> {
1650 self.started.lock().clone()
1651 }
1652
1653 pub fn cancelled_fragments(&self) -> Vec<FragmentId> {
1655 self.cancelled.lock().clone()
1656 }
1657
1658 pub fn refill_log(&self) -> Vec<(FragmentId, usize, usize)> {
1660 self.refills.lock().clone()
1661 }
1662
1663 pub fn control_for(&self, fragment_id: FragmentId) -> Option<ExecutionControl> {
1665 self.controls.lock().get(&fragment_id).cloned()
1666 }
1667}
1668
1669#[async_trait::async_trait]
1670impl FragmentTransport for InMemoryTransport {
1671 async fn execute_fragment(
1672 &self,
1673 _query_id: QueryId,
1674 fragment: &PlanFragment,
1675 inputs: Vec<FragmentStream>,
1676 control: FragmentControl,
1677 ) -> DistributedResult<FragmentStream> {
1678 self.started.lock().push(fragment.fragment_id);
1679 self.controls
1680 .lock()
1681 .insert(fragment.fragment_id, control.control.clone());
1682 self.executor_for(&fragment.assignment)
1683 .execute(fragment, inputs, control)
1684 .await
1685 }
1686
1687 fn cancel_fragment(
1688 &self,
1689 _query_id: QueryId,
1690 fragment_id: FragmentId,
1691 ) -> DistributedResult<()> {
1692 self.cancelled.lock().push(fragment_id);
1693 if let Some(control) = self.controls.lock().get(&fragment_id) {
1694 control.cancel(CancellationReason::ClientRequest);
1695 }
1696 Ok(())
1697 }
1698
1699 async fn refill_top_k(
1700 &self,
1701 _query_id: QueryId,
1702 fragment: &PlanFragment,
1703 offset: usize,
1704 limit: usize,
1705 control: FragmentControl,
1706 ) -> DistributedResult<TopKRefill> {
1707 self.refills
1708 .lock()
1709 .push((fragment.fragment_id, offset, limit));
1710 self.executor_for(&fragment.assignment)
1711 .refill_top_k(fragment, offset, limit, control)
1712 }
1713}
1714
1715pub const REMOTE_FRAGMENT_PROTOCOL_VERSION: u16 = 1;
1724pub const REMOTE_FRAGMENT_SERVICE_ID: u32 = 1;
1727
1728pub const DEFAULT_REMOTE_FRAGMENT_MESSAGE_BYTES: usize = 16 * 1024 * 1024;
1730
1731pub const DEFAULT_REMOTE_FRAGMENT_EXECUTIONS: usize = 1_024;
1733
1734#[derive(Debug, Clone, Serialize, Deserialize)]
1735struct RemoteFragmentEnvelope {
1736 version: u16,
1737 request: RemoteFragmentRequest,
1738}
1739
1740#[derive(Debug, Clone, Serialize, Deserialize)]
1741enum RemoteFragmentRequest {
1742 Start {
1743 query_id: QueryId,
1744 fragment: PlanFragment,
1745 inputs: Vec<Vec<RemoteBatchFrame>>,
1746 max_spill_bytes: u64,
1747 authorization_context: Vec<u8>,
1748 deadline_ms: Option<u64>,
1749 },
1750 Pull {
1751 query_id: QueryId,
1752 fragment_id: FragmentId,
1753 },
1754 Cancel {
1755 query_id: QueryId,
1756 fragment_id: FragmentId,
1757 },
1758 RefillTopK {
1759 query_id: QueryId,
1760 fragment: PlanFragment,
1761 offset: usize,
1762 limit: usize,
1763 authorization_context: Vec<u8>,
1764 deadline_ms: Option<u64>,
1765 },
1766}
1767
1768#[derive(Debug, Clone, Serialize, Deserialize)]
1769struct RemoteFragmentResponseEnvelope {
1770 version: u16,
1771 response: RemoteFragmentResponse,
1772}
1773
1774#[derive(Debug, Clone, Serialize, Deserialize)]
1775enum RemoteFragmentResponse {
1776 Started,
1777 Frame(Option<RemoteBatchFrame>),
1778 Cancelled,
1779 TopKRefill {
1780 rows: Vec<TopKCandidate>,
1781 payload: Vec<u8>,
1782 unseen_bound: Option<u64>,
1783 },
1784 Error(String),
1785}
1786
1787#[derive(Debug, Clone, Serialize, Deserialize)]
1788struct RemoteBatchFrame {
1789 ipc: Vec<u8>,
1790 score_bound: ScoreBound,
1791}
1792
1793type RemoteExecutionKey = (QueryId, FragmentId);
1794
1795struct RemoteExecution {
1796 stream: tokio::sync::Mutex<Option<FragmentStream>>,
1797 control: ExecutionControl,
1798}
1799
1800pub struct RemoteFragmentEndpoint {
1807 executor: Arc<dyn FragmentExecutor>,
1808 executions: parking_lot::Mutex<HashMap<RemoteExecutionKey, Arc<RemoteExecution>>>,
1809 max_executions: usize,
1810 max_message_bytes: usize,
1811}
1812
1813impl RemoteFragmentEndpoint {
1814 pub fn new(executor: Arc<dyn FragmentExecutor>) -> Self {
1816 Self::with_limits(
1817 executor,
1818 DEFAULT_REMOTE_FRAGMENT_EXECUTIONS,
1819 DEFAULT_REMOTE_FRAGMENT_MESSAGE_BYTES,
1820 )
1821 }
1822
1823 pub fn with_limits(
1825 executor: Arc<dyn FragmentExecutor>,
1826 max_executions: usize,
1827 max_message_bytes: usize,
1828 ) -> Self {
1829 Self {
1830 executor,
1831 executions: parking_lot::Mutex::new(HashMap::new()),
1832 max_executions: max_executions.max(1),
1833 max_message_bytes: max_message_bytes.max(1),
1834 }
1835 }
1836
1837 pub fn active_executions(&self) -> usize {
1839 self.executions.lock().len()
1840 }
1841
1842 pub async fn handle(&self, bytes: &[u8]) -> DistributedResult<Vec<u8>> {
1844 if bytes.len() > self.max_message_bytes {
1845 return Err(DistributedError::RemoteProtocol(format!(
1846 "fragment request is {} bytes; limit is {}",
1847 bytes.len(),
1848 self.max_message_bytes
1849 )));
1850 }
1851 let envelope: RemoteFragmentEnvelope = decode_remote_wire(bytes, self.max_message_bytes)?;
1852 if envelope.version != REMOTE_FRAGMENT_PROTOCOL_VERSION {
1853 return Err(DistributedError::RemoteProtocol(format!(
1854 "unsupported fragment protocol version {}; supported version is {}",
1855 envelope.version, REMOTE_FRAGMENT_PROTOCOL_VERSION
1856 )));
1857 }
1858 let response = match self.handle_request(envelope.request).await {
1859 Ok(response) => response,
1860 Err(error) => RemoteFragmentResponse::Error(error.to_string()),
1861 };
1862 encode_remote_wire(
1863 &RemoteFragmentResponseEnvelope {
1864 version: REMOTE_FRAGMENT_PROTOCOL_VERSION,
1865 response,
1866 },
1867 self.max_message_bytes,
1868 )
1869 }
1870
1871 async fn handle_request(
1872 &self,
1873 request: RemoteFragmentRequest,
1874 ) -> DistributedResult<RemoteFragmentResponse> {
1875 match request {
1876 RemoteFragmentRequest::Start {
1877 query_id,
1878 fragment,
1879 inputs,
1880 max_spill_bytes,
1881 authorization_context,
1882 deadline_ms,
1883 } => {
1884 if authorization_context.len() > MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES {
1885 return Err(DistributedError::RemoteProtocol(format!(
1886 "fragment authorization context is {} bytes; limit is {}",
1887 authorization_context.len(),
1888 MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES
1889 )));
1890 }
1891 let key = (query_id, fragment.fragment_id);
1892 {
1893 let executions = self.executions.lock();
1894 if executions.contains_key(&key) {
1895 return Err(DistributedError::RemoteProtocol(format!(
1896 "fragment {} for query {query_id} is already running",
1897 fragment.fragment_id
1898 )));
1899 }
1900 if executions.len() >= self.max_executions {
1901 return Err(DistributedError::Reservation {
1902 fragment_id: fragment.fragment_id,
1903 reason: format!(
1904 "worker holds {} remote fragments; limit is {}",
1905 executions.len(),
1906 self.max_executions
1907 ),
1908 });
1909 }
1910 }
1911 let inputs = inputs
1912 .into_iter()
1913 .map(|frames| {
1914 frames
1915 .into_iter()
1916 .map(|frame| decode_remote_frame(frame, self.max_message_bytes))
1917 .collect::<DistributedResult<Vec<_>>>()
1918 .map(|frames| {
1919 Box::pin(stream::iter(frames.into_iter().map(Ok))) as FragmentStream
1920 })
1921 })
1922 .collect::<DistributedResult<Vec<_>>>()?;
1923 let control = deadline_ms.map_or_else(
1924 || ExecutionControl::new(None),
1925 |milliseconds| {
1926 ExecutionControl::with_timeout(Duration::from_millis(milliseconds))
1927 },
1928 );
1929 let execution = Arc::new(RemoteExecution {
1930 stream: tokio::sync::Mutex::new(None),
1931 control: control.clone(),
1932 });
1933 {
1934 let mut executions = self.executions.lock();
1935 if executions.contains_key(&key) {
1936 return Err(DistributedError::RemoteProtocol(format!(
1937 "fragment {} for query {query_id} raced another start",
1938 fragment.fragment_id
1939 )));
1940 }
1941 if executions.len() >= self.max_executions {
1942 return Err(DistributedError::Reservation {
1943 fragment_id: fragment.fragment_id,
1944 reason: "remote fragment limit reached during start".to_owned(),
1945 });
1946 }
1947 executions.insert(key, Arc::clone(&execution));
1948 }
1949 let stream = match self
1950 .executor
1951 .execute(
1952 &fragment,
1953 inputs,
1954 FragmentControl {
1955 control,
1956 max_spill_bytes,
1957 authorization_context: authorization_context.into(),
1958 },
1959 )
1960 .await
1961 {
1962 Ok(stream) => stream,
1963 Err(error) => {
1964 self.executions.lock().remove(&key);
1965 return Err(error);
1966 }
1967 };
1968 if self.executions.lock().get(&key).is_none() {
1969 return Err(DistributedError::Cancelled(
1970 CancellationReason::ClientRequest,
1971 ));
1972 }
1973 if let Err(error) = checkpoint(&execution.control) {
1974 self.executions.lock().remove(&key);
1975 return Err(error);
1976 }
1977 *execution.stream.lock().await = Some(stream);
1978 Ok(RemoteFragmentResponse::Started)
1979 }
1980 RemoteFragmentRequest::Pull {
1981 query_id,
1982 fragment_id,
1983 } => {
1984 let key = (query_id, fragment_id);
1985 let execution = self.executions.lock().get(&key).cloned().ok_or_else(|| {
1986 DistributedError::RemoteProtocol(format!(
1987 "fragment {fragment_id} for query {query_id} is not running"
1988 ))
1989 })?;
1990 let next = {
1991 checkpoint(&execution.control)?;
1992 let mut stream = execution.stream.lock().await;
1993 let stream = stream.as_mut().ok_or_else(|| {
1994 DistributedError::RemoteProtocol(format!(
1995 "fragment {fragment_id} for query {query_id} is not ready"
1996 ))
1997 })?;
1998 stream.next().await.transpose()?
1999 };
2000 match next {
2001 Some(frame) => Ok(RemoteFragmentResponse::Frame(Some(encode_remote_frame(
2002 &frame,
2003 self.max_message_bytes,
2004 )?))),
2005 None => {
2006 self.executions.lock().remove(&key);
2007 Ok(RemoteFragmentResponse::Frame(None))
2008 }
2009 }
2010 }
2011 RemoteFragmentRequest::Cancel {
2012 query_id,
2013 fragment_id,
2014 } => {
2015 if let Some(execution) = self.executions.lock().remove(&(query_id, fragment_id)) {
2016 execution.control.cancel(CancellationReason::ClientRequest);
2017 }
2018 Ok(RemoteFragmentResponse::Cancelled)
2019 }
2020 RemoteFragmentRequest::RefillTopK {
2021 query_id: _,
2022 fragment,
2023 offset,
2024 limit,
2025 authorization_context,
2026 deadline_ms,
2027 } => {
2028 if authorization_context.len() > MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES {
2029 return Err(DistributedError::RemoteProtocol(format!(
2030 "fragment authorization context is {} bytes; limit is {}",
2031 authorization_context.len(),
2032 MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES
2033 )));
2034 }
2035 let control = deadline_ms.map_or_else(
2036 || ExecutionControl::new(None),
2037 |milliseconds| {
2038 ExecutionControl::with_timeout(Duration::from_millis(milliseconds))
2039 },
2040 );
2041 let refill = self.executor.refill_top_k(
2042 &fragment,
2043 offset,
2044 limit,
2045 FragmentControl {
2046 control,
2047 max_spill_bytes: fragment.max_spill_bytes,
2048 authorization_context: authorization_context.into(),
2049 },
2050 )?;
2051 let payload = encode_record_batch(&refill.payload, self.max_message_bytes)?;
2052 Ok(RemoteFragmentResponse::TopKRefill {
2053 rows: refill.rows,
2054 payload,
2055 unseen_bound: refill.unseen_bound,
2056 })
2057 }
2058 }
2059 }
2060}
2061
2062#[async_trait::async_trait]
2068pub trait FragmentRpcClient: Send + Sync {
2069 async fn call(&self, request: Vec<u8>) -> DistributedResult<Vec<u8>>;
2071}
2072
2073pub struct LoopbackFragmentRpcClient {
2075 endpoint: Arc<RemoteFragmentEndpoint>,
2076}
2077
2078impl LoopbackFragmentRpcClient {
2079 pub fn new(endpoint: Arc<RemoteFragmentEndpoint>) -> Self {
2081 Self { endpoint }
2082 }
2083}
2084
2085#[async_trait::async_trait]
2086impl FragmentRpcClient for LoopbackFragmentRpcClient {
2087 async fn call(&self, request: Vec<u8>) -> DistributedResult<Vec<u8>> {
2088 self.endpoint.handle(&request).await
2089 }
2090}
2091
2092pub struct RemoteFragmentTransport {
2097 default_client: Option<Arc<dyn FragmentRpcClient>>,
2098 clients: parking_lot::RwLock<HashMap<TabletId, Arc<dyn FragmentRpcClient>>>,
2099 active: Arc<parking_lot::Mutex<HashMap<RemoteExecutionKey, Arc<dyn FragmentRpcClient>>>>,
2100 max_message_bytes: usize,
2101}
2102
2103impl RemoteFragmentTransport {
2104 pub fn new(default_client: Arc<dyn FragmentRpcClient>) -> Self {
2106 Self::with_message_limit(default_client, DEFAULT_REMOTE_FRAGMENT_MESSAGE_BYTES)
2107 }
2108
2109 pub fn with_message_limit(
2111 default_client: Arc<dyn FragmentRpcClient>,
2112 max_message_bytes: usize,
2113 ) -> Self {
2114 Self {
2115 default_client: Some(default_client),
2116 clients: parking_lot::RwLock::new(HashMap::new()),
2117 active: Arc::new(parking_lot::Mutex::new(HashMap::new())),
2118 max_message_bytes: max_message_bytes.max(1),
2119 }
2120 }
2121
2122 pub fn routed() -> Self {
2124 Self {
2125 default_client: None,
2126 clients: parking_lot::RwLock::new(HashMap::new()),
2127 active: Arc::new(parking_lot::Mutex::new(HashMap::new())),
2128 max_message_bytes: DEFAULT_REMOTE_FRAGMENT_MESSAGE_BYTES,
2129 }
2130 }
2131
2132 pub fn with_client(self, tablet: TabletId, client: Arc<dyn FragmentRpcClient>) -> Self {
2134 self.clients.write().insert(tablet, client);
2135 self
2136 }
2137
2138 fn client_for(&self, fragment: &PlanFragment) -> DistributedResult<Arc<dyn FragmentRpcClient>> {
2139 let tablet = tablet_of(fragment)?;
2140 self.clients
2141 .read()
2142 .get(&tablet)
2143 .cloned()
2144 .or_else(|| self.default_client.as_ref().map(Arc::clone))
2145 .ok_or_else(|| {
2146 DistributedError::RemoteTransport(format!(
2147 "no authenticated fragment route for tablet {tablet}"
2148 ))
2149 })
2150 }
2151
2152 async fn call(
2153 &self,
2154 client: &Arc<dyn FragmentRpcClient>,
2155 request: RemoteFragmentRequest,
2156 ) -> DistributedResult<RemoteFragmentResponse> {
2157 remote_call(client, request, self.max_message_bytes).await
2158 }
2159}
2160
2161struct RemoteStreamState {
2162 client: Arc<dyn FragmentRpcClient>,
2163 key: RemoteExecutionKey,
2164 active: Arc<parking_lot::Mutex<HashMap<RemoteExecutionKey, Arc<dyn FragmentRpcClient>>>>,
2165 max_message_bytes: usize,
2166 complete: bool,
2167 yielded_error: bool,
2168}
2169
2170impl RemoteStreamState {
2171 fn mark_complete(&mut self) {
2172 self.complete = true;
2173 }
2174}
2175
2176impl Drop for RemoteStreamState {
2177 fn drop(&mut self) {
2178 self.active.lock().remove(&self.key);
2179 if self.complete {
2180 return;
2181 }
2182 let request = RemoteFragmentRequest::Cancel {
2183 query_id: self.key.0,
2184 fragment_id: self.key.1,
2185 };
2186 let client = Arc::clone(&self.client);
2187 let max_message_bytes = self.max_message_bytes;
2188 if let Ok(runtime) = tokio::runtime::Handle::try_current() {
2189 runtime.spawn(async move {
2190 let _ = remote_call(&client, request, max_message_bytes).await;
2191 });
2192 }
2193 }
2194}
2195
2196#[async_trait::async_trait]
2197impl FragmentTransport for RemoteFragmentTransport {
2198 async fn execute_fragment(
2199 &self,
2200 query_id: QueryId,
2201 fragment: &PlanFragment,
2202 inputs: Vec<FragmentStream>,
2203 control: FragmentControl,
2204 ) -> DistributedResult<FragmentStream> {
2205 let client = self.client_for(fragment)?;
2206 let mut wire_inputs = Vec::with_capacity(inputs.len());
2207 for input in inputs {
2208 let frames = drain_stream(input, &control.control).await?;
2209 wire_inputs.push(
2210 frames
2211 .iter()
2212 .map(|frame| encode_remote_frame(frame, self.max_message_bytes))
2213 .collect::<DistributedResult<Vec<_>>>()?,
2214 );
2215 }
2216 let key = (query_id, fragment.fragment_id);
2217 self.active.lock().insert(key, Arc::clone(&client));
2218 let start = self.call(
2219 &client,
2220 RemoteFragmentRequest::Start {
2221 query_id,
2222 fragment: fragment.clone(),
2223 inputs: wire_inputs,
2224 max_spill_bytes: control.max_spill_bytes,
2225 authorization_context: control.authorization_context.to_vec(),
2226 deadline_ms: control
2227 .control
2228 .remaining_duration()
2229 .map(|duration| duration.as_millis().min(u128::from(u64::MAX)) as u64),
2230 },
2231 );
2232 let response = tokio::select! {
2233 response = start => response,
2234 _ = control.control.cancelled() => {
2235 let _ = remote_call(
2236 &client,
2237 RemoteFragmentRequest::Cancel {
2238 query_id,
2239 fragment_id: fragment.fragment_id,
2240 },
2241 self.max_message_bytes,
2242 ).await;
2243 Err(DistributedError::Cancelled(control.control.reason()))
2244 }
2245 };
2246 let response = match response {
2247 Ok(response) => response,
2248 Err(error) => {
2249 self.active.lock().remove(&key);
2250 return Err(error);
2251 }
2252 };
2253 match response {
2254 RemoteFragmentResponse::Started => {}
2255 other => {
2256 self.active.lock().remove(&key);
2257 return Err(unexpected_remote_response("Started", &other));
2258 }
2259 }
2260 let state = RemoteStreamState {
2261 client,
2262 key,
2263 active: Arc::clone(&self.active),
2264 max_message_bytes: self.max_message_bytes,
2265 complete: false,
2266 yielded_error: false,
2267 };
2268 let output = stream::unfold(state, |mut state| async move {
2269 if state.complete || state.yielded_error {
2270 return None;
2271 }
2272 let response = remote_call(
2273 &state.client,
2274 RemoteFragmentRequest::Pull {
2275 query_id: state.key.0,
2276 fragment_id: state.key.1,
2277 },
2278 state.max_message_bytes,
2279 )
2280 .await;
2281 match response {
2282 Ok(RemoteFragmentResponse::Frame(Some(frame))) => {
2283 let item = decode_remote_frame(frame, state.max_message_bytes);
2284 if item.is_err() {
2285 state.yielded_error = true;
2286 }
2287 Some((item, state))
2288 }
2289 Ok(RemoteFragmentResponse::Frame(None)) => {
2290 state.mark_complete();
2291 None
2292 }
2293 Ok(other) => {
2294 state.yielded_error = true;
2295 Some((Err(unexpected_remote_response("Frame", &other)), state))
2296 }
2297 Err(error) => {
2298 state.yielded_error = true;
2299 Some((Err(error), state))
2300 }
2301 }
2302 });
2303 Ok(Box::pin(output))
2304 }
2305
2306 fn cancel_fragment(&self, query_id: QueryId, fragment_id: FragmentId) -> DistributedResult<()> {
2307 let key = (query_id, fragment_id);
2308 let Some(client) = self.active.lock().remove(&key) else {
2309 return Ok(());
2310 };
2311 let max_message_bytes = self.max_message_bytes;
2312 let runtime = tokio::runtime::Handle::try_current().map_err(|error| {
2313 DistributedError::RemoteTransport(format!(
2314 "cannot schedule fragment cancellation outside Tokio: {error}"
2315 ))
2316 })?;
2317 runtime.spawn(async move {
2318 let _ = remote_call(
2319 &client,
2320 RemoteFragmentRequest::Cancel {
2321 query_id,
2322 fragment_id,
2323 },
2324 max_message_bytes,
2325 )
2326 .await;
2327 });
2328 Ok(())
2329 }
2330
2331 async fn refill_top_k(
2332 &self,
2333 query_id: QueryId,
2334 fragment: &PlanFragment,
2335 offset: usize,
2336 limit: usize,
2337 control: FragmentControl,
2338 ) -> DistributedResult<TopKRefill> {
2339 let client = self.client_for(fragment)?;
2340 match self
2341 .call(
2342 &client,
2343 RemoteFragmentRequest::RefillTopK {
2344 query_id,
2345 fragment: fragment.clone(),
2346 offset,
2347 limit,
2348 authorization_context: control.authorization_context.to_vec(),
2349 deadline_ms: control
2350 .control
2351 .remaining_duration()
2352 .map(|duration| duration.as_millis().min(u128::from(u64::MAX)) as u64),
2353 },
2354 )
2355 .await?
2356 {
2357 RemoteFragmentResponse::TopKRefill {
2358 rows,
2359 payload,
2360 unseen_bound,
2361 } => Ok(TopKRefill {
2362 rows,
2363 payload: decode_record_batch(&payload, self.max_message_bytes)?,
2364 unseen_bound,
2365 }),
2366 other => Err(unexpected_remote_response("TopKRefill", &other)),
2367 }
2368 }
2369}
2370
2371async fn remote_call(
2372 client: &Arc<dyn FragmentRpcClient>,
2373 request: RemoteFragmentRequest,
2374 max_message_bytes: usize,
2375) -> DistributedResult<RemoteFragmentResponse> {
2376 let request = encode_remote_wire(
2377 &RemoteFragmentEnvelope {
2378 version: REMOTE_FRAGMENT_PROTOCOL_VERSION,
2379 request,
2380 },
2381 max_message_bytes,
2382 )?;
2383 let response = client.call(request).await?;
2384 let envelope: RemoteFragmentResponseEnvelope =
2385 decode_remote_wire(&response, max_message_bytes)?;
2386 if envelope.version != REMOTE_FRAGMENT_PROTOCOL_VERSION {
2387 return Err(DistributedError::RemoteProtocol(format!(
2388 "peer answered with fragment protocol version {}; supported version is {}",
2389 envelope.version, REMOTE_FRAGMENT_PROTOCOL_VERSION
2390 )));
2391 }
2392 match envelope.response {
2393 RemoteFragmentResponse::Error(message) => Err(DistributedError::RemoteTransport(message)),
2394 response => Ok(response),
2395 }
2396}
2397
2398fn remote_wire_options() -> impl Options {
2399 bincode::DefaultOptions::new()
2400 .with_fixint_encoding()
2401 .reject_trailing_bytes()
2402}
2403
2404fn encode_remote_wire<T: Serialize>(
2405 value: &T,
2406 max_message_bytes: usize,
2407) -> DistributedResult<Vec<u8>> {
2408 let bytes = remote_wire_options()
2409 .serialize(value)
2410 .map_err(|error| DistributedError::RemoteProtocol(error.to_string()))?;
2411 if bytes.len() > max_message_bytes {
2412 return Err(DistributedError::RemoteProtocol(format!(
2413 "encoded fragment message is {} bytes; limit is {max_message_bytes}",
2414 bytes.len()
2415 )));
2416 }
2417 Ok(bytes)
2418}
2419
2420fn decode_remote_wire<T: for<'de> Deserialize<'de>>(
2421 bytes: &[u8],
2422 max_message_bytes: usize,
2423) -> DistributedResult<T> {
2424 if bytes.len() > max_message_bytes {
2425 return Err(DistributedError::RemoteProtocol(format!(
2426 "fragment message is {} bytes; limit is {max_message_bytes}",
2427 bytes.len()
2428 )));
2429 }
2430 remote_wire_options()
2431 .with_limit(max_message_bytes as u64)
2432 .deserialize(bytes)
2433 .map_err(|error| DistributedError::RemoteProtocol(error.to_string()))
2434}
2435
2436fn encode_record_batch(
2437 batch: &RecordBatch,
2438 max_message_bytes: usize,
2439) -> DistributedResult<Vec<u8>> {
2440 let mut ipc = Vec::new();
2441 {
2442 let mut writer = StreamWriter::try_new(&mut ipc, &batch.schema())?;
2443 writer.write(batch)?;
2444 writer.finish()?;
2445 }
2446 if ipc.len() > max_message_bytes {
2447 return Err(DistributedError::RemoteProtocol(format!(
2448 "Arrow IPC batch is {} bytes; limit is {max_message_bytes}",
2449 ipc.len()
2450 )));
2451 }
2452 Ok(ipc)
2453}
2454
2455fn decode_record_batch(ipc: &[u8], max_message_bytes: usize) -> DistributedResult<RecordBatch> {
2456 if ipc.len() > max_message_bytes {
2457 return Err(DistributedError::RemoteProtocol(format!(
2458 "Arrow IPC batch is {} bytes; limit is {max_message_bytes}",
2459 ipc.len()
2460 )));
2461 }
2462 let mut reader = StreamReader::try_new(Cursor::new(ipc), None)?;
2463 let batch = reader.next().transpose()?.ok_or_else(|| {
2464 DistributedError::RemoteProtocol("Arrow IPC frame contains no record batch".to_owned())
2465 })?;
2466 if reader.next().transpose()?.is_some() {
2467 return Err(DistributedError::RemoteProtocol(
2468 "Arrow IPC frame contains more than one record batch".to_owned(),
2469 ));
2470 }
2471 Ok(batch)
2472}
2473
2474fn encode_remote_frame(
2475 frame: &BatchFrame,
2476 max_message_bytes: usize,
2477) -> DistributedResult<RemoteBatchFrame> {
2478 Ok(RemoteBatchFrame {
2479 ipc: encode_record_batch(&frame.batch, max_message_bytes)?,
2480 score_bound: frame.score_bound,
2481 })
2482}
2483
2484fn decode_remote_frame(
2485 frame: RemoteBatchFrame,
2486 max_message_bytes: usize,
2487) -> DistributedResult<BatchFrame> {
2488 Ok(BatchFrame {
2489 batch: decode_record_batch(&frame.ipc, max_message_bytes)?,
2490 score_bound: frame.score_bound,
2491 })
2492}
2493
2494fn unexpected_remote_response(expected: &str, actual: &RemoteFragmentResponse) -> DistributedError {
2495 DistributedError::RemoteProtocol(format!(
2496 "expected remote {expected} response, got {actual:?}"
2497 ))
2498}
2499
2500pub trait FragmentTableSource: Send + Sync {
2504 fn scan(
2506 &self,
2507 table: &str,
2508 tablet: TabletId,
2509 include_row_id: bool,
2510 control: Option<&FragmentControl>,
2511 ) -> DistributedResult<Vec<RecordBatch>>;
2512
2513 fn schema(&self, table: &str, tablet: TabletId) -> DistributedResult<Option<SchemaRef>>;
2515}
2516
2517pub trait FragmentDatabaseProvider: Send + Sync {
2519 fn database(&self, tablet: TabletId) -> Option<Arc<mongreldb_core::Database>>;
2521}
2522
2523pub trait FragmentAuthorizationResolver: Send + Sync {
2525 fn resolve(
2528 &self,
2529 database: &mongreldb_core::Database,
2530 context: &[u8],
2531 ) -> DistributedResult<Option<mongreldb_core::Principal>>;
2532}
2533
2534pub struct CoreFragmentTableSource {
2536 databases: Arc<dyn FragmentDatabaseProvider>,
2537 authorization: Arc<dyn FragmentAuthorizationResolver>,
2538}
2539
2540impl CoreFragmentTableSource {
2541 pub fn new(
2543 databases: Arc<dyn FragmentDatabaseProvider>,
2544 authorization: Arc<dyn FragmentAuthorizationResolver>,
2545 ) -> Self {
2546 Self {
2547 databases,
2548 authorization,
2549 }
2550 }
2551}
2552
2553impl FragmentTableSource for CoreFragmentTableSource {
2554 fn scan(
2555 &self,
2556 table: &str,
2557 tablet: TabletId,
2558 include_row_id: bool,
2559 control: Option<&FragmentControl>,
2560 ) -> DistributedResult<Vec<RecordBatch>> {
2561 let control = control.ok_or_else(|| {
2562 DistributedError::RemoteProtocol(
2563 "core tablet scan requires fragment authorization control".to_owned(),
2564 )
2565 })?;
2566 checkpoint(&control.control)?;
2567 let database = self.databases.database(tablet).ok_or_else(|| {
2568 DistributedError::RemoteTransport(format!("this node does not host tablet {tablet}"))
2569 })?;
2570 let principal = self
2571 .authorization
2572 .resolve(&database, &control.authorization_context)?;
2573 let schema = database
2574 .table(table)
2575 .map_err(|error| DistributedError::RemoteTransport(error.to_string()))?
2576 .lock()
2577 .schema()
2578 .clone();
2579 let rows = database
2580 .query_as_principal_controlled(
2581 table,
2582 &mongreldb_core::Query {
2583 conditions: Vec::new(),
2584 limit: None,
2589 offset: 0,
2590 },
2591 None,
2592 principal.as_ref(),
2593 &control.control,
2594 )
2595 .map_err(|error| DistributedError::RemoteTransport(error.to_string()))?;
2596 let batch = crate::arrow_conv::rows_to_batch(&rows, &schema)
2597 .map_err(|error| DistributedError::Arrow(error.to_string()))?;
2598 if !include_row_id {
2599 return Ok(vec![batch]);
2600 }
2601 let mut fields = batch.schema().fields().iter().cloned().collect::<Vec<_>>();
2602 fields.push(Arc::new(Field::new(
2603 TOPK_ROWID_COLUMN,
2604 DataType::UInt64,
2605 false,
2606 )));
2607 let mut columns = batch.columns().to_vec();
2608 columns.push(Arc::new(UInt64Array::from(
2609 rows.iter().map(|row| row.row_id.0).collect::<Vec<_>>(),
2610 )));
2611 let batch = RecordBatch::try_new(Arc::new(Schema::new(fields)), columns)?;
2612 Ok(vec![batch])
2613 }
2614
2615 fn schema(&self, _table: &str, _tablet: TabletId) -> DistributedResult<Option<SchemaRef>> {
2616 Ok(None)
2619 }
2620}
2621
2622#[derive(Default)]
2625pub struct InMemoryTableStore {
2626 tables: parking_lot::RwLock<HashMap<(String, TabletId), Vec<RecordBatch>>>,
2627 schemas: parking_lot::RwLock<HashMap<String, SchemaRef>>,
2628}
2629
2630impl InMemoryTableStore {
2631 pub fn new() -> Self {
2633 Self::default()
2634 }
2635
2636 pub fn insert(&self, table: &str, tablet: TabletId, batch: RecordBatch) {
2639 self.schemas
2640 .write()
2641 .entry(table.to_owned())
2642 .or_insert_with(|| batch.schema());
2643 self.tables
2644 .write()
2645 .entry((table.to_owned(), tablet))
2646 .or_default()
2647 .push(batch);
2648 }
2649
2650 pub fn register_schema(&self, table: &str, schema: SchemaRef) {
2652 self.schemas.write().insert(table.to_owned(), schema);
2653 }
2654
2655 pub fn snapshot(&self, table: &str, tablet: TabletId) -> Vec<RecordBatch> {
2657 self.tables
2658 .read()
2659 .get(&(table.to_owned(), tablet))
2660 .cloned()
2661 .unwrap_or_default()
2662 }
2663
2664 pub fn schema(&self, table: &str) -> Option<SchemaRef> {
2666 self.schemas.read().get(table).cloned()
2667 }
2668}
2669
2670impl FragmentTableSource for InMemoryTableStore {
2671 fn scan(
2672 &self,
2673 table: &str,
2674 tablet: TabletId,
2675 _include_row_id: bool,
2676 _control: Option<&FragmentControl>,
2677 ) -> DistributedResult<Vec<RecordBatch>> {
2678 Ok(self.snapshot(table, tablet))
2679 }
2680
2681 fn schema(&self, table: &str, _tablet: TabletId) -> DistributedResult<Option<SchemaRef>> {
2682 Ok(self.schema(table))
2683 }
2684}
2685
2686pub struct InMemoryFragmentExecutor {
2692 store: Arc<dyn FragmentTableSource>,
2693 topk_emit_batch: Option<usize>,
2697}
2698
2699impl InMemoryFragmentExecutor {
2700 pub fn new(store: Arc<InMemoryTableStore>) -> Self {
2702 Self {
2703 store,
2704 topk_emit_batch: None,
2705 }
2706 }
2707
2708 pub fn with_topk_emit_batch(store: Arc<InMemoryTableStore>, batch: usize) -> Self {
2710 Self {
2711 store,
2712 topk_emit_batch: Some(batch),
2713 }
2714 }
2715
2716 pub fn from_source(store: Arc<dyn FragmentTableSource>) -> Self {
2719 Self {
2720 store,
2721 topk_emit_batch: None,
2722 }
2723 }
2724
2725 fn scan_batches(
2727 &self,
2728 fragment: &PlanFragment,
2729 control: Option<&FragmentControl>,
2730 ) -> DistributedResult<Vec<RecordBatch>> {
2731 let tablet = tablet_of(fragment)?;
2732 let scan = fragment
2733 .operators
2734 .iter()
2735 .find_map(|operator| match operator {
2736 FragmentOperator::TabletScan {
2737 table,
2738 predicate,
2739 projection,
2740 } => Some((table, predicate, projection)),
2741 _ => None,
2742 })
2743 .ok_or_else(|| {
2744 DistributedError::InvalidPlan(format!(
2745 "fragment {} has no tablet scan",
2746 fragment.fragment_id
2747 ))
2748 })?;
2749 if scan.1.is_some() {
2750 return Err(DistributedError::Unsupported(
2751 "tablet predicate execution is not bound to the fragment operator engine"
2752 .to_owned(),
2753 ));
2754 }
2755 let include_row_id = fragment
2756 .operators
2757 .iter()
2758 .any(|operator| matches!(operator, FragmentOperator::DistributedTopK { .. }));
2759 let mut batches = self.store.scan(scan.0, tablet, include_row_id, control)?;
2760 if batches.is_empty() {
2761 if let Some(schema) = self.store.schema(scan.0, tablet)? {
2762 batches = vec![RecordBatch::new_empty(schema)];
2763 }
2764 }
2765 if !scan.2.is_empty() {
2766 batches = project_batches(&batches, scan.2)?;
2767 }
2768 Ok(batches)
2769 }
2770}
2771
2772#[async_trait::async_trait]
2773impl FragmentExecutor for InMemoryFragmentExecutor {
2774 async fn execute(
2775 &self,
2776 fragment: &PlanFragment,
2777 inputs: Vec<FragmentStream>,
2778 control: FragmentControl,
2779 ) -> DistributedResult<FragmentStream> {
2780 let mut batches: Vec<RecordBatch> = Vec::new();
2781 let mut inputs = inputs.into_iter();
2782 let mut bound = ScoreBound::Unknown;
2783 for operator in &fragment.operators {
2784 checkpoint(&control.control)?;
2785 match operator {
2786 FragmentOperator::TabletScan { .. } => {
2787 batches = self.scan_batches(fragment, Some(&control))?;
2788 }
2789 FragmentOperator::RemoteExchangeSource { .. } => {
2790 let input = inputs.next().ok_or_else(|| {
2791 DistributedError::InvalidPlan(format!(
2792 "fragment {} is missing an exchange input stream",
2793 fragment.fragment_id
2794 ))
2795 })?;
2796 let frames = drain_stream(input, &control.control).await?;
2797 batches.extend(frames.into_iter().map(|frame| frame.batch));
2798 }
2799 FragmentOperator::PartialAggregate {
2800 group_by,
2801 aggregates,
2802 } => {
2803 batches = vec![partial_aggregate_batches(&batches, group_by, aggregates)?];
2804 }
2805 FragmentOperator::FinalAggregate {
2806 group_by,
2807 aggregates,
2808 } => {
2809 batches = vec![final_aggregate_batches(&batches, group_by, aggregates)?];
2810 }
2811 FragmentOperator::MergeSort { keys, limit } => {
2812 batches = sort_batches_local(&batches, keys, *limit)?;
2813 }
2814 FragmentOperator::DistributedTopK { k, score } => {
2815 let tablet = tablet_of(fragment)?;
2816 let input = prepare_top_k(&batches, &score.column, tablet)?;
2817 let emit = self.topk_emit_batch.unwrap_or(*k).min(*k);
2818 let (_rows, payload, next) = input.emit(0, emit);
2819 batches = vec![payload];
2820 bound = match next {
2821 Some(next) => ScoreBound::AtMost(next),
2822 None => ScoreBound::Exhausted,
2823 };
2824 }
2825 FragmentOperator::DistributedLimit { limit } => {
2826 batches = limit_batches(&batches, *limit);
2827 }
2828 FragmentOperator::RemoteExchangeSink { .. } => {
2829 }
2832 FragmentOperator::DistributedHashJoin { .. }
2833 | FragmentOperator::BroadcastJoin { .. }
2834 | FragmentOperator::RepartitionJoin { .. } => {
2835 return Err(DistributedError::Unsupported(
2836 "join execution binding lands with the tablet wave".to_owned(),
2837 ));
2838 }
2839 }
2840 }
2841 let mut frames: Vec<BatchFrame> = batches.into_iter().map(BatchFrame::data).collect();
2842 if bound != ScoreBound::Unknown {
2843 if let Some(last) = frames.last_mut() {
2844 last.score_bound = bound;
2845 }
2846 }
2847 Ok(Box::pin(stream::iter(frames.into_iter().map(Ok))))
2848 }
2849
2850 fn refill_top_k(
2851 &self,
2852 fragment: &PlanFragment,
2853 offset: usize,
2854 limit: usize,
2855 control: FragmentControl,
2856 ) -> DistributedResult<TopKRefill> {
2857 let tablet = tablet_of(fragment)?;
2858 let score = fragment
2859 .operators
2860 .iter()
2861 .find_map(|operator| match operator {
2862 FragmentOperator::DistributedTopK { score, .. } => Some(score.clone()),
2863 _ => None,
2864 })
2865 .ok_or_else(|| {
2866 DistributedError::InvalidPlan(format!(
2867 "fragment {} has no distributed top-k operator",
2868 fragment.fragment_id
2869 ))
2870 })?;
2871 let batches = self.scan_batches(fragment, Some(&control))?;
2872 let input = prepare_top_k(&batches, &score.column, tablet)?;
2873 let (rows, payload, unseen_bound) = input.emit(offset, limit);
2874 Ok(TopKRefill {
2875 rows,
2876 payload,
2877 unseen_bound,
2878 })
2879 }
2880}
2881
2882fn checkpoint(control: &ExecutionControl) -> DistributedResult<()> {
2888 control
2889 .checkpoint()
2890 .map_err(|_| DistributedError::Cancelled(control.reason()))
2891}
2892
2893fn tablet_of(fragment: &PlanFragment) -> DistributedResult<TabletId> {
2895 match fragment.assignment {
2896 FragmentAssignment::Tablet(tablet) => Ok(tablet),
2897 FragmentAssignment::Coordinator => Err(DistributedError::InvalidPlan(format!(
2898 "fragment {} operator requires a tablet assignment",
2899 fragment.fragment_id
2900 ))),
2901 }
2902}
2903
2904async fn drain_stream(
2906 mut stream: FragmentStream,
2907 control: &ExecutionControl,
2908) -> DistributedResult<Vec<BatchFrame>> {
2909 let mut frames = Vec::new();
2910 while let Some(item) = stream.next().await {
2911 checkpoint(control)?;
2912 frames.push(item?);
2913 }
2914 Ok(frames)
2915}
2916
2917fn concat_all(batches: &[RecordBatch]) -> DistributedResult<Option<RecordBatch>> {
2919 let Some(first) = batches.first() else {
2920 return Ok(None);
2921 };
2922 if batches.len() == 1 {
2923 return Ok(Some(first.clone()));
2924 }
2925 Ok(Some(concat_batches(&first.schema(), batches)?))
2926}
2927
2928fn project_batches(
2930 batches: &[RecordBatch],
2931 projection: &[String],
2932) -> DistributedResult<Vec<RecordBatch>> {
2933 batches
2934 .iter()
2935 .map(|batch| {
2936 let schema = batch.schema();
2937 let indexes: Vec<usize> = projection
2938 .iter()
2939 .map(|name| {
2940 schema.index_of(name).map_err(|_| {
2941 DistributedError::InvalidPlan(format!(
2942 "projection column `{name}` not in schema"
2943 ))
2944 })
2945 })
2946 .collect::<DistributedResult<Vec<usize>>>()?;
2947 Ok(batch.project(&indexes)?)
2948 })
2949 .collect()
2950}
2951
2952fn row_converter(
2954 schema: &Schema,
2955 keys: &[SortKey],
2956) -> DistributedResult<(RowConverter, Vec<usize>)> {
2957 let mut fields = Vec::with_capacity(keys.len());
2958 let mut indexes = Vec::with_capacity(keys.len());
2959 for key in keys {
2960 let index = schema.index_of(&key.column).map_err(|_| {
2961 DistributedError::InvalidPlan(format!("sort key `{}` not in schema", key.column))
2962 })?;
2963 let options = arrow::compute::SortOptions {
2967 descending: key.descending,
2968 nulls_first: key.descending,
2969 };
2970 fields.push(SortField::new_with_options(
2971 schema.field(index).data_type().clone(),
2972 options,
2973 ));
2974 indexes.push(index);
2975 }
2976 Ok((RowConverter::new(fields)?, indexes))
2977}
2978
2979fn take_rows(batch: &RecordBatch, order: &[usize]) -> DistributedResult<Vec<RecordBatch>> {
2981 let mut out = Vec::new();
2982 for chunk in order.chunks(COORDINATOR_OUTPUT_BATCH_ROWS) {
2983 let indices = UInt32Array::from(
2984 chunk
2985 .iter()
2986 .map(|index| u32::try_from(*index).unwrap_or(u32::MAX))
2987 .collect::<Vec<u32>>(),
2988 );
2989 let mut columns = Vec::with_capacity(batch.num_columns());
2990 for column in batch.columns() {
2991 columns.push(take(column, &indices, None)?);
2992 }
2993 out.push(RecordBatch::try_new(batch.schema(), columns)?);
2994 }
2995 if out.is_empty() {
2996 out.push(RecordBatch::new_empty(batch.schema()));
2997 }
2998 Ok(out)
2999}
3000
3001fn emit_interleaved(
3004 streams: &[RecordBatch],
3005 order: &[(usize, usize)],
3006) -> DistributedResult<Vec<RecordBatch>> {
3007 let Some(first) = streams.first() else {
3008 return Ok(Vec::new());
3009 };
3010 if order.is_empty() {
3011 return Ok(vec![RecordBatch::new_empty(first.schema())]);
3012 }
3013 let schema = first.schema();
3014 let mut out = Vec::new();
3015 for chunk in order.chunks(COORDINATOR_OUTPUT_BATCH_ROWS) {
3016 let mut columns = Vec::with_capacity(schema.fields().len());
3017 for column_index in 0..schema.fields().len() {
3018 let refs: Vec<&dyn Array> = streams
3019 .iter()
3020 .map(|batch| batch.column(column_index).as_ref())
3021 .collect();
3022 columns.push(interleave(&refs, chunk)?);
3023 }
3024 out.push(RecordBatch::try_new(schema.clone(), columns)?);
3025 }
3026 Ok(out)
3027}
3028
3029fn sort_batches_local(
3032 batches: &[RecordBatch],
3033 keys: &[SortKey],
3034 limit: Option<usize>,
3035) -> DistributedResult<Vec<RecordBatch>> {
3036 let Some(batch) = concat_all(batches)? else {
3037 return Ok(Vec::new());
3038 };
3039 if batch.num_rows() == 0 {
3040 return Ok(vec![RecordBatch::new_empty(batch.schema())]);
3041 }
3042 let (converter, indexes) = row_converter(&batch.schema(), keys)?;
3043 let columns: Vec<ArrayRef> = indexes
3044 .iter()
3045 .map(|index| batch.column(*index).clone())
3046 .collect();
3047 let rows = converter.convert_columns(&columns)?;
3048 let mut order: Vec<usize> = (0..batch.num_rows()).collect();
3049 order.sort_by(|left, right| {
3050 rows.row(*left)
3051 .cmp(&rows.row(*right))
3052 .then_with(|| left.cmp(right))
3053 });
3054 if let Some(limit) = limit {
3055 order.truncate(limit);
3056 }
3057 take_rows(&batch, &order)
3058}
3059
3060fn merge_sorted_streams(
3064 streams: &[RecordBatch],
3065 keys: &[SortKey],
3066 limit: Option<usize>,
3067) -> DistributedResult<Vec<RecordBatch>> {
3068 struct MergeItem {
3070 key: Vec<u8>,
3071 stream: usize,
3072 }
3073 impl PartialEq for MergeItem {
3074 fn eq(&self, other: &Self) -> bool {
3075 self.key == other.key && self.stream == other.stream
3076 }
3077 }
3078 impl Eq for MergeItem {}
3079 impl PartialOrd for MergeItem {
3080 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
3081 Some(self.cmp(other))
3082 }
3083 }
3084 impl Ord for MergeItem {
3085 fn cmp(&self, other: &Self) -> Ordering {
3086 self.key
3087 .cmp(&other.key)
3088 .then_with(|| self.stream.cmp(&other.stream))
3089 }
3090 }
3091
3092 let mut per_stream: Vec<Vec<Vec<u8>>> = Vec::with_capacity(streams.len());
3093 for batch in streams {
3094 let (converter, indexes) = row_converter(&batch.schema(), keys)?;
3095 let columns: Vec<ArrayRef> = indexes
3096 .iter()
3097 .map(|index| batch.column(*index).clone())
3098 .collect();
3099 let rows = converter.convert_columns(&columns)?;
3100 per_stream.push(
3101 (0..batch.num_rows())
3102 .map(|row| rows.row(row).as_ref().to_vec())
3103 .collect(),
3104 );
3105 }
3106 let mut cursors: Vec<usize> = vec![0; streams.len()];
3107 let mut heap = BinaryHeap::new();
3108 for (stream, keys) in per_stream.iter().enumerate() {
3109 if let Some(key) = keys.first() {
3110 heap.push(std::cmp::Reverse(MergeItem {
3111 key: key.clone(),
3112 stream,
3113 }));
3114 }
3115 }
3116 let mut order = Vec::new();
3117 while let Some(std::cmp::Reverse(item)) = heap.pop() {
3118 let row = cursors[item.stream];
3119 order.push((item.stream, row));
3120 if limit.is_some_and(|limit| order.len() >= limit) {
3121 break;
3122 }
3123 cursors[item.stream] += 1;
3124 let cursor = cursors[item.stream];
3125 if let Some(key) = per_stream[item.stream].get(cursor) {
3126 heap.push(std::cmp::Reverse(MergeItem {
3127 key: key.clone(),
3128 stream: item.stream,
3129 }));
3130 }
3131 }
3132 emit_interleaved(streams, &order)
3133}
3134
3135fn limit_batches(batches: &[RecordBatch], limit: usize) -> Vec<RecordBatch> {
3137 let mut remaining = limit;
3138 let mut out = Vec::new();
3139 for batch in batches {
3140 if remaining == 0 {
3141 break;
3142 }
3143 let rows = batch.num_rows().min(remaining);
3144 if rows == 0 {
3145 continue;
3146 }
3147 out.push(if rows == batch.num_rows() {
3148 batch.clone()
3149 } else {
3150 batch.slice(0, rows)
3151 });
3152 remaining -= rows;
3153 }
3154 out
3155}
3156
3157fn repartition_frames(
3161 frames: &[BatchFrame],
3162 keys: &[String],
3163 width: usize,
3164 index: usize,
3165) -> DistributedResult<Vec<BatchFrame>> {
3166 let batches: Vec<RecordBatch> = frames.iter().map(|frame| frame.batch.clone()).collect();
3167 let Some(batch) = concat_all(&batches)? else {
3168 return Ok(Vec::new());
3169 };
3170 if batch.num_rows() == 0 || width <= 1 {
3171 return Ok(vec![BatchFrame::data(batch)]);
3172 }
3173 let schema = batch.schema();
3174 let mut key_columns = Vec::with_capacity(keys.len());
3175 for key in keys {
3176 let column_index = schema.index_of(key).map_err(|_| {
3177 DistributedError::InvalidPlan(format!("repartition key `{key}` not in schema"))
3178 })?;
3179 key_columns.push(batch.column(column_index).clone());
3180 }
3181 let converter = RowConverter::new(
3182 key_columns
3183 .iter()
3184 .map(|column| SortField::new(column.data_type().clone()))
3185 .collect::<Vec<SortField>>(),
3186 )?;
3187 let rows = converter.convert_columns(&key_columns)?;
3188 let mut order = Vec::new();
3189 for row in 0..batch.num_rows() {
3190 let bucket = (fnv1a64(rows.row(row).as_ref()) % width as u64) as usize;
3191 if bucket == index {
3192 order.push(row);
3193 }
3194 }
3195 Ok(take_rows(&batch, &order)?
3196 .into_iter()
3197 .map(BatchFrame::data)
3198 .collect())
3199}
3200
3201fn score_key(array: &dyn Array, row: usize) -> DistributedResult<u64> {
3204 if array.is_null(row) {
3205 return Ok(0);
3206 }
3207 match array.data_type() {
3208 DataType::UInt64 => Ok(array
3209 .as_any()
3210 .downcast_ref::<UInt64Array>()
3211 .expect("type checked")
3212 .value(row)),
3213 DataType::Int64 => {
3214 let value = array
3215 .as_any()
3216 .downcast_ref::<Int64Array>()
3217 .expect("type checked")
3218 .value(row);
3219 Ok((value as u64) ^ (1_u64 << 63))
3220 }
3221 DataType::Float64 => {
3222 let bits = array
3223 .as_any()
3224 .downcast_ref::<Float64Array>()
3225 .expect("type checked")
3226 .value(row)
3227 .to_bits();
3228 Ok(if bits & (1_u64 << 63) != 0 {
3229 !bits
3230 } else {
3231 bits | (1_u64 << 63)
3232 })
3233 }
3234 other => Err(DistributedError::Unsupported(format!(
3235 "top-k score column of type {other} is not supported in this wave"
3236 ))),
3237 }
3238}
3239
3240fn row_ids(batch: &RecordBatch) -> DistributedResult<UInt64Array> {
3242 let index = batch.schema().index_of(TOPK_ROWID_COLUMN).map_err(|_| {
3243 DistributedError::InvalidPlan(format!(
3244 "top-k stream is missing the `{TOPK_ROWID_COLUMN}` column"
3245 ))
3246 })?;
3247 let array = batch.column(index);
3248 if array.data_type() != &DataType::UInt64 {
3249 return Err(DistributedError::InvalidPlan(format!(
3250 "`{TOPK_ROWID_COLUMN}` must be UInt64, found {}",
3251 array.data_type()
3252 )));
3253 }
3254 Ok(array
3255 .as_any()
3256 .downcast_ref::<UInt64Array>()
3257 .expect("type checked")
3258 .clone())
3259}
3260
3261struct TopKInput {
3264 batch: RecordBatch,
3265 candidates: Vec<TopKCandidate>,
3267 positions: Vec<usize>,
3269}
3270
3271impl TopKInput {
3272 fn emit(&self, offset: usize, limit: usize) -> (Vec<TopKCandidate>, RecordBatch, Option<u64>) {
3276 let offset = offset.min(self.candidates.len());
3277 let end = (offset + limit).min(self.candidates.len());
3278 let rows = self.candidates[offset..end].to_vec();
3279 let positions = &self.positions[offset..end];
3280 let payload = take_rows(&self.batch, positions)
3281 .unwrap_or_else(|_| vec![RecordBatch::new_empty(self.batch.schema())])
3282 .into_iter()
3283 .next()
3284 .unwrap_or_else(|| RecordBatch::new_empty(self.batch.schema()));
3285 let bound = self.candidates.get(end).map(|candidate| candidate.score);
3286 (rows, payload, bound)
3287 }
3288}
3289
3290fn prepare_top_k(
3292 batches: &[RecordBatch],
3293 score_column: &str,
3294 tablet: TabletId,
3295) -> DistributedResult<TopKInput> {
3296 let Some(batch) = concat_all(batches)? else {
3297 return Err(DistributedError::InvalidPlan(
3298 "top-k input has no batches".to_owned(),
3299 ));
3300 };
3301 let schema = batch.schema();
3302 let score_index = schema.index_of(score_column).map_err(|_| {
3303 DistributedError::InvalidPlan(format!("top-k score column `{score_column}` not in schema"))
3304 })?;
3305 let score_array = batch.column(score_index).clone();
3306 let row_id_array = row_ids(&batch)?;
3307 let mut candidates = Vec::with_capacity(batch.num_rows());
3308 for row in 0..batch.num_rows() {
3309 candidates.push(TopKCandidate {
3310 score: score_key(score_array.as_ref(), row)?,
3311 tablet,
3312 row_id: RowId(row_id_array.value(row)),
3313 });
3314 }
3315 let mut positions: Vec<usize> = (0..candidates.len()).collect();
3316 positions.sort_by(|left, right| topk_cmp(&candidates[*left], &candidates[*right]));
3317 let candidates = positions.iter().map(|index| candidates[*index]).collect();
3318 Ok(TopKInput {
3319 batch,
3320 candidates,
3321 positions,
3322 })
3323}
3324
3325#[derive(Clone, Copy, Debug)]
3332enum AggValue {
3333 I64(i64),
3334 F64(f64),
3335}
3336
3337fn numeric_cell(array: &dyn Array, row: usize) -> DistributedResult<Option<AggValue>> {
3339 if array.is_null(row) {
3340 return Ok(None);
3341 }
3342 match array.data_type() {
3343 DataType::Int64 => Ok(Some(AggValue::I64(
3344 array
3345 .as_any()
3346 .downcast_ref::<Int64Array>()
3347 .expect("type checked")
3348 .value(row),
3349 ))),
3350 DataType::Float64 => Ok(Some(AggValue::F64(
3351 array
3352 .as_any()
3353 .downcast_ref::<Float64Array>()
3354 .expect("type checked")
3355 .value(row),
3356 ))),
3357 other => Err(DistributedError::Unsupported(format!(
3358 "aggregate over {other} is not supported in this wave"
3359 ))),
3360 }
3361}
3362
3363#[derive(Clone, Debug)]
3365enum AggAccum {
3366 Count(i64),
3367 SumI(i128, bool),
3368 SumF(f64, bool),
3369 MinI(i64, bool),
3370 MinF(f64, bool),
3371 MaxI(i64, bool),
3372 MaxF(f64, bool),
3373 Avg { sum: f64, count: i64 },
3374}
3375
3376impl AggAccum {
3377 fn fresh(function: AggregateFunction, float: bool) -> Self {
3380 match function {
3381 AggregateFunction::Count => Self::Count(0),
3382 AggregateFunction::Sum if float => Self::SumF(0.0, false),
3383 AggregateFunction::Sum => Self::SumI(0, false),
3384 AggregateFunction::Min if float => Self::MinF(f64::INFINITY, false),
3385 AggregateFunction::Min => Self::MinI(i64::MAX, false),
3386 AggregateFunction::Max if float => Self::MaxF(f64::NEG_INFINITY, false),
3387 AggregateFunction::Max => Self::MaxI(i64::MIN, false),
3388 AggregateFunction::Avg => Self::Avg { sum: 0.0, count: 0 },
3389 }
3390 }
3391
3392 fn fold_value(&mut self, cell: Option<AggValue>) {
3394 match (self, cell) {
3395 (Self::SumI(sum, seen), Some(AggValue::I64(value))) => {
3396 *sum += i128::from(value);
3397 *seen = true;
3398 }
3399 (Self::SumF(sum, seen), Some(AggValue::F64(value))) => {
3400 *sum += value;
3401 *seen = true;
3402 }
3403 (Self::MinI(min, seen), Some(AggValue::I64(value))) => {
3404 *min = (*min).min(value);
3405 *seen = true;
3406 }
3407 (Self::MinF(min, seen), Some(AggValue::F64(value))) => {
3408 *min = min.min(value);
3409 *seen = true;
3410 }
3411 (Self::MaxI(max, seen), Some(AggValue::I64(value))) => {
3412 *max = (*max).max(value);
3413 *seen = true;
3414 }
3415 (Self::MaxF(max, seen), Some(AggValue::F64(value))) => {
3416 *max = max.max(value);
3417 *seen = true;
3418 }
3419 (Self::Avg { sum, count }, Some(AggValue::I64(value))) => {
3420 *sum += value as f64;
3421 *count += 1;
3422 }
3423 (Self::Avg { sum, count }, Some(AggValue::F64(value))) => {
3424 *sum += value;
3425 *count += 1;
3426 }
3427 _ => {}
3428 }
3429 }
3430}
3431
3432struct GroupEntry {
3434 first_row: usize,
3437 accums: Vec<AggAccum>,
3438}
3439
3440#[derive(Default)]
3442struct GroupFold {
3443 order: Vec<String>,
3444 groups: HashMap<String, GroupEntry>,
3445}
3446
3447impl GroupFold {
3448 fn entry(&mut self, key: String, row: usize, templates: &[AggAccum]) -> &mut GroupEntry {
3449 if !self.groups.contains_key(&key) {
3450 self.order.push(key.clone());
3451 self.groups.insert(
3452 key.clone(),
3453 GroupEntry {
3454 first_row: row,
3455 accums: templates.to_vec(),
3456 },
3457 );
3458 }
3459 self.groups.get_mut(&key).expect("inserted above")
3460 }
3461}
3462
3463fn group_key(batch: &RecordBatch, key_indexes: &[usize], row: usize) -> DistributedResult<String> {
3465 let mut parts = Vec::with_capacity(key_indexes.len());
3466 for index in key_indexes {
3467 parts.push(array_value_to_string(batch.column(*index).as_ref(), row)?);
3468 }
3469 Ok(parts.join("\u{1f}"))
3470}
3471
3472fn resolve_columns(schema: &Schema, columns: &[String]) -> DistributedResult<Vec<usize>> {
3474 columns
3475 .iter()
3476 .map(|column| {
3477 schema.index_of(column).map_err(|_| {
3478 DistributedError::InvalidPlan(format!("group-by column `{column}` not in schema"))
3479 })
3480 })
3481 .collect()
3482}
3483
3484fn aggregate_float(schema: &Schema, aggregate: &AggregateExpr) -> DistributedResult<bool> {
3486 match &aggregate.column {
3487 None => Ok(false),
3488 Some(column) => {
3489 let index = schema.index_of(column).map_err(|_| {
3490 DistributedError::InvalidPlan(format!("aggregate column `{column}` not in schema"))
3491 })?;
3492 match schema.field(index).data_type() {
3493 DataType::Int64 => Ok(false),
3494 DataType::Float64 => Ok(true),
3495 other => Err(DistributedError::Unsupported(format!(
3496 "aggregate over {other} is not supported in this wave"
3497 ))),
3498 }
3499 }
3500 }
3501}
3502
3503fn take_group_keys(
3505 batch: &RecordBatch,
3506 key_indexes: &[usize],
3507 fold: &GroupFold,
3508) -> DistributedResult<Vec<ArrayRef>> {
3509 if key_indexes.is_empty() {
3510 return Ok(Vec::new());
3511 }
3512 let indices = UInt32Array::from(
3513 fold.order
3514 .iter()
3515 .map(|key| u32::try_from(fold.groups[key].first_row).unwrap_or(u32::MAX))
3516 .collect::<Vec<u32>>(),
3517 );
3518 let mut columns = Vec::with_capacity(key_indexes.len());
3519 for index in key_indexes {
3520 columns.push(take(batch.column(*index), &indices, None)?);
3521 }
3522 Ok(columns)
3523}
3524
3525fn emit_int_accum(groups: &GroupFold, index: usize) -> DistributedResult<Vec<Option<i64>>> {
3527 let mut values = Vec::with_capacity(groups.order.len());
3528 for key in &groups.order {
3529 let accum = &groups.groups[key].accums[index];
3530 values.push(match accum {
3531 AggAccum::Count(count) => Some(*count),
3532 AggAccum::SumI(sum, seen) => seen
3533 .then(|| {
3534 i64::try_from(*sum).map_err(|_| {
3535 DistributedError::Unsupported(
3536 "integer sum overflow in this wave".to_owned(),
3537 )
3538 })
3539 })
3540 .transpose()?,
3541 AggAccum::MinI(value, seen) | AggAccum::MaxI(value, seen) => seen.then_some(*value),
3542 other => {
3543 return Err(DistributedError::InvalidPlan(format!(
3544 "internal: accumulator {other:?} is not an integer column"
3545 )))
3546 }
3547 });
3548 }
3549 Ok(values)
3550}
3551
3552fn emit_float_accum(groups: &GroupFold, index: usize) -> DistributedResult<Vec<Option<f64>>> {
3554 let mut values = Vec::with_capacity(groups.order.len());
3555 for key in &groups.order {
3556 let accum = &groups.groups[key].accums[index];
3557 values.push(match accum {
3558 AggAccum::SumF(sum, seen) => seen.then_some(*sum),
3559 AggAccum::MinF(value, seen) | AggAccum::MaxF(value, seen) => seen.then_some(*value),
3560 other => {
3561 return Err(DistributedError::InvalidPlan(format!(
3562 "internal: accumulator {other:?} is not a float column"
3563 )))
3564 }
3565 });
3566 }
3567 Ok(values)
3568}
3569
3570fn partial_aggregate_batches(
3573 batches: &[RecordBatch],
3574 group_by: &[String],
3575 aggregates: &[AggregateExpr],
3576) -> DistributedResult<RecordBatch> {
3577 let Some(batch) = concat_all(batches)? else {
3578 return Err(DistributedError::InvalidPlan(
3579 "aggregate input has no batches".to_owned(),
3580 ));
3581 };
3582 let schema = batch.schema();
3583 let key_indexes = resolve_columns(&schema, group_by)?;
3584 let mut value_indexes = Vec::with_capacity(aggregates.len());
3585 let mut templates = Vec::with_capacity(aggregates.len());
3586 for aggregate in aggregates {
3587 let float = aggregate_float(&schema, aggregate)?;
3588 value_indexes.push(match &aggregate.column {
3589 Some(column) => Some(schema.index_of(column).map_err(|_| {
3590 DistributedError::InvalidPlan(format!("aggregate column `{column}` not in schema"))
3591 })?),
3592 None => None,
3593 });
3594 templates.push(AggAccum::fresh(aggregate.function, float));
3595 }
3596 let mut fold = GroupFold::default();
3597 for row in 0..batch.num_rows() {
3598 let key = group_key(&batch, &key_indexes, row)?;
3599 let entry = fold.entry(key, row, &templates);
3600 for (index, aggregate) in aggregates.iter().enumerate() {
3601 match aggregate.function {
3602 AggregateFunction::Count => {
3603 let counted = match value_indexes[index] {
3604 Some(column) => !batch.column(column).is_null(row),
3605 None => true,
3606 };
3607 if counted {
3608 let AggAccum::Count(count) = &mut entry.accums[index] else {
3609 unreachable!("count template");
3610 };
3611 *count += 1;
3612 }
3613 }
3614 _ => {
3615 let cell = match value_indexes[index] {
3616 Some(column) => numeric_cell(batch.column(column).as_ref(), row)?,
3617 None => None,
3618 };
3619 entry.accums[index].fold_value(cell);
3620 }
3621 }
3622 }
3623 }
3624 let mut columns = take_group_keys(&batch, &key_indexes, &fold)?;
3626 let mut fields: Vec<Field> = key_indexes
3627 .iter()
3628 .map(|index| schema.field(*index).clone())
3629 .collect();
3630 for (index, aggregate) in aggregates.iter().enumerate() {
3631 match aggregate.function {
3632 AggregateFunction::Avg => {
3633 let mut sums = Vec::with_capacity(fold.order.len());
3634 let mut counts = Vec::with_capacity(fold.order.len());
3635 for key in &fold.order {
3636 let AggAccum::Avg { sum, count } = &fold.groups[key].accums[index] else {
3637 unreachable!("avg template");
3638 };
3639 sums.push(Some(*sum));
3640 counts.push(Some(*count));
3641 }
3642 fields.push(Field::new(
3643 format!("__partial_{index}_sum"),
3644 DataType::Float64,
3645 true,
3646 ));
3647 columns.push(Arc::new(Float64Array::from(sums)));
3648 fields.push(Field::new(
3649 format!("__partial_{index}_count"),
3650 DataType::Int64,
3651 true,
3652 ));
3653 columns.push(Arc::new(Int64Array::from(counts)));
3654 }
3655 AggregateFunction::Count => {
3656 fields.push(Field::new(
3657 format!("__partial_{index}"),
3658 DataType::Int64,
3659 true,
3660 ));
3661 columns.push(Arc::new(Int64Array::from(emit_int_accum(&fold, index)?)));
3662 }
3663 _ => match &templates[index] {
3664 AggAccum::SumI(..) | AggAccum::MinI(..) | AggAccum::MaxI(..) => {
3665 fields.push(Field::new(
3666 format!("__partial_{index}"),
3667 DataType::Int64,
3668 true,
3669 ));
3670 columns.push(Arc::new(Int64Array::from(emit_int_accum(&fold, index)?)));
3671 }
3672 AggAccum::SumF(..) | AggAccum::MinF(..) | AggAccum::MaxF(..) => {
3673 fields.push(Field::new(
3674 format!("__partial_{index}"),
3675 DataType::Float64,
3676 true,
3677 ));
3678 columns.push(Arc::new(Float64Array::from(emit_float_accum(
3679 &fold, index,
3680 )?)));
3681 }
3682 other => {
3683 return Err(DistributedError::InvalidPlan(format!(
3684 "internal: unexpected accumulator {other:?}"
3685 )))
3686 }
3687 },
3688 }
3689 }
3690 Ok(RecordBatch::try_new(Schema::new(fields).into(), columns)?)
3691}
3692
3693fn final_aggregate_batches(
3696 batches: &[RecordBatch],
3697 group_by: &[String],
3698 aggregates: &[AggregateExpr],
3699) -> DistributedResult<RecordBatch> {
3700 let Some(batch) = concat_all(batches)? else {
3701 return Err(DistributedError::InvalidPlan(
3702 "aggregate input has no batches".to_owned(),
3703 ));
3704 };
3705 let schema = batch.schema();
3706 let key_indexes = resolve_columns(&schema, group_by)?;
3707 let mut partial_indexes: Vec<(usize, Option<usize>)> = Vec::with_capacity(aggregates.len());
3709 let mut templates = Vec::with_capacity(aggregates.len());
3710 for (index, aggregate) in aggregates.iter().enumerate() {
3711 let value_name = if aggregate.function == AggregateFunction::Avg {
3712 format!("__partial_{index}_sum")
3713 } else {
3714 format!("__partial_{index}")
3715 };
3716 let value_index = schema.index_of(&value_name).map_err(|_| {
3717 DistributedError::InvalidPlan(format!(
3718 "partial aggregate column `{value_name}` not in schema"
3719 ))
3720 })?;
3721 let float = match schema.field(value_index).data_type() {
3722 DataType::Int64 => false,
3723 DataType::Float64 => true,
3724 other => {
3725 return Err(DistributedError::Unsupported(format!(
3726 "aggregate partial of type {other} is not supported in this wave"
3727 )))
3728 }
3729 };
3730 templates.push(AggAccum::fresh(aggregate.function, float));
3731 let count_index = if aggregate.function == AggregateFunction::Avg {
3732 let count_name = format!("__partial_{index}_count");
3733 Some(schema.index_of(&count_name).map_err(|_| {
3734 DistributedError::InvalidPlan(format!(
3735 "partial aggregate column `{count_name}` not in schema"
3736 ))
3737 })?)
3738 } else {
3739 None
3740 };
3741 partial_indexes.push((value_index, count_index));
3742 }
3743 let mut fold = GroupFold::default();
3744 for row in 0..batch.num_rows() {
3745 let key = group_key(&batch, &key_indexes, row)?;
3746 let entry = fold.entry(key, row, &templates);
3747 for (index, aggregate) in aggregates.iter().enumerate() {
3748 let (value_index, count_index) = partial_indexes[index];
3749 match aggregate.function {
3750 AggregateFunction::Count => {
3751 if let Some(AggValue::I64(value)) =
3752 numeric_cell(batch.column(value_index).as_ref(), row)?
3753 {
3754 let AggAccum::Count(count) = &mut entry.accums[index] else {
3755 unreachable!("count template");
3756 };
3757 *count += value;
3758 }
3759 }
3760 AggregateFunction::Avg => {
3761 let sum = numeric_cell(batch.column(value_index).as_ref(), row)?;
3762 let count = match count_index {
3763 Some(count_index) => numeric_cell(batch.column(count_index).as_ref(), row)?,
3764 None => None,
3765 };
3766 let AggAccum::Avg {
3767 sum: total,
3768 count: rows,
3769 } = &mut entry.accums[index]
3770 else {
3771 unreachable!("avg template");
3772 };
3773 match sum {
3774 Some(AggValue::F64(value)) => *total += value,
3775 Some(AggValue::I64(value)) => *total += value as f64,
3776 None => {}
3777 }
3778 if let Some(AggValue::I64(value)) = count {
3779 *rows += value;
3780 }
3781 }
3782 _ => {
3783 let cell = numeric_cell(batch.column(value_index).as_ref(), row)?;
3784 entry.accums[index].fold_value(cell);
3785 }
3786 }
3787 }
3788 }
3789 if fold.order.is_empty() && group_by.is_empty() {
3791 fold.entry(String::new(), 0, &templates);
3792 }
3793 let mut columns = take_group_keys(&batch, &key_indexes, &fold)?;
3795 let mut fields: Vec<Field> = key_indexes
3796 .iter()
3797 .map(|index| schema.field(*index).clone())
3798 .collect();
3799 for (index, aggregate) in aggregates.iter().enumerate() {
3800 let name = aggregate_output_name(aggregate);
3801 match &templates[index] {
3802 AggAccum::Count(_) | AggAccum::SumI(..) | AggAccum::MinI(..) | AggAccum::MaxI(..) => {
3803 fields.push(Field::new(name, DataType::Int64, true));
3804 columns.push(Arc::new(Int64Array::from(emit_int_accum(&fold, index)?)));
3805 }
3806 AggAccum::SumF(..) | AggAccum::MinF(..) | AggAccum::MaxF(..) => {
3807 fields.push(Field::new(name, DataType::Float64, true));
3808 columns.push(Arc::new(Float64Array::from(emit_float_accum(
3809 &fold, index,
3810 )?)));
3811 }
3812 AggAccum::Avg { .. } => {
3813 let values: Vec<Option<f64>> = fold
3814 .order
3815 .iter()
3816 .map(|key| match &fold.groups[key].accums[index] {
3817 AggAccum::Avg { sum, count } => {
3818 (*count > 0).then_some(*sum / *count as f64)
3819 }
3820 _ => unreachable!("avg template"),
3821 })
3822 .collect();
3823 fields.push(Field::new(name, DataType::Float64, true));
3824 columns.push(Arc::new(Float64Array::from(values)));
3825 }
3826 }
3827 }
3828 Ok(RecordBatch::try_new(Schema::new(fields).into(), columns)?)
3829}
3830
3831#[derive(Debug)]
3838pub struct ResourceLedger {
3839 state: Mutex<LedgerState>,
3840 max_fragments: usize,
3841 max_bytes: u64,
3842}
3843
3844#[derive(Default, Debug)]
3845struct LedgerState {
3846 fragments: usize,
3847 bytes: u64,
3848}
3849
3850#[derive(Debug)]
3852pub struct ResourcePermit {
3853 ledger: Arc<ResourceLedger>,
3854 bytes: u64,
3855}
3856
3857impl Drop for ResourcePermit {
3858 fn drop(&mut self) {
3859 let mut state = self.ledger.state.lock();
3860 state.fragments = state.fragments.saturating_sub(1);
3861 state.bytes = state.bytes.saturating_sub(self.bytes);
3862 }
3863}
3864
3865impl ResourceLedger {
3866 pub fn new(max_fragments: usize, max_bytes: u64) -> Self {
3869 Self {
3870 state: Mutex::new(LedgerState::default()),
3871 max_fragments,
3872 max_bytes,
3873 }
3874 }
3875
3876 pub fn reserve(self: &Arc<Self>, fragment: &PlanFragment) -> DistributedResult<ResourcePermit> {
3879 let mut state = self.state.lock();
3880 if state.fragments >= self.max_fragments {
3881 return Err(DistributedError::Reservation {
3882 fragment_id: fragment.fragment_id,
3883 reason: format!("fragment concurrency limit {} reached", self.max_fragments),
3884 });
3885 }
3886 if state.bytes.saturating_add(fragment.estimated_bytes) > self.max_bytes {
3887 return Err(DistributedError::Reservation {
3888 fragment_id: fragment.fragment_id,
3889 reason: format!(
3890 "estimated bytes {} exceed the {} byte budget",
3891 state.bytes.saturating_add(fragment.estimated_bytes),
3892 self.max_bytes
3893 ),
3894 });
3895 }
3896 state.fragments += 1;
3897 state.bytes = state.bytes.saturating_add(fragment.estimated_bytes);
3898 Ok(ResourcePermit {
3899 ledger: Arc::clone(self),
3900 bytes: fragment.estimated_bytes,
3901 })
3902 }
3903
3904 pub fn reserved_fragments(&self) -> usize {
3906 self.state.lock().fragments
3907 }
3908
3909 pub fn reserved_bytes(&self) -> u64 {
3911 self.state.lock().bytes
3912 }
3913}
3914
3915#[derive(Default)]
3919pub struct LeaseLedger {
3920 leases: Mutex<HashMap<TabletId, Instant>>,
3921}
3922
3923impl LeaseLedger {
3924 pub fn renew(&self, worker: TabletId, expiry: Instant) {
3926 self.leases.lock().insert(worker, expiry);
3927 }
3928
3929 pub fn expiry(&self, worker: &TabletId) -> Option<Instant> {
3931 self.leases.lock().get(worker).copied()
3932 }
3933
3934 pub fn sweep(&self, now: Instant) -> Vec<TabletId> {
3937 let mut leases = self.leases.lock();
3938 let expired: Vec<TabletId> = leases
3939 .iter()
3940 .filter(|(_, expiry)| **expiry <= now)
3941 .map(|(worker, _)| *worker)
3942 .collect();
3943 for worker in &expired {
3944 leases.remove(worker);
3945 }
3946 expired
3947 }
3948}
3949
3950struct InFlight {
3952 worker: Option<TabletId>,
3953 control: ExecutionControl,
3954 _permit: ResourcePermit,
3955}
3956
3957struct ExecutionState {
3959 query_id: QueryId,
3960 in_flight: Mutex<HashMap<FragmentId, InFlight>>,
3961}
3962
3963impl ExecutionState {
3964 fn new(query_id: QueryId) -> Self {
3965 Self {
3966 query_id,
3967 in_flight: Mutex::new(HashMap::new()),
3968 }
3969 }
3970}
3971
3972struct ProducerInput {
3974 fragment_id: FragmentId,
3975 tablet: Option<TabletId>,
3976 frames: Vec<BatchFrame>,
3977}
3978
3979enum RootData {
3983 Streams(Vec<ProducerInput>),
3984 Batches(Vec<RecordBatch>),
3985}
3986
3987impl RootData {
3988 fn flatten(&self) -> Vec<RecordBatch> {
3990 match self {
3991 Self::Streams(inputs) => inputs
3992 .iter()
3993 .flat_map(|input| input.frames.iter().map(|frame| frame.batch.clone()))
3994 .collect(),
3995 Self::Batches(batches) => batches.clone(),
3996 }
3997 }
3998}
3999
4000pub struct Coordinator {
4007 transport: Arc<dyn FragmentTransport>,
4008 registry: Arc<SqlQueryRegistry>,
4009 resources: Arc<ResourceLedger>,
4010 leases: LeaseLedger,
4011 lease_ttl: Duration,
4012 executions: Mutex<HashMap<QueryId, Arc<ExecutionState>>>,
4013}
4014
4015impl Coordinator {
4016 pub fn new(transport: Arc<dyn FragmentTransport>, registry: Arc<SqlQueryRegistry>) -> Self {
4019 Self::with_limits(
4020 transport,
4021 registry,
4022 1_024,
4023 16 * 1024 * 1024 * 1024,
4024 Duration::from_secs(30),
4025 )
4026 }
4027
4028 pub fn with_limits(
4030 transport: Arc<dyn FragmentTransport>,
4031 registry: Arc<SqlQueryRegistry>,
4032 max_fragments: usize,
4033 max_bytes: u64,
4034 lease_ttl: Duration,
4035 ) -> Self {
4036 Self {
4037 transport,
4038 registry,
4039 resources: Arc::new(ResourceLedger::new(max_fragments, max_bytes)),
4040 leases: LeaseLedger::default(),
4041 lease_ttl,
4042 executions: Mutex::new(HashMap::new()),
4043 }
4044 }
4045
4046 pub fn resources(&self) -> &Arc<ResourceLedger> {
4048 &self.resources
4049 }
4050
4051 pub async fn execute(&self, plan: &DistributedPlan) -> DistributedResult<Vec<RecordBatch>> {
4065 self.execute_with_authorization(plan, &[]).await
4066 }
4067
4068 pub async fn execute_with_authorization(
4071 &self,
4072 plan: &DistributedPlan,
4073 authorization_context: &[u8],
4074 ) -> DistributedResult<Vec<RecordBatch>> {
4075 if authorization_context.len() > MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES {
4076 return Err(DistributedError::InvalidPlan(format!(
4077 "fragment authorization context is {} bytes; limit is {}",
4078 authorization_context.len(),
4079 MAX_FRAGMENT_AUTHORIZATION_CONTEXT_BYTES
4080 )));
4081 }
4082 let registry_id = to_registry_query_id(&plan.query_id)?;
4083 let registered = self
4084 .registry
4085 .register(SqlQueryOptions {
4086 query_id: Some(registry_id),
4087 ..Default::default()
4088 })
4089 .map_err(|error| {
4090 DistributedError::InvalidPlan(format!("query registration failed: {error}"))
4091 })?;
4092 let result = self
4093 .execute_registered(plan, ®istered, authorization_context.into())
4094 .await;
4095 match &result {
4096 Ok(_) => {
4097 let _ = registered.try_complete();
4098 }
4099 Err(_) => registered.fail(),
4100 }
4101 result
4102 }
4103
4104 pub fn cancel_query(&self, query_id: &QueryId) -> DistributedResult<bool> {
4110 let outcome = self.registry.cancel(to_registry_query_id(query_id)?);
4111 let state = self.executions.lock().get(query_id).cloned();
4112 if let Some(state) = state {
4113 let in_flight = state.in_flight.lock();
4114 for (fragment_id, inflight) in in_flight.iter() {
4115 inflight.control.cancel(CancellationReason::ClientRequest);
4116 let _ = self.transport.cancel_fragment(*query_id, *fragment_id);
4117 }
4118 }
4119 Ok(matches!(
4120 outcome,
4121 CancelOutcome::Accepted | CancelOutcome::AlreadyCancelling
4122 ))
4123 }
4124
4125 pub fn sweep_expired_leases(&self, now: Instant) -> usize {
4132 let expired = self.leases.sweep(now);
4133 if expired.is_empty() {
4134 return 0;
4135 }
4136 let states: Vec<Arc<ExecutionState>> = self.executions.lock().values().cloned().collect();
4137 let mut cleaned = 0;
4138 for state in states {
4139 let victims: Vec<FragmentId> = {
4140 let in_flight = state.in_flight.lock();
4141 in_flight
4142 .iter()
4143 .filter(|(_, inflight)| {
4144 inflight
4145 .worker
4146 .is_some_and(|worker| expired.contains(&worker))
4147 })
4148 .map(|(fragment_id, _)| *fragment_id)
4149 .collect()
4150 };
4151 for fragment_id in victims {
4152 let inflight = state.in_flight.lock().remove(&fragment_id);
4153 if let Some(inflight) = inflight {
4154 inflight.control.cancel(CancellationReason::ServerShutdown);
4155 drop(inflight);
4156 let _ = self.transport.cancel_fragment(state.query_id, fragment_id);
4157 cleaned += 1;
4158 }
4159 }
4160 }
4161 cleaned
4162 }
4163
4164 async fn execute_registered(
4165 &self,
4166 plan: &DistributedPlan,
4167 registered: &RegisteredSqlQuery,
4168 authorization_context: Arc<[u8]>,
4169 ) -> DistributedResult<Vec<RecordBatch>> {
4170 validate_plan_shape(plan)?;
4171 let root = plan
4172 .root_fragment_id()
4173 .ok_or_else(|| DistributedError::InvalidPlan("plan has no root fragment".to_owned()))?;
4174 let state = Arc::new(ExecutionState::new(plan.query_id));
4175 self.executions
4176 .lock()
4177 .insert(plan.query_id, Arc::clone(&state));
4178 let result = self
4179 .run_plan(plan, root, registered, &state, authorization_context)
4180 .await;
4181 self.executions.lock().remove(&plan.query_id);
4182 result
4183 }
4184
4185 async fn run_plan(
4186 &self,
4187 plan: &DistributedPlan,
4188 root: FragmentId,
4189 registered: &RegisteredSqlQuery,
4190 state: &Arc<ExecutionState>,
4191 authorization_context: Arc<[u8]>,
4192 ) -> DistributedResult<Vec<RecordBatch>> {
4193 let parent = registered.control().clone();
4194 let layers = fragment_layers(plan, root)?;
4195 let mut outputs: HashMap<FragmentId, Vec<BatchFrame>> = HashMap::new();
4196 for layer in &layers {
4197 checkpoint(&parent)?;
4198 let now = Instant::now();
4199 for &fragment_id in layer {
4200 if let FragmentAssignment::Tablet(tablet) =
4201 plan.fragments[fragment_id as usize].assignment
4202 {
4203 self.leases.renew(tablet, now + self.lease_ttl);
4204 }
4205 }
4206 let mut tasks = FuturesUnordered::new();
4207 let mut spawn_error: Option<DistributedError> = None;
4208 for &fragment_id in layer {
4209 let fragment = &plan.fragments[fragment_id as usize];
4210 let reserved = match self.resources.reserve(fragment) {
4211 Ok(permit) => permit,
4212 Err(error) => {
4213 spawn_error = Some(error);
4214 break;
4215 }
4216 };
4217 let inputs = match build_inputs(plan, fragment_id, &outputs) {
4218 Ok(inputs) => inputs,
4219 Err(error) => {
4220 spawn_error = Some(error);
4221 break;
4222 }
4223 };
4224 let control = parent.child_with_deadline(None);
4225 let worker = match fragment.assignment {
4226 FragmentAssignment::Tablet(tablet) => Some(tablet),
4227 FragmentAssignment::Coordinator => None,
4228 };
4229 state.in_flight.lock().insert(
4230 fragment_id,
4231 InFlight {
4232 worker,
4233 control: control.clone(),
4234 _permit: reserved,
4235 },
4236 );
4237 let fragment = fragment.clone();
4238 let fragment_control = FragmentControl {
4239 control,
4240 max_spill_bytes: fragment.max_spill_bytes,
4241 authorization_context: Arc::clone(&authorization_context),
4242 };
4243 let transport = Arc::clone(&self.transport);
4244 tasks.push(async move {
4245 let worker = worker_label(&fragment);
4246 let drain_control = fragment_control.control.clone();
4247 let result = async {
4248 let stream = transport
4249 .execute_fragment(plan.query_id, &fragment, inputs, fragment_control)
4250 .await?;
4251 drain_stream(stream, &drain_control).await
4252 }
4253 .await;
4254 (fragment.fragment_id, worker, result)
4255 });
4256 }
4257 if let Some(error) = spawn_error {
4258 self.abort(plan, state);
4259 while let Some((fragment_id, _, _)) = tasks.next().await {
4260 state.in_flight.lock().remove(&fragment_id);
4261 }
4262 return Err(error);
4263 }
4264 let mut failure: Option<DistributedError> = None;
4265 while let Some((fragment_id, worker, result)) = tasks.next().await {
4266 state.in_flight.lock().remove(&fragment_id);
4267 match result {
4268 Ok(frames) => {
4269 outputs.insert(fragment_id, frames);
4270 }
4271 Err(error) => {
4272 if failure.is_none() {
4273 failure = Some(match error {
4274 DistributedError::Cancelled(reason) => {
4275 DistributedError::Cancelled(reason)
4276 }
4277 other => DistributedError::FragmentExecution {
4278 fragment_id,
4279 worker,
4280 message: other.to_string(),
4281 },
4282 });
4283 }
4284 }
4285 }
4286 }
4287 if let Some(error) = failure {
4288 self.abort(plan, state);
4289 return Err(error);
4290 }
4291 }
4292 checkpoint(&parent)?;
4293 self.run_root(plan, root, &outputs, &parent, authorization_context)
4294 .await
4295 }
4296
4297 fn abort(&self, plan: &DistributedPlan, state: &Arc<ExecutionState>) {
4300 {
4301 let in_flight = state.in_flight.lock();
4302 for inflight in in_flight.values() {
4303 inflight.control.cancel(CancellationReason::ClientRequest);
4304 }
4305 }
4306 for fragment in &plan.fragments {
4307 let _ = self
4308 .transport
4309 .cancel_fragment(plan.query_id, fragment.fragment_id);
4310 }
4311 }
4312
4313 async fn run_root(
4315 &self,
4316 plan: &DistributedPlan,
4317 root: FragmentId,
4318 outputs: &HashMap<FragmentId, Vec<BatchFrame>>,
4319 parent: &ExecutionControl,
4320 authorization_context: Arc<[u8]>,
4321 ) -> DistributedResult<Vec<RecordBatch>> {
4322 let fragment = &plan.fragments[root as usize];
4323 let mut inputs: Vec<ProducerInput> = Vec::new();
4324 for operator in &fragment.operators {
4325 if let FragmentOperator::RemoteExchangeSource { exchange } = operator {
4326 let edge = plan.exchanges.get(*exchange as usize).ok_or_else(|| {
4327 DistributedError::InvalidPlan(format!("unknown exchange {exchange}"))
4328 })?;
4329 let producer = plan.fragments.get(edge.producer as usize).ok_or_else(|| {
4330 DistributedError::InvalidPlan(format!(
4331 "unknown producer fragment {}",
4332 edge.producer
4333 ))
4334 })?;
4335 let tablet = match producer.assignment {
4336 FragmentAssignment::Tablet(tablet) => Some(tablet),
4337 FragmentAssignment::Coordinator => None,
4338 };
4339 let frames = outputs.get(&edge.producer).cloned().ok_or_else(|| {
4340 DistributedError::InvalidPlan(format!(
4341 "missing output of producer fragment {}",
4342 edge.producer
4343 ))
4344 })?;
4345 let frames = route_frames(plan, edge, frames)?;
4346 inputs.push(ProducerInput {
4347 fragment_id: edge.producer,
4348 tablet,
4349 frames,
4350 });
4351 }
4352 }
4353 let mut current = RootData::Streams(inputs);
4354 for operator in &fragment.operators {
4355 checkpoint(parent)?;
4356 match operator {
4357 FragmentOperator::RemoteExchangeSource { .. } => {}
4358 FragmentOperator::FinalAggregate {
4359 group_by,
4360 aggregates,
4361 } => {
4362 let batches = current.flatten();
4363 current = RootData::Batches(vec![final_aggregate_batches(
4364 &batches, group_by, aggregates,
4365 )?]);
4366 }
4367 FragmentOperator::MergeSort { keys, limit } => {
4368 current = RootData::Batches(match ¤t {
4369 RootData::Streams(_) => {
4370 let streams = per_stream_batches(¤t);
4371 merge_sorted_streams(&streams, keys, *limit)?
4372 }
4373 RootData::Batches(batches) => sort_batches_local(batches, keys, *limit)?,
4374 });
4375 }
4376 FragmentOperator::DistributedTopK { k, score } => {
4377 current = RootData::Batches(
4378 self.coordinator_top_k(
4379 plan,
4380 ¤t,
4381 *k,
4382 score,
4383 parent,
4384 &authorization_context,
4385 )
4386 .await?,
4387 );
4388 }
4389 FragmentOperator::DistributedLimit { limit } => {
4390 let batches = current.flatten();
4391 current = RootData::Batches(limit_batches(&batches, *limit));
4392 }
4393 other => {
4394 return Err(DistributedError::Unsupported(format!(
4395 "root fragment operator {other:?} is not coordinator-executable in this wave"
4396 )))
4397 }
4398 }
4399 }
4400 Ok(current.flatten())
4401 }
4402
4403 async fn coordinator_top_k(
4408 &self,
4409 plan: &DistributedPlan,
4410 data: &RootData,
4411 k: usize,
4412 score: &SortKey,
4413 control: &ExecutionControl,
4414 authorization_context: &[u8],
4415 ) -> DistributedResult<Vec<RecordBatch>> {
4416 let synthetic;
4419 let inputs: &[ProducerInput] = match data {
4420 RootData::Streams(inputs) => inputs,
4421 RootData::Batches(batches) => {
4422 synthetic = ProducerInput {
4423 fragment_id: u32::MAX,
4424 tablet: Some(TabletId::ZERO),
4425 frames: batches.iter().cloned().map(BatchFrame::data).collect(),
4426 };
4427 std::slice::from_ref(&synthetic)
4428 }
4429 };
4430 let mut all_batches: Vec<RecordBatch> = Vec::new();
4431 let mut batch_tablet: Vec<TabletId> = Vec::new();
4432 let mut shards: BTreeMap<TabletId, TabletTopK> = BTreeMap::new();
4433 let mut fragment_of: HashMap<TabletId, FragmentId> = HashMap::new();
4434 for input in inputs {
4435 let tablet = input.tablet.ok_or_else(|| {
4436 DistributedError::InvalidPlan(
4437 "distributed top-k inputs must be tablet fragments".to_owned(),
4438 )
4439 })?;
4440 fragment_of.insert(tablet, input.fragment_id);
4441 let mut rows = Vec::new();
4442 let mut bound = None;
4443 for frame in &input.frames {
4444 let batch = &frame.batch;
4445 if batch.num_rows() > 0 {
4446 let score_index = batch.schema().index_of(&score.column).map_err(|_| {
4447 DistributedError::InvalidPlan(format!(
4448 "top-k score column `{}` not in schema",
4449 score.column
4450 ))
4451 })?;
4452 let score_array = batch.column(score_index).clone();
4453 let row_id_array = row_ids(batch)?;
4454 for row in 0..batch.num_rows() {
4455 rows.push(TopKCandidate {
4456 score: score_key(score_array.as_ref(), row)?,
4457 tablet,
4458 row_id: RowId(row_id_array.value(row)),
4459 });
4460 }
4461 }
4462 batch_tablet.push(tablet);
4463 all_batches.push(batch.clone());
4464 match frame.score_bound {
4465 ScoreBound::AtMost(next) => bound = Some(next),
4466 ScoreBound::Exhausted => bound = None,
4467 ScoreBound::Unknown => {}
4470 }
4471 }
4472 shards.insert(
4473 tablet,
4474 TabletTopK {
4475 tablet,
4476 rows,
4477 unseen_bound: bound,
4478 },
4479 );
4480 }
4481 let winners = loop {
4482 let ordered: Vec<TabletTopK> = shards.values().cloned().collect();
4483 let merge = merge_top_k(&ordered, k);
4484 if merge.refill.is_empty() {
4485 break merge.winners;
4486 }
4487 for tablet in merge.refill {
4488 let fragment_id = fragment_of[&tablet];
4489 let already = shards[&tablet].rows.len();
4490 let refill = self
4491 .transport
4492 .refill_top_k(
4493 plan.query_id,
4494 &plan.fragments[fragment_id as usize],
4495 already,
4496 k,
4497 FragmentControl {
4498 control: control.child_with_deadline(None),
4499 max_spill_bytes: plan.fragments[fragment_id as usize].max_spill_bytes,
4500 authorization_context: authorization_context.into(),
4501 },
4502 )
4503 .await?;
4504 let entry = shards.get_mut(&tablet).expect("shard exists");
4505 if refill.rows.is_empty() && refill.unseen_bound == entry.unseen_bound {
4506 return Err(DistributedError::InvalidPlan(format!(
4507 "top-k refill for fragment {fragment_id} made no progress"
4508 )));
4509 }
4510 batch_tablet.push(tablet);
4511 all_batches.push(refill.payload);
4512 entry.rows.extend(refill.rows);
4513 entry.unseen_bound = refill.unseen_bound;
4514 }
4515 };
4516 if winners.is_empty() {
4517 return Ok(Vec::new());
4518 }
4519 let mut locations: HashMap<(TabletId, u64), (usize, usize)> = HashMap::new();
4521 for (batch_index, batch) in all_batches.iter().enumerate() {
4522 if batch.num_rows() == 0 {
4523 continue;
4524 }
4525 let row_id_array = row_ids(batch)?;
4526 let tablet = batch_tablet[batch_index];
4527 for row in 0..batch.num_rows() {
4528 locations.insert((tablet, row_id_array.value(row)), (batch_index, row));
4529 }
4530 }
4531 let mut order = Vec::with_capacity(winners.len());
4532 for winner in &winners {
4533 let location = locations
4534 .get(&(winner.tablet, winner.row_id.0))
4535 .ok_or_else(|| {
4536 DistributedError::InvalidPlan(format!(
4537 "top-k winner {:?} has no payload row",
4538 winner.row_id
4539 ))
4540 })?;
4541 order.push(*location);
4542 }
4543 let merged = emit_interleaved(&all_batches, &order)?;
4544 let schema = merged.first().map(|batch| batch.schema()).ok_or_else(|| {
4546 DistributedError::InvalidPlan("top-k merge produced no output".to_owned())
4547 })?;
4548 let keep: Vec<usize> = schema
4549 .fields()
4550 .iter()
4551 .enumerate()
4552 .filter(|(_, field)| field.name() != TOPK_ROWID_COLUMN)
4553 .map(|(index, _)| index)
4554 .collect();
4555 if keep.len() == schema.fields().len() {
4556 return Ok(merged);
4557 }
4558 merged
4559 .iter()
4560 .map(|batch| Ok(batch.project(&keep)?))
4561 .collect()
4562 }
4563}
4564
4565fn per_stream_batches(data: &RootData) -> Vec<RecordBatch> {
4567 match data {
4568 RootData::Streams(inputs) => inputs
4569 .iter()
4570 .filter_map(|input| {
4571 let batches: Vec<RecordBatch> = input
4572 .frames
4573 .iter()
4574 .map(|frame| frame.batch.clone())
4575 .collect();
4576 concat_all(&batches).ok().flatten()
4577 })
4578 .collect(),
4579 RootData::Batches(batches) => batches.clone(),
4580 }
4581}
4582
4583fn worker_label(fragment: &PlanFragment) -> String {
4585 match fragment.assignment {
4586 FragmentAssignment::Tablet(tablet) => format!("tablet {tablet}"),
4587 FragmentAssignment::Coordinator => "coordinator".to_owned(),
4588 }
4589}
4590
4591fn validate_plan_shape(plan: &DistributedPlan) -> DistributedResult<()> {
4593 if plan.fragments.is_empty() {
4594 return Err(DistributedError::InvalidPlan(
4595 "plan has no fragments".to_owned(),
4596 ));
4597 }
4598 for (index, fragment) in plan.fragments.iter().enumerate() {
4599 if fragment.fragment_id as usize != index {
4600 return Err(DistributedError::InvalidPlan(
4601 "fragment ids must equal their vector index".to_owned(),
4602 ));
4603 }
4604 }
4605 for edge in &plan.exchanges {
4606 if edge.producer as usize >= plan.fragments.len()
4607 || edge.consumer as usize >= plan.fragments.len()
4608 {
4609 return Err(DistributedError::InvalidPlan(format!(
4610 "exchange {} references a missing fragment",
4611 edge.exchange_id
4612 )));
4613 }
4614 }
4615 let roots = plan
4616 .fragments
4617 .iter()
4618 .filter(|fragment| {
4619 !plan
4620 .exchanges
4621 .iter()
4622 .any(|edge| edge.producer == fragment.fragment_id)
4623 })
4624 .count();
4625 if roots != 1 {
4626 return Err(DistributedError::InvalidPlan(format!(
4627 "plan must have exactly one root fragment, found {roots}"
4628 )));
4629 }
4630 if let Some(root) = plan.root_fragment_id() {
4631 for fragment in &plan.fragments {
4632 if fragment.fragment_id != root
4633 && fragment.assignment == FragmentAssignment::Coordinator
4634 {
4635 return Err(DistributedError::InvalidPlan(
4636 "only the root fragment may be coordinator-assigned".to_owned(),
4637 ));
4638 }
4639 }
4640 }
4641 Ok(())
4642}
4643
4644fn fragment_layers(
4646 plan: &DistributedPlan,
4647 root: FragmentId,
4648) -> DistributedResult<Vec<Vec<FragmentId>>> {
4649 fn depth(
4650 plan: &DistributedPlan,
4651 fragment: FragmentId,
4652 memo: &mut [Option<usize>],
4653 visiting: &mut [bool],
4654 ) -> DistributedResult<usize> {
4655 if let Some(depth) = memo[fragment as usize] {
4656 return Ok(depth);
4657 }
4658 if visiting[fragment as usize] {
4659 return Err(DistributedError::InvalidPlan(
4660 "exchange graph has a cycle".to_owned(),
4661 ));
4662 }
4663 visiting[fragment as usize] = true;
4664 let mut best = 0;
4665 for edge in plan.exchanges_into(fragment) {
4666 best = best.max(depth(plan, edge.producer, memo, visiting)? + 1);
4667 }
4668 visiting[fragment as usize] = false;
4669 memo[fragment as usize] = Some(best);
4670 Ok(best)
4671 }
4672
4673 let mut memo = vec![None; plan.fragments.len()];
4674 let mut visiting = vec![false; plan.fragments.len()];
4675 let mut layers: Vec<Vec<FragmentId>> = Vec::new();
4676 for fragment in &plan.fragments {
4677 if fragment.fragment_id == root {
4678 continue;
4679 }
4680 let depth = depth(plan, fragment.fragment_id, &mut memo, &mut visiting)?;
4681 if layers.len() <= depth {
4682 layers.resize(depth + 1, Vec::new());
4683 }
4684 layers[depth].push(fragment.fragment_id);
4685 }
4686 Ok(layers)
4687}
4688
4689fn build_inputs(
4692 plan: &DistributedPlan,
4693 consumer: FragmentId,
4694 outputs: &HashMap<FragmentId, Vec<BatchFrame>>,
4695) -> DistributedResult<Vec<FragmentStream>> {
4696 let mut inputs = Vec::new();
4697 for edge in plan.exchanges_into(consumer) {
4698 let frames = outputs.get(&edge.producer).cloned().ok_or_else(|| {
4699 DistributedError::InvalidPlan(format!(
4700 "missing output of producer fragment {}",
4701 edge.producer
4702 ))
4703 })?;
4704 let frames = route_frames(plan, edge, frames)?;
4705 inputs.push(Box::pin(stream::iter(frames.into_iter().map(Ok))) as FragmentStream);
4706 }
4707 Ok(inputs)
4708}
4709
4710fn route_frames(
4713 plan: &DistributedPlan,
4714 edge: &ExchangeDescriptor,
4715 frames: Vec<BatchFrame>,
4716) -> DistributedResult<Vec<BatchFrame>> {
4717 match &edge.kind {
4718 ExchangeKind::Merge | ExchangeKind::Broadcast => Ok(frames),
4719 ExchangeKind::HashRepartition { keys } => {
4720 let mut siblings: Vec<&ExchangeDescriptor> = plan
4721 .exchanges
4722 .iter()
4723 .filter(|candidate| {
4724 candidate.producer == edge.producer
4725 && matches!(candidate.kind, ExchangeKind::HashRepartition { .. })
4726 })
4727 .collect();
4728 siblings.sort_by_key(|candidate| candidate.consumer);
4729 let width = siblings.len();
4730 let index = siblings
4731 .iter()
4732 .position(|candidate| candidate.consumer == edge.consumer)
4733 .ok_or_else(|| {
4734 DistributedError::InvalidPlan(format!(
4735 "exchange {} is missing from its repartition boundary",
4736 edge.exchange_id
4737 ))
4738 })?;
4739 repartition_frames(&frames, keys, width, index)
4740 }
4741 }
4742}
4743
4744fn to_registry_query_id(query_id: &QueryId) -> DistributedResult<crate::query_registry::QueryId> {
4746 query_id
4747 .to_hex()
4748 .parse()
4749 .map_err(|error| DistributedError::InvalidPlan(format!("query id bridge failed: {error}")))
4750}
4751
4752#[cfg(test)]
4757mod tests {
4758 use super::*;
4759 use arrow::datatypes::Field;
4760
4761 fn tablet(n: u8) -> TabletId {
4763 let mut bytes = [0u8; 16];
4764 bytes[15] = n;
4765 TabletId::from_bytes(bytes)
4766 }
4767
4768 struct SplitMix64(u64);
4770
4771 impl SplitMix64 {
4772 fn next(&mut self) -> u64 {
4773 self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
4774 let mut z = self.0;
4775 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
4776 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
4777 z ^ (z >> 31)
4778 }
4779
4780 fn below(&mut self, n: u64) -> u64 {
4781 self.next() % n
4782 }
4783 }
4784
4785 #[derive(Default)]
4786 struct StaticLocator {
4787 tablets: HashMap<String, Vec<TabletId>>,
4788 specs: HashMap<String, PartitionSpec>,
4789 }
4790
4791 impl TabletLocator for StaticLocator {
4792 fn tablets_for_table(&self, table: &str) -> DistributedResult<Vec<TabletId>> {
4793 self.tablets
4794 .get(table)
4795 .cloned()
4796 .ok_or_else(|| DistributedError::UnknownTable(table.to_owned()))
4797 }
4798
4799 fn partitioning(&self, table: &str) -> DistributedResult<PartitionSpec> {
4800 self.specs
4801 .get(table)
4802 .cloned()
4803 .ok_or_else(|| DistributedError::UnknownTable(table.to_owned()))
4804 }
4805 }
4806
4807 struct StaticMetadata {
4808 version: MetadataVersion,
4809 stats: HashMap<String, TableStats>,
4810 }
4811
4812 impl ClusterMetadata for StaticMetadata {
4813 fn metadata_version(&self) -> MetadataVersion {
4814 self.version
4815 }
4816
4817 fn table_stats(&self, table: &str) -> DistributedResult<TableStats> {
4818 self.stats
4819 .get(table)
4820 .copied()
4821 .ok_or_else(|| DistributedError::UnknownTable(table.to_owned()))
4822 }
4823 }
4824
4825 fn world(
4827 entries: &[(&str, &[TabletId], PartitionSpec, TableStats)],
4828 ) -> (StaticLocator, StaticMetadata) {
4829 let mut locator = StaticLocator::default();
4830 let mut stats = HashMap::new();
4831 for (table, tablets, spec, stat) in entries {
4832 locator
4833 .tablets
4834 .insert((*table).to_owned(), tablets.to_vec());
4835 locator.specs.insert((*table).to_owned(), spec.clone());
4836 stats.insert((*table).to_owned(), *stat);
4837 }
4838 (
4839 locator,
4840 StaticMetadata {
4841 version: MetadataVersion::new(7),
4842 stats,
4843 },
4844 )
4845 }
4846
4847 fn scan(table: &str) -> LogicalPlanLite {
4848 LogicalPlanLite::Scan {
4849 table: table.to_owned(),
4850 predicate: None,
4851 projection: Vec::new(),
4852 }
4853 }
4854
4855 fn desc(root: LogicalPlanLite) -> PlanDescription {
4856 PlanDescription {
4857 query_id: QueryId::new_random(),
4858 root,
4859 options: PlannerOptions::default(),
4860 }
4861 }
4862
4863 fn hash_spec(column: &str) -> PartitionSpec {
4864 PartitionSpec::Hash {
4865 columns: vec![column.to_owned()],
4866 buckets: 16,
4867 }
4868 }
4869
4870 fn stats(rows: u64, bytes: u64) -> TableStats {
4871 TableStats {
4872 row_count: rows,
4873 total_bytes: bytes,
4874 }
4875 }
4876
4877 fn i64_batch(columns: &[(&str, Vec<i64>)]) -> RecordBatch {
4878 let schema = Schema::new(
4879 columns
4880 .iter()
4881 .map(|(name, _)| Field::new(*name, DataType::Int64, true))
4882 .collect::<Vec<_>>(),
4883 );
4884 let arrays: Vec<ArrayRef> = columns
4885 .iter()
4886 .map(|(_, values)| Arc::new(Int64Array::from(values.clone())) as ArrayRef)
4887 .collect();
4888 RecordBatch::try_new(schema.into(), arrays).unwrap()
4889 }
4890
4891 fn score_batch(scores: Vec<u64>, row_ids: Vec<u64>, payloads: Vec<i64>) -> RecordBatch {
4892 let schema = Schema::new(vec![
4893 Field::new("score", DataType::UInt64, true),
4894 Field::new(TOPK_ROWID_COLUMN, DataType::UInt64, false),
4895 Field::new("payload", DataType::Int64, true),
4896 ]);
4897 RecordBatch::try_new(
4898 schema.into(),
4899 vec![
4900 Arc::new(UInt64Array::from(scores)),
4901 Arc::new(UInt64Array::from(row_ids)),
4902 Arc::new(Int64Array::from(payloads)),
4903 ],
4904 )
4905 .unwrap()
4906 }
4907
4908 fn collect_i64(batches: &[RecordBatch], column: &str) -> Vec<i64> {
4909 let mut values = Vec::new();
4910 for batch in batches {
4911 let index = batch.schema().index_of(column).unwrap();
4912 let array = batch
4913 .column(index)
4914 .as_any()
4915 .downcast_ref::<Int64Array>()
4916 .unwrap();
4917 for row in 0..batch.num_rows() {
4918 assert!(
4919 !array.is_null(row),
4920 "column {column} has an unexpected null"
4921 );
4922 values.push(array.value(row));
4923 }
4924 }
4925 values
4926 }
4927
4928 fn collect_u64(batches: &[RecordBatch], column: &str) -> Vec<u64> {
4929 let mut values = Vec::new();
4930 for batch in batches {
4931 let index = batch.schema().index_of(column).unwrap();
4932 let array = batch
4933 .column(index)
4934 .as_any()
4935 .downcast_ref::<UInt64Array>()
4936 .unwrap();
4937 for row in 0..batch.num_rows() {
4938 values.push(array.value(row));
4939 }
4940 }
4941 values
4942 }
4943
4944 fn collect_f64(batches: &[RecordBatch], column: &str) -> Vec<Option<f64>> {
4945 let mut values = Vec::new();
4946 for batch in batches {
4947 let index = batch.schema().index_of(column).unwrap();
4948 let array = batch
4949 .column(index)
4950 .as_any()
4951 .downcast_ref::<Float64Array>()
4952 .unwrap();
4953 for row in 0..batch.num_rows() {
4954 values.push((!array.is_null(row)).then(|| array.value(row)));
4955 }
4956 }
4957 values
4958 }
4959
4960 fn operators(plan: &DistributedPlan, fragment_id: FragmentId) -> &[FragmentOperator] {
4961 &plan.fragments[fragment_id as usize].operators
4962 }
4963
4964 #[test]
4969 fn scan_plan_shape_estimates_and_spill() {
4970 let tablets = [tablet(1), tablet(2), tablet(3)];
4971 let (locator, metadata) = world(&[(
4972 "t",
4973 &tablets,
4974 hash_spec("id"),
4975 stats(3_000, 3 * 1024 * 1024),
4976 )]);
4977 let description = desc(scan("t"));
4978 let plan = distribute(&description, &locator, &metadata).unwrap();
4979 assert_eq!(plan.query_id, description.query_id);
4980 assert_eq!(plan.metadata_version, MetadataVersion::new(7));
4981 assert_eq!(
4982 plan.fragments.len(),
4983 4,
4984 "3 scan fragments + coordinator gather"
4985 );
4986 for (index, id) in tablets.iter().enumerate() {
4987 let fragment = &plan.fragments[index];
4988 assert_eq!(fragment.assignment, FragmentAssignment::Tablet(*id));
4989 assert!(
4990 matches!(
4991 operators(&plan, fragment.fragment_id)[0],
4992 FragmentOperator::TabletScan { .. }
4993 ),
4994 "fragment {index} starts with a tablet scan"
4995 );
4996 assert!(
4997 matches!(
4998 operators(&plan, fragment.fragment_id).last().unwrap(),
4999 FragmentOperator::RemoteExchangeSink { .. }
5000 ),
5001 "fragment {index} ends with a sink"
5002 );
5003 assert_eq!(fragment.estimated_rows, 1_000);
5004 assert_eq!(fragment.estimated_bytes, 1024 * 1024);
5005 assert_eq!(
5006 fragment.max_spill_bytes,
5007 DEFAULT_MAX_SPILL_BYTES_PER_FRAGMENT
5008 );
5009 }
5010 let root = plan.root_fragment_id().unwrap();
5011 assert_eq!(root, 3);
5012 assert_eq!(
5013 plan.fragments[3].assignment,
5014 FragmentAssignment::Coordinator
5015 );
5016 assert_eq!(plan.exchanges.len(), 3);
5017 for edge in &plan.exchanges {
5018 assert_eq!(edge.kind, ExchangeKind::Merge);
5019 assert_eq!(edge.consumer, root);
5020 assert_eq!(
5021 edge.schema_fingerprint,
5022 plan.exchanges[0].schema_fingerprint
5023 );
5024 }
5025 let mut custom = desc(scan("t"));
5027 custom.options.max_spill_bytes_per_fragment = 1_234;
5028 let plan = distribute(&custom, &locator, &metadata).unwrap();
5029 assert!(plan
5030 .fragments
5031 .iter()
5032 .all(|fragment| fragment.max_spill_bytes == 1_234));
5033 }
5034
5035 #[test]
5036 fn fragment_control_begin_spill_opens_core_spill_session() {
5037 use mongreldb_core::ExecutionControl;
5038 use mongreldb_types::ids::QueryId;
5039
5040 let dir = tempfile::tempdir().unwrap();
5041 let db = mongreldb_core::Database::create(dir.path()).unwrap();
5043 let control = FragmentControl {
5044 control: ExecutionControl::new(None),
5045 max_spill_bytes: 64 * 1024,
5046 authorization_context: Arc::from([]),
5047 };
5048 let session = control
5049 .begin_spill(db.spill_manager(), QueryId::from_bytes([0xAB; 16]))
5050 .expect("begin_spill binds planner allowance to core SpillManager");
5051 let stats = db.spill_manager().stats();
5052 assert_eq!(
5053 stats.global_budget_bytes,
5054 db.spill_manager().config().global_bytes
5055 );
5056 drop(session);
5057 }
5058
5059 #[test]
5060 fn aggregate_plan_shape_grouped_and_ungrouped() {
5061 let tablets = [tablet(1), tablet(2), tablet(3)];
5062 let (locator, metadata) = world(&[("t", &tablets, hash_spec("id"), stats(900, 900_000))]);
5063 let aggregates = vec![
5064 AggregateExpr {
5065 function: AggregateFunction::Count,
5066 column: None,
5067 },
5068 AggregateExpr {
5069 function: AggregateFunction::Sum,
5070 column: Some("v".to_owned()),
5071 },
5072 ];
5073 let grouped = LogicalPlanLite::Aggregate {
5076 input: Box::new(scan("t")),
5077 group_by: vec!["g".to_owned()],
5078 aggregates: aggregates.clone(),
5079 };
5080 let plan = distribute(&desc(grouped), &locator, &metadata).unwrap();
5081 assert_eq!(plan.fragments.len(), 4);
5082 for index in 0..3 {
5083 assert!(matches!(
5084 operators(&plan, index as FragmentId)[1],
5085 FragmentOperator::PartialAggregate { .. }
5086 ));
5087 }
5088 assert_eq!(plan.exchanges.len(), 3);
5089 for edge in &plan.exchanges {
5090 assert_eq!(
5091 edge.kind,
5092 ExchangeKind::HashRepartition {
5093 keys: vec!["g".to_owned()]
5094 }
5095 );
5096 }
5097 let root = plan.root_fragment_id().unwrap();
5098 assert!(matches!(
5099 operators(&plan, root).last().unwrap(),
5100 FragmentOperator::FinalAggregate { .. }
5101 ));
5102 let ungrouped = LogicalPlanLite::Aggregate {
5104 input: Box::new(scan("t")),
5105 group_by: Vec::new(),
5106 aggregates: aggregates[..1].to_vec(),
5107 };
5108 let plan = distribute(&desc(ungrouped), &locator, &metadata).unwrap();
5109 assert!(plan
5110 .exchanges
5111 .iter()
5112 .all(|edge| edge.kind == ExchangeKind::Merge));
5113 let root = plan.root_fragment_id().unwrap();
5114 assert_eq!(plan.fragments[root as usize].estimated_rows, 1);
5115 }
5116
5117 #[test]
5118 fn colocated_join_plan_fuses_scans_without_exchange() {
5119 let tablets = [tablet(1), tablet(2), tablet(3)];
5120 let (locator, metadata) = world(&[
5121 (
5122 "a",
5123 &tablets,
5124 hash_spec("id"),
5125 stats(6_000, 6 * 1024 * 1024),
5126 ),
5127 (
5128 "b",
5129 &tablets,
5130 hash_spec("id"),
5131 stats(3_000, 3 * 1024 * 1024),
5132 ),
5133 ]);
5134 let join = LogicalPlanLite::Join {
5135 left: Box::new(scan("a")),
5136 right: Box::new(scan("b")),
5137 on: vec![JoinKey {
5138 left: "id".to_owned(),
5139 right: "id".to_owned(),
5140 }],
5141 };
5142 let plan = distribute(&desc(join), &locator, &metadata).unwrap();
5143 assert_eq!(plan.fragments.len(), 4, "3 fused fragments + gather");
5144 for index in 0..3 {
5145 let ops = operators(&plan, index as FragmentId);
5146 assert!(
5147 matches!(&ops[0], FragmentOperator::TabletScan { table, .. } if table == "a"),
5148 "left scan first: {ops:?}"
5149 );
5150 assert!(
5151 matches!(&ops[1], FragmentOperator::TabletScan { table, .. } if table == "b"),
5152 "right scan second: {ops:?}"
5153 );
5154 assert!(
5155 matches!(&ops[2], FragmentOperator::DistributedHashJoin { .. }),
5156 "fused hash join: {ops:?}"
5157 );
5158 }
5159 assert!(
5160 plan.exchanges
5161 .iter()
5162 .all(|edge| edge.kind == ExchangeKind::Merge),
5163 "a colocated join has no shuffle: {:?}",
5164 plan.exchanges
5165 );
5166 assert_eq!(plan.exchanges.len(), 3, "only the gather edges remain");
5167 }
5168
5169 #[test]
5170 fn broadcast_join_plan_replicates_the_small_side() {
5171 let big_tablets = [tablet(1), tablet(2), tablet(3)];
5172 let small_tablets = [tablet(4), tablet(5)];
5173 let (locator, metadata) = world(&[
5174 (
5175 "big",
5176 &big_tablets,
5177 hash_spec("id"),
5178 stats(8_000, 64 * 1024 * 1024),
5179 ),
5180 (
5181 "small",
5182 &small_tablets,
5183 hash_spec("key"),
5184 stats(100, 1024 * 1024),
5185 ),
5186 ]);
5187 let join = LogicalPlanLite::Join {
5188 left: Box::new(scan("big")),
5189 right: Box::new(scan("small")),
5190 on: vec![JoinKey {
5191 left: "id".to_owned(),
5192 right: "key".to_owned(),
5193 }],
5194 };
5195 let plan = distribute(&desc(join), &locator, &metadata).unwrap();
5196 assert_eq!(plan.fragments.len(), 6);
5198 let root = plan.root_fragment_id().unwrap();
5199 let broadcast_edges: Vec<&ExchangeDescriptor> = plan
5200 .exchanges
5201 .iter()
5202 .filter(|edge| edge.kind == ExchangeKind::Broadcast)
5203 .collect();
5204 assert_eq!(
5205 broadcast_edges.len(),
5206 6,
5207 "each small producer x each big fragment"
5208 );
5209 for edge in &broadcast_edges {
5210 assert!(edge.producer >= 3, "small side produces the broadcast");
5211 assert!(edge.consumer < 3, "big side consumes the broadcast");
5212 }
5213 for index in 0..3 {
5214 let ops = operators(&plan, index as FragmentId);
5215 let last_transform = ops
5216 .iter()
5217 .rfind(|op| !matches!(op, FragmentOperator::RemoteExchangeSink { .. }))
5218 .unwrap();
5219 assert!(matches!(
5220 last_transform,
5221 FragmentOperator::BroadcastJoin {
5222 build_side: BuildSide::Right,
5223 ..
5224 }
5225 ));
5226 let sources = ops
5227 .iter()
5228 .filter(|op| matches!(op, FragmentOperator::RemoteExchangeSource { .. }))
5229 .count();
5230 assert_eq!(sources, 2, "one source per small producer");
5231 }
5232 assert!(plan
5233 .exchanges
5234 .iter()
5235 .filter(|edge| edge.consumer == root)
5236 .all(|edge| edge.kind == ExchangeKind::Merge && edge.producer < 3));
5237 }
5238
5239 #[test]
5240 fn repartition_join_plan_shuffles_both_sides() {
5241 let left_tablets = [tablet(1), tablet(2)];
5242 let right_tablets = [tablet(3), tablet(4), tablet(5)];
5243 let (locator, metadata) = world(&[
5244 (
5245 "l",
5246 &left_tablets,
5247 hash_spec("a"),
5248 stats(8_000, 64 * 1024 * 1024),
5249 ),
5250 (
5251 "r",
5252 &right_tablets,
5253 hash_spec("b"),
5254 stats(9_000, 96 * 1024 * 1024),
5255 ),
5256 ]);
5257 let join = LogicalPlanLite::Join {
5258 left: Box::new(scan("l")),
5259 right: Box::new(scan("r")),
5260 on: vec![JoinKey {
5261 left: "a".to_owned(),
5262 right: "b".to_owned(),
5263 }],
5264 };
5265 let plan = distribute(&desc(join), &locator, &metadata).unwrap();
5266 assert_eq!(plan.fragments.len(), 9);
5268 let root = plan.root_fragment_id().unwrap();
5269 let shuffles: Vec<&ExchangeDescriptor> = plan
5270 .exchanges
5271 .iter()
5272 .filter(|edge| matches!(edge.kind, ExchangeKind::HashRepartition { .. }))
5273 .collect();
5274 assert_eq!(shuffles.len(), 2 * 3 + 3 * 3);
5275 let left_keys: Vec<&ExchangeDescriptor> = shuffles
5276 .iter()
5277 .filter(|edge| edge.producer < 2)
5278 .copied()
5279 .collect();
5280 assert!(
5281 left_keys
5282 .iter()
5283 .all(|edge| matches!(&edge.kind, ExchangeKind::HashRepartition { keys } if keys == &vec!["a".to_owned()]))
5284 );
5285 for join_fragment in 5..8 {
5286 let fragment = &plan.fragments[join_fragment];
5287 assert_eq!(
5288 fragment.assignment,
5289 FragmentAssignment::Tablet(right_tablets[(join_fragment - 5) % 3]),
5290 "join fragments round-robin over the larger side's tablets"
5291 );
5292 let ops = operators(&plan, fragment.fragment_id);
5293 let sources = ops
5294 .iter()
5295 .filter(|op| matches!(op, FragmentOperator::RemoteExchangeSource { .. }))
5296 .count();
5297 assert_eq!(sources, 5, "2 left + 3 right sources: {ops:?}");
5298 let last_transform = ops
5299 .iter()
5300 .rfind(|op| !matches!(op, FragmentOperator::RemoteExchangeSink { .. }))
5301 .unwrap();
5302 assert!(matches!(
5303 last_transform,
5304 FragmentOperator::RepartitionJoin { .. }
5305 ));
5306 }
5307 assert!(plan
5308 .exchanges
5309 .iter()
5310 .filter(|edge| edge.consumer == root)
5311 .all(|edge| edge.kind == ExchangeKind::Merge));
5312 }
5313
5314 #[test]
5315 fn sort_and_limit_plan_shapes() {
5316 let tablets = [tablet(1), tablet(2), tablet(3)];
5317 let (locator, metadata) = world(&[("t", &tablets, hash_spec("id"), stats(900, 900_000))]);
5318
5319 let topk = LogicalPlanLite::Sort {
5321 input: Box::new(scan("t")),
5322 keys: vec![SortKey {
5323 column: "score".to_owned(),
5324 descending: true,
5325 }],
5326 limit: Some(10),
5327 };
5328 let plan = distribute(&desc(topk), &locator, &metadata).unwrap();
5329 for index in 0..3 {
5330 assert!(matches!(
5331 operators(&plan, index as FragmentId)[1],
5332 FragmentOperator::DistributedTopK { k: 10, .. }
5333 ));
5334 }
5335 let root = plan.root_fragment_id().unwrap();
5336 assert!(matches!(
5337 operators(&plan, root).last().unwrap(),
5338 FragmentOperator::DistributedTopK { k: 10, .. }
5339 ));
5340 assert!(plan
5341 .exchanges
5342 .iter()
5343 .all(|edge| edge.kind == ExchangeKind::Merge));
5344
5345 let sort = LogicalPlanLite::Sort {
5347 input: Box::new(scan("t")),
5348 keys: vec![SortKey {
5349 column: "x".to_owned(),
5350 descending: false,
5351 }],
5352 limit: None,
5353 };
5354 let plan = distribute(&desc(sort), &locator, &metadata).unwrap();
5355 for index in 0..3 {
5356 assert!(matches!(
5357 operators(&plan, index as FragmentId)[1],
5358 FragmentOperator::MergeSort { limit: None, .. }
5359 ));
5360 }
5361 let root = plan.root_fragment_id().unwrap();
5362 assert!(matches!(
5363 operators(&plan, root).last().unwrap(),
5364 FragmentOperator::MergeSort { limit: None, .. }
5365 ));
5366
5367 let multi = LogicalPlanLite::Sort {
5370 input: Box::new(scan("t")),
5371 keys: vec![
5372 SortKey {
5373 column: "a".to_owned(),
5374 descending: false,
5375 },
5376 SortKey {
5377 column: "b".to_owned(),
5378 descending: true,
5379 },
5380 ],
5381 limit: Some(5),
5382 };
5383 let plan = distribute(&desc(multi), &locator, &metadata).unwrap();
5384 for index in 0..3 {
5385 assert!(matches!(
5386 operators(&plan, index as FragmentId)[1],
5387 FragmentOperator::MergeSort { limit: Some(5), .. }
5388 ));
5389 }
5390 let root = plan.root_fragment_id().unwrap();
5391 assert!(matches!(
5392 operators(&plan, root).last().unwrap(),
5393 FragmentOperator::MergeSort { limit: Some(5), .. }
5394 ));
5395
5396 let limit = LogicalPlanLite::Limit {
5398 input: Box::new(scan("t")),
5399 limit: 7,
5400 };
5401 let plan = distribute(&desc(limit), &locator, &metadata).unwrap();
5402 for index in 0..3 {
5403 assert!(matches!(
5404 operators(&plan, index as FragmentId)[1],
5405 FragmentOperator::DistributedLimit { limit: 7 }
5406 ));
5407 }
5408 let root = plan.root_fragment_id().unwrap();
5409 assert!(matches!(
5410 operators(&plan, root).last().unwrap(),
5411 FragmentOperator::DistributedLimit { limit: 7 }
5412 ));
5413
5414 let chained = LogicalPlanLite::Limit {
5417 input: Box::new(LogicalPlanLite::Aggregate {
5418 input: Box::new(scan("t")),
5419 group_by: Vec::new(),
5420 aggregates: vec![AggregateExpr {
5421 function: AggregateFunction::Count,
5422 column: None,
5423 }],
5424 }),
5425 limit: 5,
5426 };
5427 let plan = distribute(&desc(chained), &locator, &metadata).unwrap();
5428 assert_eq!(plan.fragments.len(), 4);
5429 let root = plan.root_fragment_id().unwrap();
5430 let ops = operators(&plan, root);
5431 assert!(matches!(
5432 ops[ops.len() - 2],
5433 FragmentOperator::FinalAggregate { .. }
5434 ));
5435 assert!(matches!(
5436 ops[ops.len() - 1],
5437 FragmentOperator::DistributedLimit { limit: 5 }
5438 ));
5439 }
5440
5441 #[test]
5442 fn planner_rejects_invalid_inputs() {
5443 let tablets = [tablet(1)];
5444 let (locator, metadata) = world(&[("t", &tablets, hash_spec("id"), stats(10, 100))]);
5445 let error = distribute(&desc(scan("nope")), &locator, &metadata).unwrap_err();
5447 assert!(
5448 matches!(error, DistributedError::UnknownTable(_)),
5449 "{error}"
5450 );
5451 let (locator, metadata) = world(&[("e", &[], hash_spec("id"), stats(0, 0))]);
5453 let error = distribute(&desc(scan("e")), &locator, &metadata).unwrap_err();
5454 assert!(
5455 matches!(error, DistributedError::EmptyLayout { .. }),
5456 "{error}"
5457 );
5458 let (locator, metadata) = world(&[("t", &tablets, hash_spec("id"), stats(10, 100))]);
5459 let join = LogicalPlanLite::Join {
5461 left: Box::new(scan("t")),
5462 right: Box::new(scan("t")),
5463 on: Vec::new(),
5464 };
5465 let error = distribute(&desc(join), &locator, &metadata).unwrap_err();
5466 assert!(matches!(error, DistributedError::InvalidPlan(_)), "{error}");
5467 let aggregate = LogicalPlanLite::Aggregate {
5469 input: Box::new(scan("t")),
5470 group_by: Vec::new(),
5471 aggregates: vec![AggregateExpr {
5472 function: AggregateFunction::Sum,
5473 column: None,
5474 }],
5475 };
5476 let error = distribute(&desc(aggregate), &locator, &metadata).unwrap_err();
5477 assert!(matches!(error, DistributedError::InvalidPlan(_)), "{error}");
5478 let sort = LogicalPlanLite::Sort {
5480 input: Box::new(scan("t")),
5481 keys: Vec::new(),
5482 limit: None,
5483 };
5484 let error = distribute(&desc(sort), &locator, &metadata).unwrap_err();
5485 assert!(matches!(error, DistributedError::InvalidPlan(_)), "{error}");
5486 }
5487
5488 fn candidate(score: u64, tablet_n: u8, row_id: u64) -> TopKCandidate {
5493 TopKCandidate {
5494 score,
5495 tablet: tablet(tablet_n),
5496 row_id: RowId(row_id),
5497 }
5498 }
5499
5500 #[test]
5501 fn topk_tie_break_is_exact() {
5502 let mut rows = vec![
5504 candidate(5, 2, 0),
5505 candidate(5, 1, 2),
5506 candidate(7, 3, 0),
5507 candidate(5, 1, 0),
5508 candidate(3, 1, 0),
5509 ];
5510 rows.sort_by(topk_cmp);
5511 assert_eq!(
5512 rows,
5513 vec![
5514 candidate(7, 3, 0),
5515 candidate(5, 1, 0),
5516 candidate(5, 1, 2),
5517 candidate(5, 2, 0),
5518 candidate(3, 1, 0),
5519 ]
5520 );
5521 }
5522
5523 #[test]
5524 fn merge_top_k_needs_no_refill_when_tablets_are_exhausted() {
5525 let shards = vec![
5526 TabletTopK {
5527 tablet: tablet(1),
5528 rows: vec![candidate(100, 1, 0), candidate(90, 1, 1)],
5529 unseen_bound: None,
5530 },
5531 TabletTopK {
5532 tablet: tablet(2),
5533 rows: vec![candidate(96, 2, 0)],
5534 unseen_bound: None,
5535 },
5536 ];
5537 let merge = merge_top_k(&shards, 2);
5538 assert_eq!(
5539 merge.winners,
5540 vec![candidate(100, 1, 0), candidate(96, 2, 0)]
5541 );
5542 assert!(merge.refill.is_empty());
5543 }
5544
5545 #[test]
5546 fn merge_top_k_refill_rules_follow_the_bounds() {
5547 let shards = vec![
5549 TabletTopK {
5550 tablet: tablet(1),
5551 rows: vec![candidate(100, 1, 0), candidate(90, 1, 1)],
5552 unseen_bound: Some(95),
5553 },
5554 TabletTopK {
5555 tablet: tablet(2),
5556 rows: vec![candidate(96, 2, 0)],
5557 unseen_bound: None,
5558 },
5559 ];
5560 let merge = merge_top_k(&shards, 2);
5561 assert!(
5562 merge.refill.is_empty(),
5563 "unseen scores of tablet 1 are at most 95 < 96: {:?}",
5564 merge.refill
5565 );
5566 let shards = vec![
5568 TabletTopK {
5569 tablet: tablet(1),
5570 rows: vec![candidate(100, 1, 0), candidate(90, 1, 1)],
5571 unseen_bound: Some(97),
5572 },
5573 TabletTopK {
5574 tablet: tablet(2),
5575 rows: vec![candidate(96, 2, 0)],
5576 unseen_bound: None,
5577 },
5578 ];
5579 let merge = merge_top_k(&shards, 2);
5580 assert_eq!(merge.refill, vec![tablet(1)]);
5581 let shards = vec![
5584 TabletTopK {
5585 tablet: tablet(1),
5586 rows: vec![candidate(100, 1, 0)],
5587 unseen_bound: Some(96),
5588 },
5589 TabletTopK {
5590 tablet: tablet(2),
5591 rows: vec![candidate(96, 2, 9)],
5592 unseen_bound: None,
5593 },
5594 ];
5595 let merge = merge_top_k(&shards, 2);
5596 assert_eq!(
5597 merge.refill,
5598 vec![tablet(1)],
5599 "(96, tablet 1, min row id) ranks better than (96, tablet 2, 9)"
5600 );
5601 let shards = vec![
5603 TabletTopK {
5604 tablet: tablet(1),
5605 rows: vec![candidate(5, 1, 0)],
5606 unseen_bound: Some(1),
5607 },
5608 TabletTopK {
5609 tablet: tablet(2),
5610 rows: Vec::new(),
5611 unseen_bound: None,
5612 },
5613 ];
5614 let merge = merge_top_k(&shards, 3);
5615 assert_eq!(merge.winners, vec![candidate(5, 1, 0)]);
5616 assert_eq!(merge.refill, vec![tablet(1)]);
5617 let merge = merge_top_k(&shards, 0);
5619 assert!(merge.winners.is_empty() && merge.refill.is_empty());
5620 }
5621
5622 #[test]
5623 fn exact_top_k_fills_winners_via_adaptive_refill() {
5624 let tablet_one_rows = vec![
5626 candidate(100, 1, 0),
5627 candidate(99, 1, 1),
5628 candidate(98, 1, 2),
5629 candidate(97, 1, 3),
5630 ];
5631 let shards = vec![
5632 TabletTopK {
5633 tablet: tablet(1),
5634 rows: tablet_one_rows[..2].to_vec(),
5635 unseen_bound: Some(98),
5636 },
5637 TabletTopK {
5638 tablet: tablet(2),
5639 rows: vec![candidate(96, 2, 0), candidate(95, 2, 1)],
5640 unseen_bound: None,
5641 },
5642 ];
5643 let remaining = tablet_one_rows.clone();
5644 let result = exact_top_k(3, shards, move |tablet_id| {
5645 assert_eq!(tablet_id, tablet(1));
5646 TabletTopK {
5647 tablet: tablet_id,
5648 rows: remaining[2..].to_vec(),
5649 unseen_bound: None,
5650 }
5651 })
5652 .unwrap();
5653 assert_eq!(
5654 result,
5655 vec![
5656 candidate(100, 1, 0),
5657 candidate(99, 1, 1),
5658 candidate(98, 1, 2),
5659 ]
5660 );
5661 }
5662
5663 #[test]
5664 fn exact_top_k_rejects_a_stuck_refill() {
5665 let shards = vec![TabletTopK {
5666 tablet: tablet(1),
5667 rows: Vec::new(),
5668 unseen_bound: Some(10),
5669 }];
5670 let error = exact_top_k(1, shards, |tablet| TabletTopK {
5671 tablet,
5672 rows: Vec::new(),
5673 unseen_bound: Some(10),
5674 })
5675 .unwrap_err();
5676 assert!(matches!(error, DistributedError::InvalidPlan(_)), "{error}");
5677 }
5678
5679 #[test]
5680 fn exact_top_k_matches_single_node_oracle_across_1000_sharded_datasets() {
5681 let mut total_refills = 0usize;
5682 for seed in 0..1_000u64 {
5683 let mut rng = SplitMix64(seed.wrapping_mul(0x9E37_79B9_7F4A_7C15).wrapping_add(1));
5684 let tablet_count = 1 + rng.below(8) as usize;
5685 let k = rng.below(30) as usize;
5686 let batch = 1 + rng.below(4) as usize;
5687 let mut oracle_rows = Vec::new();
5688 let mut per_tablet: HashMap<TabletId, Vec<TopKCandidate>> = HashMap::new();
5689 for index in 0..tablet_count {
5690 let tablet = tablet(index as u8 + 1);
5691 let row_count = rng.below(40) as usize;
5692 let mut rows: Vec<TopKCandidate> = (0..row_count)
5693 .map(|row| TopKCandidate {
5694 score: rng.below(12),
5695 tablet,
5696 row_id: RowId(row as u64),
5697 })
5698 .collect();
5699 rows.sort_by(topk_cmp);
5700 oracle_rows.extend(rows.iter().copied());
5701 per_tablet.insert(tablet, rows);
5702 }
5703 let mut oracle = oracle_rows;
5704 oracle.sort_by(topk_cmp);
5705 oracle.truncate(k);
5706 let contribution = |tablet: TabletId, offset: usize| -> TabletTopK {
5707 let rows = &per_tablet[&tablet];
5708 let start = offset.min(rows.len());
5709 let end = (offset + batch).min(rows.len());
5710 TabletTopK {
5711 tablet,
5712 rows: rows[start..end].to_vec(),
5713 unseen_bound: rows.get(end).map(|candidate| candidate.score),
5714 }
5715 };
5716 let initial: Vec<TabletTopK> = (0..tablet_count)
5717 .map(|index| contribution(tablet(index as u8 + 1), 0))
5718 .collect();
5719 let mut returned: HashMap<TabletId, usize> = (0..tablet_count)
5720 .map(|index| (tablet(index as u8 + 1), batch))
5721 .collect();
5722 let result = exact_top_k(k, initial, |tablet| {
5723 total_refills += 1;
5724 let offset = returned[&tablet];
5725 returned.insert(tablet, offset + batch);
5726 contribution(tablet, offset)
5727 })
5728 .unwrap_or_else(|error| panic!("seed {seed}: {error}"));
5729 assert_eq!(result, oracle, "seed {seed}: k={k} batch={batch}");
5730 }
5731 assert!(
5732 total_refills > 0,
5733 "the property run must actually exercise adaptive refill"
5734 );
5735 }
5736
5737 fn coordinator_with(
5744 executor: Arc<dyn FragmentExecutor>,
5745 ) -> (
5746 Arc<Coordinator>,
5747 Arc<InMemoryTransport>,
5748 Arc<SqlQueryRegistry>,
5749 ) {
5750 let transport = Arc::new(InMemoryTransport::new(executor));
5751 let registry = Arc::new(SqlQueryRegistry::default());
5752 let coordinator = Arc::new(Coordinator::new(
5753 Arc::clone(&transport) as Arc<dyn FragmentTransport>,
5754 Arc::clone(®istry),
5755 ));
5756 (coordinator, transport, registry)
5757 }
5758
5759 struct BlockingExecutor {
5762 barrier: Arc<tokio::sync::Barrier>,
5763 }
5764
5765 #[async_trait::async_trait]
5766 impl FragmentExecutor for BlockingExecutor {
5767 async fn execute(
5768 &self,
5769 _fragment: &PlanFragment,
5770 _inputs: Vec<FragmentStream>,
5771 control: FragmentControl,
5772 ) -> DistributedResult<FragmentStream> {
5773 self.barrier.wait().await;
5774 control.control.cancelled().await;
5775 Err(DistributedError::Cancelled(control.control.reason()))
5776 }
5777 }
5778
5779 fn scan_plan(table: &str) -> (DistributedPlan, Vec<TabletId>) {
5782 let tablets = [tablet(1), tablet(2), tablet(3)];
5783 let (locator, metadata) = world(&[(
5784 table,
5785 &tablets,
5786 hash_spec("id"),
5787 stats(3_000, 3 * 1024 * 1024),
5788 )]);
5789 let plan = distribute(&desc(scan(table)), &locator, &metadata).unwrap();
5790 (plan, tablets.to_vec())
5791 }
5792
5793 #[tokio::test]
5794 async fn scan_gather_returns_all_rows() {
5795 let store = Arc::new(InMemoryTableStore::new());
5796 let tablets = [tablet(1), tablet(2), tablet(3)];
5797 store.insert("t", tablets[0], i64_batch(&[("v", vec![1, 2])]));
5798 store.insert("t", tablets[1], i64_batch(&[("v", vec![3])]));
5799 let (plan, _) = scan_plan("t");
5801 let (coordinator, _, _) = coordinator_with(Arc::new(InMemoryFragmentExecutor::new(store)));
5802 let batches = coordinator.execute(&plan).await.unwrap();
5803 let mut values = collect_i64(&batches, "v");
5804 values.sort_unstable();
5805 assert_eq!(values, vec![1, 2, 3]);
5806 }
5807
5808 #[tokio::test]
5809 async fn remote_arrow_ipc_transport_gathers_with_pull_backpressure() {
5810 let store = Arc::new(InMemoryTableStore::new());
5811 let tablets = [tablet(1), tablet(2), tablet(3)];
5812 store.insert("t", tablets[0], i64_batch(&[("v", vec![1, 2])]));
5813 store.insert("t", tablets[1], i64_batch(&[("v", vec![3])]));
5814 let executor: Arc<dyn FragmentExecutor> = Arc::new(InMemoryFragmentExecutor::new(store));
5815 let endpoint = Arc::new(RemoteFragmentEndpoint::new(executor));
5816 let client: Arc<dyn FragmentRpcClient> =
5817 Arc::new(LoopbackFragmentRpcClient::new(Arc::clone(&endpoint)));
5818 let transport: Arc<dyn FragmentTransport> = Arc::new(RemoteFragmentTransport::new(client));
5819 let coordinator = Coordinator::new(transport, Arc::new(SqlQueryRegistry::default()));
5820 let (plan, _) = scan_plan("t");
5821
5822 let batches = coordinator.execute(&plan).await.unwrap();
5823 let mut values = collect_i64(&batches, "v");
5824 values.sort_unstable();
5825 assert_eq!(values, vec![1, 2, 3]);
5826 assert_eq!(
5827 endpoint.active_executions(),
5828 0,
5829 "terminal pulls release every worker cursor"
5830 );
5831 }
5832
5833 #[tokio::test]
5834 async fn merge_sort_matches_single_node_sort() {
5835 let store = Arc::new(InMemoryTableStore::new());
5836 let tablets = [tablet(1), tablet(2), tablet(3)];
5837 store.insert("s", tablets[0], i64_batch(&[("x", vec![5, 1, 9])]));
5838 store.insert("s", tablets[1], i64_batch(&[("x", vec![3, 7])]));
5839 store.insert("s", tablets[2], i64_batch(&[("x", vec![8, 2, 6, 4])]));
5840 let (locator, metadata) = world(&[("s", &tablets, hash_spec("id"), stats(9, 900))]);
5841 let (coordinator, _, _) = coordinator_with(Arc::new(InMemoryFragmentExecutor::new(store)));
5842
5843 let sort = LogicalPlanLite::Sort {
5845 input: Box::new(scan("s")),
5846 keys: vec![SortKey {
5847 column: "x".to_owned(),
5848 descending: false,
5849 }],
5850 limit: None,
5851 };
5852 let plan = distribute(&desc(sort), &locator, &metadata).unwrap();
5853 let batches = coordinator.execute(&plan).await.unwrap();
5854 assert_eq!(collect_i64(&batches, "x"), vec![1, 2, 3, 4, 5, 6, 7, 8, 9]);
5855
5856 let sort = LogicalPlanLite::Sort {
5858 input: Box::new(scan("s")),
5859 keys: vec![SortKey {
5860 column: "x".to_owned(),
5861 descending: false,
5862 }],
5863 limit: Some(4),
5864 };
5865 let plan = distribute(&desc(sort), &locator, &metadata).unwrap();
5866 let batches = coordinator.execute(&plan).await.unwrap();
5867 assert_eq!(collect_i64(&batches, "x"), vec![1, 2, 3, 4]);
5868 }
5869
5870 #[tokio::test]
5871 async fn final_aggregate_matches_single_node_aggregate() {
5872 let store = Arc::new(InMemoryTableStore::new());
5873 let tablets = [tablet(1), tablet(2), tablet(3)];
5874 store.insert(
5875 "t",
5876 tablets[0],
5877 i64_batch(&[("g", vec![1, 2, 1, 3]), ("v", vec![10, 20, 30, 40])]),
5878 );
5879 store.insert(
5880 "t",
5881 tablets[1],
5882 i64_batch(&[("g", vec![2, 1, 3]), ("v", vec![50, 60, 70])]),
5883 );
5884 store.insert(
5885 "t",
5886 tablets[2],
5887 i64_batch(&[("g", vec![3, 2]), ("v", vec![80, 90])]),
5888 );
5889 let (locator, metadata) = world(&[("t", &tablets, hash_spec("id"), stats(9, 900))]);
5890 let (coordinator, _, _) = coordinator_with(Arc::new(InMemoryFragmentExecutor::new(store)));
5891
5892 let aggregates = vec![
5893 AggregateExpr {
5894 function: AggregateFunction::Count,
5895 column: None,
5896 },
5897 AggregateExpr {
5898 function: AggregateFunction::Sum,
5899 column: Some("v".to_owned()),
5900 },
5901 AggregateExpr {
5902 function: AggregateFunction::Min,
5903 column: Some("v".to_owned()),
5904 },
5905 AggregateExpr {
5906 function: AggregateFunction::Max,
5907 column: Some("v".to_owned()),
5908 },
5909 AggregateExpr {
5910 function: AggregateFunction::Avg,
5911 column: Some("v".to_owned()),
5912 },
5913 ];
5914 let grouped = LogicalPlanLite::Aggregate {
5915 input: Box::new(scan("t")),
5916 group_by: vec!["g".to_owned()],
5917 aggregates,
5918 };
5919 let plan = distribute(&desc(grouped), &locator, &metadata).unwrap();
5920 let batches = coordinator.execute(&plan).await.unwrap();
5921 let groups = collect_i64(&batches, "g");
5922 let counts = collect_i64(&batches, "count_star");
5923 let sums = collect_i64(&batches, "sum_v");
5924 let mins = collect_i64(&batches, "min_v");
5925 let maxs = collect_i64(&batches, "max_v");
5926 let avgs = collect_f64(&batches, "avg_v");
5927 let mut oracle: HashMap<i64, (i64, i64, i64, i64)> = HashMap::new();
5929 for (g, v) in [
5930 (1, 10),
5931 (2, 20),
5932 (1, 30),
5933 (3, 40),
5934 (2, 50),
5935 (1, 60),
5936 (3, 70),
5937 (3, 80),
5938 (2, 90),
5939 ] {
5940 let entry = oracle.entry(g).or_insert((0, 0, i64::MAX, i64::MIN));
5941 entry.0 += 1;
5942 entry.1 += v;
5943 entry.2 = entry.2.min(v);
5944 entry.3 = entry.3.max(v);
5945 }
5946 assert_eq!(groups.len(), 3);
5947 for index in 0..groups.len() {
5948 let (count, sum, min, max) = oracle[&groups[index]];
5949 assert_eq!(counts[index], count, "count for g={}", groups[index]);
5950 assert_eq!(sums[index], sum, "sum for g={}", groups[index]);
5951 assert_eq!(mins[index], min, "min for g={}", groups[index]);
5952 assert_eq!(maxs[index], max, "max for g={}", groups[index]);
5953 let expected_avg = sum as f64 / count as f64;
5954 let actual_avg = avgs[index].expect("avg is set");
5955 assert!(
5956 (actual_avg - expected_avg).abs() < 1e-12,
5957 "avg for g={}: {actual_avg} vs {expected_avg}",
5958 groups[index]
5959 );
5960 }
5961
5962 let scalar = LogicalPlanLite::Aggregate {
5964 input: Box::new(scan("t")),
5965 group_by: Vec::new(),
5966 aggregates: vec![
5967 AggregateExpr {
5968 function: AggregateFunction::Count,
5969 column: None,
5970 },
5971 AggregateExpr {
5972 function: AggregateFunction::Sum,
5973 column: Some("v".to_owned()),
5974 },
5975 ],
5976 };
5977 let plan = distribute(&desc(scalar), &locator, &metadata).unwrap();
5978 let batches = coordinator.execute(&plan).await.unwrap();
5979 assert_eq!(collect_i64(&batches, "count_star"), vec![9]);
5980 assert_eq!(collect_i64(&batches, "sum_v"), vec![450]);
5981 }
5982
5983 #[tokio::test]
5984 async fn distributed_top_k_matches_oracle_with_and_without_refill() {
5985 let store = Arc::new(InMemoryTableStore::new());
5988 let tablets = [tablet(1), tablet(2), tablet(3)];
5989 store.insert(
5990 "k",
5991 tablets[0],
5992 score_batch(vec![100, 90, 80, 70], vec![0, 1, 2, 3], vec![0, 1, 2, 3]),
5993 );
5994 store.insert(
5995 "k",
5996 tablets[1],
5997 score_batch(vec![90, 80, 60], vec![0, 1, 2], vec![0, 1, 2]),
5998 );
5999 store.insert(
6000 "k",
6001 tablets[2],
6002 score_batch(vec![90, 80, 50], vec![0, 1, 2], vec![0, 1, 2]),
6003 );
6004 let (locator, metadata) = world(&[("k", &tablets, hash_spec("id"), stats(10, 1_000))]);
6005 let topk = LogicalPlanLite::Sort {
6006 input: Box::new(scan("k")),
6007 keys: vec![SortKey {
6008 column: "score".to_owned(),
6009 descending: true,
6010 }],
6011 limit: Some(5),
6012 };
6013 let mut oracle: Vec<TopKCandidate> = Vec::new();
6015 for (tablet_n, rows) in [
6016 (1u8, vec![(100u64, 0u64), (90, 1), (80, 2), (70, 3)]),
6017 (2u8, vec![(90, 0), (80, 1), (60, 2)]),
6018 (3u8, vec![(90, 0), (80, 1), (50, 2)]),
6019 ] {
6020 for (score, row_id) in rows {
6021 oracle.push(candidate(score, tablet_n, row_id));
6022 }
6023 }
6024 oracle.sort_by(topk_cmp);
6025 oracle.truncate(5);
6026 let expected: Vec<(u64, i64)> = oracle
6027 .iter()
6028 .map(|winner| (winner.score, winner.row_id.0 as i64))
6029 .collect();
6030
6031 let run = async |emit_batch: Option<usize>| {
6032 let executor = match emit_batch {
6033 Some(batch) => InMemoryFragmentExecutor::with_topk_emit_batch(store.clone(), batch),
6034 None => InMemoryFragmentExecutor::new(store.clone()),
6035 };
6036 let (coordinator, transport, _) = coordinator_with(Arc::new(executor));
6037 let plan = distribute(&desc(topk.clone()), &locator, &metadata).unwrap();
6038 let batches = coordinator.execute(&plan).await.unwrap();
6039 (batches, transport)
6040 };
6041
6042 let (batches, transport) = run(None).await;
6044 let scores = collect_u64(&batches, "score");
6045 let payloads = collect_i64(&batches, "payload");
6046 let actual: Vec<(u64, i64)> = scores.into_iter().zip(payloads).collect();
6047 assert_eq!(actual, expected);
6048 assert!(
6049 transport.refill_log().is_empty(),
6050 "exact local top-k never needs refill"
6051 );
6052 assert!(
6053 batches
6054 .iter()
6055 .all(|batch| batch.schema().index_of(TOPK_ROWID_COLUMN).is_err()),
6056 "the internal row-id column is stripped"
6057 );
6058
6059 let (batches, transport) = run(Some(2)).await;
6062 let scores = collect_u64(&batches, "score");
6063 let payloads = collect_i64(&batches, "payload");
6064 let actual: Vec<(u64, i64)> = scores.into_iter().zip(payloads).collect();
6065 assert_eq!(actual, expected);
6066 assert!(
6067 !transport.refill_log().is_empty(),
6068 "bounded emission must trigger adaptive refill"
6069 );
6070
6071 let executor: Arc<dyn FragmentExecutor> = Arc::new(
6074 InMemoryFragmentExecutor::with_topk_emit_batch(Arc::clone(&store), 2),
6075 );
6076 let endpoint = Arc::new(RemoteFragmentEndpoint::new(executor));
6077 let client: Arc<dyn FragmentRpcClient> =
6078 Arc::new(LoopbackFragmentRpcClient::new(Arc::clone(&endpoint)));
6079 let coordinator = Coordinator::new(
6080 Arc::new(RemoteFragmentTransport::new(client)),
6081 Arc::new(SqlQueryRegistry::default()),
6082 );
6083 let plan = distribute(&desc(topk), &locator, &metadata).unwrap();
6084 let batches = coordinator.execute(&plan).await.unwrap();
6085 let actual = collect_u64(&batches, "score")
6086 .into_iter()
6087 .zip(collect_i64(&batches, "payload"))
6088 .collect::<Vec<_>>();
6089 assert_eq!(actual, expected);
6090 assert_eq!(endpoint.active_executions(), 0);
6091 }
6092
6093 #[tokio::test]
6094 async fn cancellation_fans_out_to_every_fragment() {
6095 let barrier = Arc::new(tokio::sync::Barrier::new(4));
6096 let executor = Arc::new(BlockingExecutor {
6097 barrier: Arc::clone(&barrier),
6098 });
6099 let (coordinator, transport, _registry) = coordinator_with(executor);
6100 let (plan, _) = scan_plan("t");
6101 let task = {
6102 let coordinator = Arc::clone(&coordinator);
6103 let plan = plan.clone();
6104 tokio::spawn(async move { coordinator.execute(&plan).await })
6105 };
6106 tokio::time::timeout(Duration::from_secs(30), barrier.wait())
6107 .await
6108 .expect("all fragments started");
6109 assert!(coordinator.cancel_query(&plan.query_id).unwrap());
6110 let result = task.await.unwrap();
6111 assert!(
6112 matches!(
6113 result,
6114 Err(DistributedError::Cancelled(
6115 CancellationReason::ClientRequest
6116 ))
6117 ),
6118 "cancellation surfaces as ClientRequest: {result:?}"
6119 );
6120 let cancelled = transport.cancelled_fragments();
6121 for fragment_id in 0..3 {
6122 assert!(
6123 cancelled.contains(&fragment_id),
6124 "fragment {fragment_id} received the transport cancel: {cancelled:?}"
6125 );
6126 let control = transport.control_for(fragment_id).unwrap();
6127 assert_eq!(control.reason(), CancellationReason::ClientRequest);
6128 }
6129 }
6130
6131 #[tokio::test]
6132 async fn remote_cancellation_reaches_worker_during_fragment_start() {
6133 let barrier = Arc::new(tokio::sync::Barrier::new(4));
6134 let executor: Arc<dyn FragmentExecutor> = Arc::new(BlockingExecutor {
6135 barrier: Arc::clone(&barrier),
6136 });
6137 let endpoint = Arc::new(RemoteFragmentEndpoint::new(executor));
6138 let client: Arc<dyn FragmentRpcClient> =
6139 Arc::new(LoopbackFragmentRpcClient::new(Arc::clone(&endpoint)));
6140 let coordinator = Arc::new(Coordinator::new(
6141 Arc::new(RemoteFragmentTransport::new(client)),
6142 Arc::new(SqlQueryRegistry::default()),
6143 ));
6144 let (plan, _) = scan_plan("t");
6145 let task = {
6146 let coordinator = Arc::clone(&coordinator);
6147 let plan = plan.clone();
6148 tokio::spawn(async move { coordinator.execute(&plan).await })
6149 };
6150 tokio::time::timeout(Duration::from_secs(30), barrier.wait())
6151 .await
6152 .expect("all remote workers entered fragment start");
6153 assert!(coordinator.cancel_query(&plan.query_id).unwrap());
6154 let result = tokio::time::timeout(Duration::from_secs(30), task)
6155 .await
6156 .expect("remote cancellation must not hang")
6157 .unwrap();
6158 assert!(matches!(result, Err(DistributedError::Cancelled(_))));
6159 assert_eq!(endpoint.active_executions(), 0);
6160 }
6161
6162 #[tokio::test]
6163 async fn registry_cancel_reaches_all_fragments() {
6164 let barrier = Arc::new(tokio::sync::Barrier::new(4));
6165 let executor = Arc::new(BlockingExecutor {
6166 barrier: Arc::clone(&barrier),
6167 });
6168 let (coordinator, transport, registry) = coordinator_with(executor);
6169 let (plan, _) = scan_plan("t");
6170 let task = {
6171 let coordinator = Arc::clone(&coordinator);
6172 let plan = plan.clone();
6173 tokio::spawn(async move { coordinator.execute(&plan).await })
6174 };
6175 tokio::time::timeout(Duration::from_secs(30), barrier.wait())
6176 .await
6177 .expect("all fragments started");
6178 let bridged: crate::query_registry::QueryId = plan.query_id.to_hex().parse().unwrap();
6180 assert_eq!(registry.cancel(bridged), CancelOutcome::Accepted);
6181 let result = task.await.unwrap();
6182 assert!(
6183 matches!(result, Err(DistributedError::Cancelled(_))),
6184 "registry cancellation aborts the distributed query: {result:?}"
6185 );
6186 for fragment_id in 0..3 {
6187 let control = transport.control_for(fragment_id).unwrap();
6188 assert_eq!(
6189 control.reason(),
6190 CancellationReason::ClientRequest,
6191 "fragment {fragment_id} observed the registry cancel"
6192 );
6193 }
6194 }
6195
6196 #[tokio::test]
6197 async fn lease_expiry_reclaims_abandoned_fragments() {
6198 let barrier = Arc::new(tokio::sync::Barrier::new(4));
6199 let executor = Arc::new(BlockingExecutor {
6200 barrier: Arc::clone(&barrier),
6201 });
6202 let transport = Arc::new(InMemoryTransport::new(executor));
6203 let registry = Arc::new(SqlQueryRegistry::default());
6204 let coordinator = Arc::new(Coordinator::with_limits(
6205 Arc::clone(&transport) as Arc<dyn FragmentTransport>,
6206 registry,
6207 1_024,
6208 16 * 1024 * 1024 * 1024,
6209 Duration::from_secs(30),
6210 ));
6211 let (plan, _) = scan_plan("t");
6212 let task = {
6213 let coordinator = Arc::clone(&coordinator);
6214 let plan = plan.clone();
6215 tokio::spawn(async move { coordinator.execute(&plan).await })
6216 };
6217 tokio::time::timeout(Duration::from_secs(30), barrier.wait())
6218 .await
6219 .expect("all fragments started");
6220 assert_eq!(coordinator.resources().reserved_fragments(), 3);
6221 let cleaned = coordinator.sweep_expired_leases(Instant::now() + Duration::from_secs(3_600));
6223 assert_eq!(cleaned, 3, "every abandoned fragment is reclaimed");
6224 assert_eq!(
6225 coordinator.resources().reserved_fragments(),
6226 0,
6227 "reclaimed fragments release their reservations"
6228 );
6229 for fragment_id in 0..3 {
6230 let control = transport.control_for(fragment_id).unwrap();
6231 assert_eq!(
6232 control.reason(),
6233 CancellationReason::ServerShutdown,
6234 "fragment {fragment_id} observes the worker loss"
6235 );
6236 }
6237 let result = task.await.unwrap();
6238 assert!(
6239 matches!(
6240 result,
6241 Err(DistributedError::Cancelled(
6242 CancellationReason::ServerShutdown
6243 ))
6244 ),
6245 "the query fails with the worker-loss reason: {result:?}"
6246 );
6247 }
6248
6249 #[test]
6250 fn resource_reservation_denies_then_releases() {
6251 let ledger = Arc::new(ResourceLedger::new(1, u64::MAX));
6252 let fragment = PlanFragment {
6253 fragment_id: 0,
6254 assignment: FragmentAssignment::Coordinator,
6255 operators: Vec::new(),
6256 estimated_rows: 1,
6257 estimated_bytes: 100,
6258 max_spill_bytes: 0,
6259 };
6260 let permit = ledger.reserve(&fragment).unwrap();
6261 assert_eq!(ledger.reserved_fragments(), 1);
6262 assert_eq!(ledger.reserved_bytes(), 100);
6263 let denied = ledger.reserve(&fragment).unwrap_err();
6264 assert!(
6265 matches!(denied, DistributedError::Reservation { fragment_id: 0, .. }),
6266 "{denied}"
6267 );
6268 drop(permit);
6269 assert_eq!(ledger.reserved_fragments(), 0);
6270 assert_eq!(ledger.reserved_bytes(), 0);
6271 ledger.reserve(&fragment).unwrap();
6272 let tight = Arc::new(ResourceLedger::new(8, 50));
6274 let denied = tight.reserve(&fragment).unwrap_err();
6275 assert!(
6276 matches!(denied, DistributedError::Reservation { .. }),
6277 "{denied}"
6278 );
6279 }
6280
6281 #[test]
6282 fn repartition_routes_every_row_exactly_once() {
6283 let batch = i64_batch(&[("k", (0..100).collect()), ("v", (0..100).collect())]);
6284 let frames = vec![BatchFrame::data(batch)];
6285 let keys = vec!["k".to_owned()];
6286 let mut partitions = Vec::new();
6287 for index in 0..3 {
6288 partitions.push(repartition_frames(&frames, &keys, 3, index).unwrap());
6289 }
6290 let total: usize = partitions
6291 .iter()
6292 .map(|frames| {
6293 frames
6294 .iter()
6295 .map(|frame| frame.batch.num_rows())
6296 .sum::<usize>()
6297 })
6298 .sum();
6299 assert_eq!(total, 100, "every row lands in exactly one partition");
6300 for (index, partition) in partitions.iter().enumerate() {
6303 for other in 0..3 {
6304 let rerouted = repartition_frames(partition, &keys, 3, other).unwrap();
6305 let rows: usize = rerouted.iter().map(|frame| frame.batch.num_rows()).sum();
6306 if other == index {
6307 let expected: usize =
6308 partition.iter().map(|frame| frame.batch.num_rows()).sum();
6309 assert_eq!(rows, expected, "partition {index} rows stay in {index}");
6310 } else {
6311 assert_eq!(rows, 0, "partition {index} leaks rows into {other}");
6312 }
6313 }
6314 }
6315 }
6316
6317 #[test]
6318 fn score_key_mapping_preserves_order() {
6319 let ints = Int64Array::from(vec![i64::MIN, -5, 0, 5, i64::MAX]);
6320 let keys: Vec<u64> = (0..5).map(|row| score_key(&ints, row).unwrap()).collect();
6321 let mut sorted = keys.clone();
6322 sorted.sort_unstable();
6323 assert_eq!(keys, sorted, "int64 mapping is order-preserving");
6324 let floats = Float64Array::from(vec![f64::MIN, -1.5, 0.0, 2.5, f64::MAX]);
6325 let keys: Vec<u64> = (0..5).map(|row| score_key(&floats, row).unwrap()).collect();
6326 let mut sorted = keys.clone();
6327 sorted.sort_unstable();
6328 assert_eq!(keys, sorted, "float64 mapping is order-preserving");
6329 }
6330}