linkrs_query/executor/streaming/
engine.rs1use std::sync::Arc;
11
12use super::chunk::DataChunk;
13use super::executor::{SortDirection, StreamingExecutor};
14use super::operators::base::OperatorBase;
15use super::operators::blocking::BlockingOperator;
16use super::operators::blocking::BlockingOperatorKind;
17use super::operators::gather_operator::GatherOperator;
18use super::runtime::ExecutionRuntime;
19use super::stream::ResultStream;
20use crate::executor::base::{MemoryBudget, MemoryTracker};
21use crate::executor::streaming::plan::types::SyntheticNodeIdAllocator;
22use crate::executor::streaming::pool::MorselWorkerPool;
23use crate::executor::streaming::spill::{SpillConfig, SpillManager};
24use linkrs_core::error::QueryError;
25use linkrs_core::types::expr::Expression;
26
27pub struct StreamingExecutionEngine {
40 root_executor: Option<Box<StreamingExecutor>>,
41 partition_executors: Vec<StreamingExecutor>,
43 partition_count: usize,
46 max_workers: usize,
49 max_buffered_chunks: usize,
51 runtime: Option<Arc<ExecutionRuntime>>,
52 synthetic_id_alloc: SyntheticNodeIdAllocator,
55 spill_config: SpillConfig,
57}
58
59impl StreamingExecutionEngine {
60 pub fn new() -> Self {
62 Self {
63 root_executor: None,
64 partition_executors: Vec::new(),
65 partition_count: 0,
66 max_workers: 1,
67 max_buffered_chunks: 10,
68 runtime: None,
69 synthetic_id_alloc: SyntheticNodeIdAllocator::new(),
70 spill_config: SpillConfig::default(),
71 }
72 }
73
74 pub fn set_max_workers(&mut self, max_workers: usize) {
80 self.max_workers = max_workers.max(1);
81 if let Some(rt) = &self.runtime {
82 if rt.get_shared_scheduler().is_some() {
83 return;
84 }
85 if self.max_workers > 1 {
86 let pool = MorselWorkerPool::new(self.max_workers);
87 rt.set_worker_pool(Some(pool));
88 } else {
89 rt.set_worker_pool(None);
90 }
91 }
92 }
93
94 pub fn max_workers(&self) -> usize {
96 self.max_workers
97 }
98
99 pub fn set_spill_config(&mut self, config: SpillConfig) {
101 self.spill_config = config;
102 if let Some(rt) = &self.runtime {
103 let qid = rt.query_id().query_id;
104 if let Ok(manager) = SpillManager::new(self.spill_config.clone(), qid) {
105 rt.set_spill_manager(Some(Arc::new(manager)));
106 }
107 }
108 }
109
110 pub fn set_max_buffered_chunks(&mut self, max_buffered_chunks: usize) {
112 self.max_buffered_chunks = max_buffered_chunks.max(1);
113 if let Some(rt) = &self.runtime {
114 rt.set_max_buffered_chunks(self.max_buffered_chunks);
115 }
116 }
117
118 pub fn register_executor(&mut self, _executor_id: usize, executor: StreamingExecutor) {
124 if let Some(ref rt) = self.runtime {
125 let mut boxed = Box::new(executor);
127 boxed.set_runtime(Some(rt.clone()));
128 self.root_executor = Some(boxed);
129 } else {
130 self.root_executor = Some(Box::new(executor));
131 }
132 self.partition_count = 0;
133 self.configure_registered_root_parallelism();
134 }
135
136 pub fn register_partitioned_root(
141 &mut self,
142 partition_count: usize,
143 mut executor: StreamingExecutor,
144 ) -> Result<(), QueryError> {
145 if partition_count == 0 {
146 return Err(QueryError::execution(
147 "Partitioned root requires at least one partition".to_string(),
148 ));
149 }
150 if let Some(ref runtime) = self.runtime {
151 executor.set_runtime(Some(runtime.clone()));
152 }
153 self.root_executor = Some(Box::new(executor));
154 self.partition_executors.clear();
155 self.partition_count = partition_count;
156 self.ensure_partition_arenas();
157 self.configure_registered_root_parallelism();
158 Ok(())
159 }
160
161 pub fn register_partition_executors(&mut self, executors: Vec<StreamingExecutor>) {
168 if let Some(ref rt) = self.runtime {
169 let mut injected = executors;
170 for (partition_id, ex) in injected.iter_mut().enumerate() {
171 ex.set_partition_id(partition_id);
172 ex.set_runtime(Some(rt.clone()));
173 }
174 self.partition_executors = injected;
175 } else {
176 let mut partitioned = executors;
177 for (partition_id, executor) in partitioned.iter_mut().enumerate() {
178 executor.set_partition_id(partition_id);
179 }
180 self.partition_executors = partitioned;
181 }
182 self.root_executor = None;
183 self.partition_count = self.partition_executors.len();
184 self.ensure_partition_arenas();
185 }
186
187 pub fn build_partitioned_executor(
199 &mut self,
200 mut local_trees: Vec<StreamingExecutor>,
201 gather_mode: GatherOperator,
202 global_tree: Option<StreamingExecutor>,
203 ) -> Result<(), QueryError> {
204 if local_trees.is_empty() {
205 return Err(QueryError::execution(
206 "Partitioned execution requires at least one local tree".to_string(),
207 ));
208 }
209 let partition_count = local_trees.len();
210 let output_layout = Arc::clone(&local_trees[0].base().output_layout);
211 for (partition_id, tree) in local_trees.iter_mut().enumerate() {
212 tree.set_partition_id(partition_id);
213 }
214 let gather_id = self.synthetic_id_alloc.allocate();
215 let mut gather = StreamingExecutor::Gather(
216 OperatorBase::new(gather_id)
217 .with_global(true)
218 .with_output_layout(output_layout),
219 local_trees,
220 gather_mode,
221 );
222
223 if let Some(ref rt) = self.runtime {
224 gather.set_runtime(Some(rt.clone()));
225 }
226
227 if let Some(mut global) = global_tree {
228 global.set_global();
229 if let Some(ref rt) = self.runtime {
230 global.set_runtime(Some(rt.clone()));
231 }
232 Self::attach_gather_as_child(&mut global, gather)?;
233 self.root_executor = Some(Box::new(global));
234 } else {
235 self.root_executor = Some(Box::new(gather));
236 }
237 self.partition_executors.clear();
238 self.partition_count = partition_count;
239 self.ensure_partition_arenas();
240 self.configure_registered_root_parallelism();
241 Ok(())
242 }
243
244 pub fn build_partitioned_sort_executor(
251 &mut self,
252 local_trees: Vec<StreamingExecutor>,
253 sort_expressions: Vec<Expression>,
254 sort_directions: Vec<SortDirection>,
255 limit: Option<usize>,
256 ) -> Result<(), QueryError> {
257 if sort_expressions.len() != sort_directions.len() {
258 return Err(QueryError::execution(
259 "Partitioned sort requires one direction per sort expression".to_string(),
260 ));
261 }
262 let budget = self
263 .runtime
264 .as_ref()
265 .map(|runtime| runtime.memory_budget.clone())
266 .unwrap_or_else(MemoryBudget::default_budget);
267 let local_sort_id = self.synthetic_id_alloc.allocate();
268 let local_sorts = local_trees
269 .into_iter()
270 .map(|input| {
271 let output_layout = Arc::clone(&input.base().output_layout);
272 StreamingExecutor::Blocking(
273 OperatorBase::new(local_sort_id).with_output_layout(output_layout.clone()),
274 Box::new(input),
275 BlockingOperator::new(
276 BlockingOperatorKind::Sort {
277 sort_expressions: sort_expressions.clone(),
278 sort_directions: sort_directions.clone(),
279 memory_tracker: MemoryTracker::new(budget.clone()),
280 state: None,
281 },
282 output_layout,
283 ),
284 )
285 })
286 .collect::<Vec<_>>();
287 let gather_layout = local_sorts
288 .first()
289 .map(|tree| Arc::clone(&tree.base().output_layout))
290 .ok_or_else(|| {
291 QueryError::execution(
292 "Partitioned sort requires at least one local tree".to_string(),
293 )
294 })?;
295 self.build_partitioned_executor(
296 local_sorts,
297 GatherOperator::merge_sort(sort_expressions, sort_directions, limit, gather_layout),
298 None,
299 )
300 }
301
302 pub fn build_partitioned_join_executor(
309 &mut self,
310 mut left_local_trees: Vec<StreamingExecutor>,
311 mut right_local_trees: Vec<StreamingExecutor>,
312 mut global_join: StreamingExecutor,
313 ) -> Result<(), QueryError> {
314 if left_local_trees.is_empty() || right_local_trees.is_empty() {
315 return Err(QueryError::execution(
316 "Partitioned join requires at least one partition on both inputs".to_string(),
317 ));
318 }
319 if left_local_trees.len() != right_local_trees.len() {
320 return Err(QueryError::execution(format!(
321 "Partitioned join input partition counts differ: left={}, right={}",
322 left_local_trees.len(),
323 right_local_trees.len()
324 )));
325 }
326
327 let partition_count = left_local_trees.len();
328 let left_layout = Arc::clone(&left_local_trees[0].base().output_layout);
329 let right_layout = Arc::clone(&right_local_trees[0].base().output_layout);
330 for (partition_id, tree) in left_local_trees.iter_mut().enumerate() {
331 tree.set_partition_id(partition_id);
332 }
333 for (partition_id, tree) in right_local_trees.iter_mut().enumerate() {
334 tree.set_partition_id(partition_id);
335 }
336
337 let left_gather_id = self.synthetic_id_alloc.allocate();
338 let right_gather_id = self.synthetic_id_alloc.allocate();
339 let mut left_gather = StreamingExecutor::Gather(
340 OperatorBase::new(left_gather_id)
341 .with_global(true)
342 .with_output_layout(left_layout.clone()),
343 left_local_trees,
344 GatherOperator::concatenate(left_layout),
345 );
346 let mut right_gather = StreamingExecutor::Gather(
347 OperatorBase::new(right_gather_id)
348 .with_global(true)
349 .with_output_layout(right_layout.clone()),
350 right_local_trees,
351 GatherOperator::concatenate(right_layout),
352 );
353 if let Some(ref runtime) = self.runtime {
354 left_gather.set_runtime(Some(runtime.clone()));
355 right_gather.set_runtime(Some(runtime.clone()));
356 }
357
358 global_join.set_global();
359 if let Some(ref runtime) = self.runtime {
360 global_join.set_runtime(Some(runtime.clone()));
361 }
362 Self::attach_gathers_as_join_children(&mut global_join, left_gather, right_gather)?;
363
364 self.root_executor = Some(Box::new(global_join));
365 self.partition_executors.clear();
366 self.partition_count = partition_count;
367 self.ensure_partition_arenas();
368 self.configure_registered_root_parallelism();
369 Ok(())
370 }
371
372 fn attach_gather_as_child(
374 root: &mut StreamingExecutor,
375 gather: StreamingExecutor,
376 ) -> Result<(), QueryError> {
377 match root {
378 StreamingExecutor::Unary(_, input, _)
379 | StreamingExecutor::Blocking(_, input, _)
380 | StreamingExecutor::Graph(_, input, _)
381 | StreamingExecutor::RecursiveFragment(_, input, _)
382 | StreamingExecutor::Sink(_, input, _)
383 | StreamingExecutor::Ddl(_, input, _)
384 | StreamingExecutor::Fulltext(_, input, _)
385 | StreamingExecutor::Vector(_, input, _)
386 | StreamingExecutor::Txn(_, input, _) => {
387 **input = gather;
388 Ok(())
389 }
390 StreamingExecutor::Join(_, left, _, _) => {
391 **left = gather;
392 Ok(())
393 }
394 _ => Err(QueryError::execution(
395 "The requested global executor cannot accept a Gather input".to_string(),
396 )),
397 }
398 }
399
400 fn attach_gathers_as_join_children(
401 root: &mut StreamingExecutor,
402 left_gather: StreamingExecutor,
403 right_gather: StreamingExecutor,
404 ) -> Result<(), QueryError> {
405 match root {
406 StreamingExecutor::Join(_, left, right, _) => {
407 **left = left_gather;
408 **right = right_gather;
409 Ok(())
410 }
411 _ => Err(QueryError::execution(
412 "Partitioned join requires a Join executor as its global root".to_string(),
413 )),
414 }
415 }
416
417 pub fn partition_count(&self) -> usize {
419 self.partition_count
420 }
421
422 fn ensure_partition_arenas(&mut self) {
425 if let Some(arc) = self.runtime.as_mut() {
426 if let Some(rt) = Arc::get_mut(arc) {
427 rt.set_partition_count(self.partition_count + 1);
428 }
429 }
430 }
431
432 pub fn set_runtime(&mut self, runtime: Arc<ExecutionRuntime>) {
437 if let Some(ref mut executor) = self.root_executor {
438 executor.set_runtime(Some(runtime.clone()));
439 }
440 for executor in &mut self.partition_executors {
441 executor.set_runtime(Some(runtime.clone()));
442 }
443 if self.max_workers > 1 && runtime.get_shared_scheduler().is_none() {
444 let pool = MorselWorkerPool::new(self.max_workers);
445 runtime.set_worker_pool(Some(pool));
446 } else if self.max_workers == 1 && runtime.get_shared_scheduler().is_none() {
447 runtime.set_worker_pool(None);
448 }
449 runtime.set_max_buffered_chunks(self.max_buffered_chunks);
450 if runtime.get_spill_manager().is_none() {
451 let qid = runtime.query_id().query_id;
452 if let Ok(manager) = SpillManager::new(self.spill_config.clone(), qid) {
453 runtime.set_spill_manager(Some(Arc::new(manager)));
454 }
455 }
456 self.runtime = Some(runtime);
457 self.ensure_partition_arenas();
460 }
461
462 fn configure_registered_root_parallelism(&mut self) {
463 }
466
467 pub fn runtime(&self) -> Option<&Arc<ExecutionRuntime>> {
469 self.runtime.as_ref()
470 }
471
472 pub fn open_root(&mut self) -> Result<(), QueryError> {
474 let executor = self
475 .root_executor
476 .as_mut()
477 .ok_or_else(|| QueryError::execution("No executor registered".to_string()))?;
478 executor.open()
479 }
480
481 pub fn next_chunk_from_root(&mut self) -> Result<Option<DataChunk>, QueryError> {
483 let executor = self
484 .root_executor
485 .as_mut()
486 .ok_or_else(|| QueryError::execution("No executor registered".to_string()))?;
487 let mut chunk = executor.advance()?;
488 if let Some(chunk) = chunk.as_mut() {
493 chunk.normalize_for_opaque("Root");
494 }
495 Ok(chunk)
496 }
497
498 pub fn stop_root(&mut self) -> Result<(), QueryError> {
503 let mut first_error = None;
504 if let Some(ref mut executor) = self.root_executor {
505 if let Err(error) = executor.stop_tree() {
506 first_error = Some(error);
507 }
508 }
509 for executor in &mut self.partition_executors {
510 if let Err(error) = executor.stop_tree() {
511 if first_error.is_none() {
512 first_error = Some(error);
513 }
514 }
515 }
516 first_error.map_or(Ok(()), Err)
517 }
518
519 pub fn close_root(&mut self) -> Result<(), QueryError> {
521 let result = if let Some(ref mut executor) = self.root_executor {
522 executor.close_tree()
523 } else {
524 Ok(())
525 };
526 if let Some(ref runtime) = self.runtime {
527 runtime.release_resources();
528 runtime.reset_arena();
529 }
530 result
531 }
532
533 pub fn execute(mut self) -> Result<ResultStream, QueryError> {
549 if !self.partition_executors.is_empty() {
550 let chunks = self.execute_collected()?;
551 let runtime = self
552 .runtime
553 .take()
554 .ok_or_else(|| QueryError::execution("No ExecutionRuntime attached".to_string()))?;
555 runtime.profile_start();
556 return Ok(ResultStream::from_collected(chunks, self, runtime));
557 }
558 self.into_stream()
559 }
560
561 pub fn execute_collected(&mut self) -> Result<Vec<DataChunk>, QueryError> {
566 let profile_started = self.runtime.is_some();
567 if profile_started {
568 self.runtime.as_ref().unwrap().profile_start();
569 }
570
571 let result = if !self.partition_executors.is_empty() {
572 self.execute_partitions()
573 } else {
574 self.execute_single()
575 };
576
577 if result.is_err() {
579 self.close_open_executors();
580 }
581
582 if profile_started {
584 if let Some(ref rt) = self.runtime {
585 rt.profile_end();
586 rt.release_resources();
587 rt.reset_arena();
588 }
589 }
590
591 if result.is_ok() {
593 for executor in self
594 .partition_executors
595 .iter()
596 .chain(self.root_executor.iter().map(|e| e.as_ref()))
597 {
598 let peak = extract_peak_memory(executor);
599 if peak > 0 {
600 executor.record_profile_peak_memory(peak);
601 }
602 }
603 }
604
605 result
606 }
607
608 fn close_open_executors(&mut self) {
611 if let Some(ref mut executor) = self.root_executor {
612 let _ = executor.close_tree();
613 }
614 for executor in &mut self.partition_executors {
615 let _ = executor.close_tree();
616 }
617 }
618
619 fn execute_partitions(&mut self) -> Result<Vec<DataChunk>, QueryError> {
628 let mut all_chunks = Vec::new();
629 for executor in &mut self.partition_executors {
630 executor.open()?;
631 let loop_result = (|| -> Result<(), QueryError> {
632 while let Some(mut chunk) = executor.advance()? {
633 chunk.normalize_for_opaque("Engine");
635 all_chunks.push(chunk);
636 }
637 Ok(())
638 })();
639 let close_err = executor.close_tree().err();
640 loop_result?;
642 if let Some(e) = close_err {
643 return Err(e);
644 }
645 }
646 Ok(all_chunks)
647 }
648
649 fn execute_single(&mut self) -> Result<Vec<DataChunk>, QueryError> {
654 let mut output_chunks = Vec::new();
655
656 let executor = self
657 .root_executor
658 .as_mut()
659 .ok_or_else(|| QueryError::execution("No executor registered".to_string()))?;
660
661 executor.open()?;
662 let loop_result = (|| -> Result<(), QueryError> {
663 while let Some(mut chunk) = executor.advance()? {
664 chunk.normalize_for_opaque("Engine");
666 output_chunks.push(chunk);
667 }
668 Ok(())
669 })();
670 let close_err = executor.close_tree().err();
671 loop_result?;
673 if let Some(e) = close_err {
674 return Err(e);
675 }
676
677 Ok(output_chunks)
678 }
679
680 pub fn into_stream(mut self) -> Result<ResultStream, QueryError> {
685 let runtime = self
686 .runtime
687 .take()
688 .ok_or_else(|| QueryError::execution("No ExecutionRuntime attached".to_string()))?;
689 runtime.profile_start();
690 Ok(ResultStream::new(self, runtime))
691 }
692}
693
694fn extract_peak_memory(executor: &StreamingExecutor) -> u64 {
696 executor.peak_memory_bytes()
697}
698
699impl Default for StreamingExecutionEngine {
700 fn default() -> Self {
701 Self::new()
702 }
703}
704
705#[cfg(test)]
706mod tests;