1use std::collections::{HashMap, HashSet};
17use std::sync::Arc;
18use std::sync::atomic::AtomicBool;
19
20use api::v1::flow::{
21 CreateRequest, DirtyWindowRequests, DropRequest, FlowRequest, FlowResponse, FlushFlow,
22 flow_request,
23};
24use api::v1::region::InsertRequests;
25use catalog::CatalogManager;
26use common_base::Plugins;
27use common_error::ext::BoxedError;
28use common_meta::ddl::create_flow::{
29 FlowType, INTERNAL_EVAL_SCHEDULE_KEY, effective_eval_schedule_from_flow_info,
30};
31use common_meta::error::Result as MetaResult;
32use common_meta::key::flow::FlowMetadataManager;
33use common_meta::key::flow::flow_info::FlowScheduleConfig;
34use common_meta::key::flow::flow_state::FlowStat;
35use common_runtime::JoinHandle;
36use common_telemetry::{error, info, warn};
37use futures::TryStreamExt;
38use itertools::Itertools;
39use operator::utils::try_to_session_query_context;
40use session::context::QueryContextBuilder;
41use snafu::{OptionExt, ResultExt, ensure};
42use store_api::storage::{RegionId, TableId};
43use tokio::sync::{Mutex, RwLock};
44
45use crate::adapter::{CreateFlowArgs, StreamingEngine};
46use crate::batching_mode::engine::BatchingEngine;
47use crate::engine::{FlowEngine, FlowStatProvider};
48use crate::error::{
49 CreateFlowSnafu, ExternalSnafu, FlowNotFoundSnafu, FlowNotRecoveredSnafu,
50 IllegalCheckTaskStateSnafu, InternalSnafu, JoinTaskSnafu, ListFlowsSnafu,
51 NoAvailableFrontendSnafu, SyncCheckTaskSnafu, UnsupportedSnafu,
52};
53use crate::metrics::{METRIC_FLOW_ROWS, METRIC_FLOW_TASK_COUNT};
54use crate::utils::StateReportHandler;
55use crate::{Error, FlowId};
56
57pub type FlowDualEngineRef = Arc<FlowDualEngine>;
59
60pub struct FlowDualEngine {
65 streaming_engine: Arc<StreamingEngine>,
66 batching_engine: Arc<BatchingEngine>,
67 state_report_handler: RwLock<Option<StateReportHandler>>,
69 src_table2flow: RwLock<SrcTableToFlow>,
71 flow_metadata_manager: Arc<FlowMetadataManager>,
72 catalog_manager: Arc<dyn CatalogManager>,
73 check_task: tokio::sync::Mutex<Option<ConsistentCheckTask>>,
74 plugins: Plugins,
75 done_recovering: AtomicBool,
76}
77
78impl FlowDualEngine {
79 pub fn new(
80 streaming_engine: Arc<StreamingEngine>,
81 batching_engine: Arc<BatchingEngine>,
82 flow_metadata_manager: Arc<FlowMetadataManager>,
83 catalog_manager: Arc<dyn CatalogManager>,
84 plugins: Plugins,
85 ) -> Self {
86 Self {
87 streaming_engine,
88 batching_engine,
89 state_report_handler: Default::default(),
90 src_table2flow: RwLock::new(SrcTableToFlow::default()),
91 flow_metadata_manager,
92 catalog_manager,
93 check_task: Mutex::new(None),
94 plugins,
95 done_recovering: AtomicBool::new(false),
96 }
97 }
98
99 pub fn set_done_recovering(&self) {
102 info!("FlowDualEngine done recovering");
103 self.done_recovering
104 .store(true, std::sync::atomic::Ordering::Release);
105 }
106
107 pub fn is_recover_done(&self) -> bool {
109 self.done_recovering
110 .load(std::sync::atomic::Ordering::Acquire)
111 }
112
113 async fn wait_for_all_flow_recover(&self, waiting_req_cnt: usize) -> Result<(), Error> {
115 if self.is_recover_done() {
116 return Ok(());
117 }
118
119 warn!(
120 "FlowDualEngine is not done recovering, {} insert request waiting for recovery",
121 waiting_req_cnt
122 );
123 let mut retry = 0;
126 let max_retry = 3;
127 while retry < max_retry && !self.is_recover_done() {
128 warn!(
129 "FlowDualEngine is not done recovering, retry {} in 1s",
130 retry
131 );
132 tokio::time::sleep(std::time::Duration::from_secs(1)).await;
133 retry += 1;
134 }
135 if retry == max_retry {
136 return FlowNotRecoveredSnafu.fail();
137 } else {
138 info!("FlowDualEngine is done recovering");
139 }
140 Ok(())
142 }
143
144 pub fn plugins(&self) -> &Plugins {
145 &self.plugins
146 }
147
148 pub fn is_distributed(&self) -> bool {
150 self.streaming_engine.node_id.is_some()
151 }
152
153 pub fn streaming_engine(&self) -> Arc<StreamingEngine> {
154 self.streaming_engine.clone()
155 }
156
157 pub fn batching_engine(&self) -> Arc<BatchingEngine> {
158 self.batching_engine.clone()
159 }
160
161 pub async fn set_state_report_handler(&self, handler: StateReportHandler) {
162 *self.state_report_handler.write().await = Some(handler);
163 }
164
165 pub async fn gen_state_report(&self) -> FlowStat {
166 self.batching_engine.flow_stat().await
167 }
168
169 pub async fn start_state_report_task(self: Arc<Self>) -> Option<JoinHandle<()>> {
173 let state_report_handler = self.state_report_handler.write().await.take();
174 if let Some(mut handler) = state_report_handler {
175 let zelf = self.clone();
176 let handler = common_runtime::spawn_global(async move {
177 while let Some(ret_handler) = handler.recv().await {
178 let state_report = zelf.gen_state_report().await;
179 ret_handler.send(state_report).unwrap_or_else(|err| {
180 common_telemetry::error!(err; "Send state report error");
181 });
182 }
183 });
184 Some(handler)
185 } else {
186 None
187 }
188 }
189
190 async fn wait_for_available_frontend(&self, timeout: std::time::Duration) -> Result<(), Error> {
194 if !self.is_distributed() {
195 return Ok(());
196 }
197 let frontend_client = self.batching_engine().frontend_client.clone();
198 let sleep_duration = std::time::Duration::from_millis(1_000);
199 let now = std::time::Instant::now();
200 loop {
201 let frontend_list = frontend_client.scan_for_frontend().await?;
202 if !frontend_list.is_empty() {
203 let fe_list = frontend_list
204 .iter()
205 .map(|peer| &peer.addr)
206 .collect::<Vec<_>>();
207 let probe_failures = frontend_client
208 .check_all_frontends_without_auth(&frontend_list)
209 .await?;
210 if probe_failures.is_empty() {
211 info!(
212 "Available frontend found and unauthenticated probe succeeded: {:?}",
213 fe_list
214 );
215 return Ok(());
216 }
217 warn!(
218 "Unauthenticated frontend probe failed, will retry. frontends={:?}, failures={:?}",
219 fe_list, probe_failures
220 );
221 }
222 let elapsed = now.elapsed();
223 tokio::time::sleep(sleep_duration).await;
224 info!("Waiting for available frontend, elapsed={:?}", elapsed);
225 if elapsed >= timeout {
226 return NoAvailableFrontendSnafu {
227 timeout,
228 context: "No frontend accepted unauthenticated flownode probe",
229 }
230 .fail();
231 }
232 }
233 }
234
235 async fn try_sync_with_check_task(
239 &self,
240 flow_id: FlowId,
241 allow_drop: bool,
242 ) -> Result<(), Error> {
243 info!("Try to sync with check task for flow {}", flow_id);
245 let mut retry = 0;
246 let max_retry = 10;
247 while retry < max_retry {
249 if let Some(task) = self.check_task.lock().await.as_ref() {
250 task.trigger(false, allow_drop).await?;
251 break;
252 }
253 retry += 1;
254 tokio::time::sleep(std::time::Duration::from_millis(500)).await;
255 }
256
257 if retry == max_retry {
258 error!(
259 "Can't sync with check task for flow {} with allow_drop={}",
260 flow_id, allow_drop
261 );
262 return SyncCheckTaskSnafu {
263 flow_id,
264 allow_drop,
265 }
266 .fail();
267 }
268 info!("Successfully sync with check task for flow {}", flow_id);
269
270 Ok(())
271 }
272
273 async fn check_flow_consistent(
276 &self,
277 allow_create: bool,
278 allow_drop: bool,
279 ) -> Result<(), Error> {
280 let nodeid = self.streaming_engine.node_id;
282 let should_exists: Vec<_> = if let Some(nodeid) = nodeid {
283 let to_be_recover = self
286 .flow_metadata_manager
287 .flownode_flow_manager()
288 .flows(nodeid.into())
289 .try_collect::<Vec<_>>()
290 .await
291 .context(ListFlowsSnafu {
292 id: Some(nodeid.into()),
293 })?;
294 to_be_recover.into_iter().map(|(id, _)| id).collect()
295 } else {
296 let all_catalogs = self
299 .catalog_manager
300 .catalog_names()
301 .await
302 .map_err(BoxedError::new)
303 .context(ExternalSnafu)?;
304 let mut all_flow_ids = vec![];
305 for catalog in all_catalogs {
306 let flows = self
307 .flow_metadata_manager
308 .flow_name_manager()
309 .flow_names(&catalog)
310 .await
311 .try_collect::<Vec<_>>()
312 .await
313 .map_err(BoxedError::new)
314 .context(ExternalSnafu)?;
315
316 all_flow_ids.extend(flows.into_iter().map(|(_, id)| id.flow_id()));
317 }
318 all_flow_ids
319 };
320 let should_exists = should_exists
321 .into_iter()
322 .map(|i| i as FlowId)
323 .collect::<HashSet<_>>();
324 let actual_exists = self.list_flows().await?.into_iter().collect::<HashSet<_>>();
325 let to_be_created = should_exists
326 .iter()
327 .filter(|id| !actual_exists.contains(id))
328 .collect::<Vec<_>>();
329 let to_be_dropped = actual_exists
330 .iter()
331 .filter(|id| !should_exists.contains(id))
332 .collect::<Vec<_>>();
333
334 if !to_be_created.is_empty() {
335 if allow_create {
336 info!(
337 "Recovering {} flows: {:?}",
338 to_be_created.len(),
339 to_be_created
340 );
341 let mut errors = vec![];
342 for flow_id in to_be_created.clone() {
343 let flow_id = *flow_id;
344 let info = self
345 .flow_metadata_manager
346 .flow_info_manager()
347 .get(flow_id as u32)
348 .await
349 .map_err(BoxedError::new)
350 .context(ExternalSnafu)?
351 .context(FlowNotFoundSnafu { id: flow_id })?;
352
353 let sink_table_name = [
354 info.sink_table_name().catalog_name.clone(),
355 info.sink_table_name().schema_name.clone(),
356 info.sink_table_name().table_name.clone(),
357 ];
358 let args = CreateFlowArgs {
359 flow_id,
360 sink_table_name,
361 source_table_ids: info.source_table_ids().to_vec(),
362 create_if_not_exists: true,
366 or_replace: true,
367 expire_after: info.expire_after(),
368 eval_interval: info.eval_interval(),
369 comment: Some(info.comment().clone()),
370 sql: info.raw_sql().clone(),
371 flow_options: info.options().clone(),
372 eval_schedule: effective_eval_schedule_from_flow_info(&info)
373 .map_err(BoxedError::new)
374 .context(ExternalSnafu)?,
375 query_ctx: info
376 .query_context()
377 .clone()
378 .map(|ctx| {
379 try_to_session_query_context(ctx)
380 .map_err(BoxedError::new)
381 .context(ExternalSnafu)
382 })
383 .transpose()?
384 .or_else(|| {
387 Some(
388 QueryContextBuilder::default()
389 .current_catalog(info.catalog_name().clone())
390 .build(),
391 )
392 }),
393 };
394 if let Err(err) = self
395 .create_flow(args)
396 .await
397 .map_err(BoxedError::new)
398 .with_context(|_| CreateFlowSnafu {
399 sql: info.raw_sql().clone(),
400 })
401 {
402 errors.push((flow_id, err));
403 }
404 }
405 if errors.is_empty() {
406 info!("Recover flows successfully, flows: {:?}", to_be_created);
407 }
408
409 for (flow_id, err) in errors {
410 warn!("Failed to recreate flow {}, err={:#?}", flow_id, err);
411 }
412 } else {
413 warn!(
414 "Flows do not exist in flownode for node {:?}, flow_ids={:?}",
415 nodeid, to_be_created
416 );
417 }
418 }
419 if !to_be_dropped.is_empty() {
420 if allow_drop {
421 info!("Dropping flows: {:?}", to_be_dropped);
422 let mut errors = vec![];
423 for flow_id in to_be_dropped {
424 let flow_id = *flow_id;
425 if let Err(err) = self.remove_flow(flow_id).await {
426 errors.push((flow_id, err));
427 }
428 }
429 for (flow_id, err) in errors {
430 warn!("Failed to drop flow {}, err={:#?}", flow_id, err);
431 }
432 } else {
433 warn!(
434 "Flows do not exist in metadata for node {:?}, flow_ids={:?}",
435 nodeid, to_be_dropped
436 );
437 }
438 }
439 Ok(())
440 }
441
442 pub async fn start_flow_consistent_check_task(self: &Arc<Self>) -> Result<(), Error> {
444 let mut check_task = self.check_task.lock().await;
445 ensure!(
446 check_task.is_none(),
447 IllegalCheckTaskStateSnafu {
448 reason: "Flow consistent check task already exists",
449 }
450 );
451 let task = ConsistentCheckTask::start_check_task(self).await?;
452 *check_task = Some(task);
453 Ok(())
454 }
455
456 pub async fn stop_flow_consistent_check_task(&self) -> Result<(), Error> {
457 info!("Stopping flow consistent check task");
458 let mut check_task = self.check_task.lock().await;
459
460 ensure!(
461 check_task.is_some(),
462 IllegalCheckTaskStateSnafu {
463 reason: "Flow consistent check task does not exist",
464 }
465 );
466
467 check_task.take().unwrap().stop().await?;
468 info!("Stopped flow consistent check task");
469 Ok(())
470 }
471
472 pub async fn reconcile_flows_from_metadata(&self) -> Result<(), Error> {
474 self.check_flow_consistent(true, true).await
475 }
476
477 async fn flow_exist_in_metadata(&self, flow_id: FlowId) -> Result<bool, Error> {
479 self.flow_metadata_manager
480 .flow_info_manager()
481 .get(flow_id as u32)
482 .await
483 .map_err(BoxedError::new)
484 .context(ExternalSnafu)
485 .map(|info| info.is_some())
486 }
487}
488
489struct ConsistentCheckTask {
490 handle: JoinHandle<()>,
491 shutdown_tx: tokio::sync::mpsc::Sender<()>,
492 trigger_tx: tokio::sync::mpsc::Sender<(bool, bool, tokio::sync::oneshot::Sender<()>)>,
493}
494
495impl ConsistentCheckTask {
496 async fn start_check_task(engine: &Arc<FlowDualEngine>) -> Result<Self, Error> {
497 let engine = engine.clone();
498 let min_refresh_duration = engine
499 .batching_engine()
500 .batch_opts
501 .experimental_min_refresh_duration;
502 let frontend_scan_timeout = engine
503 .batching_engine()
504 .batch_opts
505 .experimental_frontend_scan_timeout;
506 engine
507 .wait_for_available_frontend(frontend_scan_timeout)
508 .await?;
509 let (tx, mut rx) = tokio::sync::mpsc::channel(1);
510 let (trigger_tx, mut trigger_rx) =
511 tokio::sync::mpsc::channel::<(bool, bool, tokio::sync::oneshot::Sender<()>)>(10);
512 let handle = common_runtime::spawn_global(async move {
513 let mut recover_retry = 0;
515 while let Err(err) = engine.check_flow_consistent(true, false).await {
516 recover_retry += 1;
517 error!(
518 "Failed to recover flows:\n {err:?}, retry {} in {}s",
519 recover_retry,
520 min_refresh_duration.as_secs()
521 );
522 tokio::time::sleep(min_refresh_duration).await;
523 }
524
525 engine.set_done_recovering();
526
527 let (mut allow_create, mut allow_drop) = (false, false);
529 let mut ret_signal: Option<tokio::sync::oneshot::Sender<()>> = None;
530 loop {
531 if let Err(err) = engine.check_flow_consistent(allow_create, allow_drop).await {
532 error!(err; "Failed to check flow consistent");
533 }
534 if let Some(done) = ret_signal.take() {
535 let _ = done.send(());
536 }
537 tokio::select! {
538 _ = rx.recv() => break,
539 incoming = trigger_rx.recv() => if let Some(incoming) = incoming {
540 (allow_create, allow_drop) = (incoming.0, incoming.1);
541 ret_signal = Some(incoming.2);
542 },
543 _ = tokio::time::sleep(std::time::Duration::from_secs(10)) => {
544 (allow_create, allow_drop) = (false, false);
545 },
546 }
547 }
548 });
549 Ok(ConsistentCheckTask {
550 handle,
551 shutdown_tx: tx,
552 trigger_tx,
553 })
554 }
555
556 async fn trigger(&self, allow_create: bool, allow_drop: bool) -> Result<(), Error> {
557 let (tx, rx) = tokio::sync::oneshot::channel();
558 self.trigger_tx
559 .send((allow_create, allow_drop, tx))
560 .await
561 .map_err(|_| {
562 IllegalCheckTaskStateSnafu {
563 reason: "Failed to send trigger signal",
564 }
565 .build()
566 })?;
567 rx.await.map_err(|_| {
568 IllegalCheckTaskStateSnafu {
569 reason: "Failed to receive trigger signal",
570 }
571 .build()
572 })?;
573 Ok(())
574 }
575
576 async fn stop(self) -> Result<(), Error> {
577 self.shutdown_tx.send(()).await.map_err(|_| {
578 IllegalCheckTaskStateSnafu {
579 reason: "Failed to send shutdown signal",
580 }
581 .build()
582 })?;
583 self.handle.abort();
585 Ok(())
586 }
587}
588
589#[derive(Default)]
590struct SrcTableToFlow {
591 stream: HashMap<TableId, HashSet<FlowId>>,
593 batch: HashMap<TableId, HashSet<FlowId>>,
595 flow_infos: HashMap<FlowId, (FlowType, Vec<TableId>)>,
597}
598
599impl SrcTableToFlow {
600 fn in_stream(&self, table_id: TableId) -> bool {
601 self.stream.contains_key(&table_id)
602 }
603 fn in_batch(&self, table_id: TableId) -> bool {
604 self.batch.contains_key(&table_id)
605 }
606 fn add_flow(&mut self, flow_id: FlowId, flow_type: FlowType, src_table_ids: Vec<TableId>) {
607 let mapping = match flow_type {
608 FlowType::Streaming => &mut self.stream,
609 FlowType::Batching => &mut self.batch,
610 };
611
612 for src_table in src_table_ids.clone() {
613 mapping
614 .entry(src_table)
615 .and_modify(|flows| {
616 flows.insert(flow_id);
617 })
618 .or_insert_with(|| {
619 let mut set = HashSet::new();
620 set.insert(flow_id);
621 set
622 });
623 }
624 self.flow_infos.insert(flow_id, (flow_type, src_table_ids));
625 }
626
627 fn remove_flow(&mut self, flow_id: FlowId) {
628 let mapping = match self.get_flow_type(flow_id) {
629 Some(FlowType::Streaming) => &mut self.stream,
630 Some(FlowType::Batching) => &mut self.batch,
631 None => return,
632 };
633 if let Some((_, src_table_ids)) = self.flow_infos.remove(&flow_id) {
634 for src_table in src_table_ids {
635 if let Some(flows) = mapping.get_mut(&src_table) {
636 flows.remove(&flow_id);
637 }
638 }
639 }
640 }
641
642 fn get_flow_type(&self, flow_id: FlowId) -> Option<FlowType> {
643 self.flow_infos
644 .get(&flow_id)
645 .map(|(flow_type, _)| flow_type)
646 .cloned()
647 }
648}
649
650impl FlowEngine for FlowDualEngine {
651 async fn create_flow(&self, args: CreateFlowArgs) -> Result<Option<FlowId>, Error> {
652 let flow_type = args
653 .flow_options
654 .get(FlowType::FLOW_TYPE_KEY)
655 .map(|s| s.as_str());
656
657 let flow_type = match flow_type {
658 Some(FlowType::BATCHING) => FlowType::Batching,
659 Some(FlowType::STREAMING) => FlowType::Streaming,
660 None => FlowType::Batching,
661 Some(flow_type) => {
662 return InternalSnafu {
663 reason: format!("Invalid flow type: {}", flow_type),
664 }
665 .fail();
666 }
667 };
668
669 let flow_id = args.flow_id;
670 let src_table_ids = args.source_table_ids.clone();
671
672 let res = match flow_type {
673 FlowType::Batching => self.batching_engine.create_flow(args).await,
674 FlowType::Streaming => self.streaming_engine.create_flow(args).await,
675 }?;
676
677 self.src_table2flow
678 .write()
679 .await
680 .add_flow(flow_id, flow_type, src_table_ids);
681
682 Ok(res)
683 }
684
685 async fn remove_flow(&self, flow_id: FlowId) -> Result<(), Error> {
686 let flow_type = self.src_table2flow.read().await.get_flow_type(flow_id);
687 let flow_type = match flow_type {
688 Some(flow_type) => Some(flow_type),
689 None if self.streaming_engine.flow_exist(flow_id).await? => Some(FlowType::Streaming),
690 None => None,
691 };
692
693 match flow_type {
694 Some(FlowType::Batching) => self.batching_engine.remove_flow(flow_id).await,
695 Some(FlowType::Streaming) => self.streaming_engine.remove_flow(flow_id).await,
696 None => {
697 warn!(
701 "Flow {} is not exist in the underlying engine, but exist in metadata",
702 flow_id
703 );
704 self.try_sync_with_check_task(flow_id, true).await?;
705
706 Ok(())
707 }
708 }?;
709 self.src_table2flow.write().await.remove_flow(flow_id);
711 Ok(())
712 }
713
714 async fn flush_flow(&self, flow_id: FlowId) -> Result<usize, Error> {
715 self.try_sync_with_check_task(flow_id, false).await?;
717 let flow_type = self.src_table2flow.read().await.get_flow_type(flow_id);
718 match flow_type {
719 Some(FlowType::Batching) => self.batching_engine.flush_flow(flow_id).await,
720 Some(FlowType::Streaming) => self.streaming_engine.flush_flow(flow_id).await,
721 None => {
722 warn!(
723 "Currently flow={flow_id} doesn't exist in flownode, ignore flush_flow request"
724 );
725 Ok(0)
726 }
727 }
728 }
729
730 async fn flow_exist(&self, flow_id: FlowId) -> Result<bool, Error> {
731 let flow_type = self.src_table2flow.read().await.get_flow_type(flow_id);
732 match flow_type {
735 Some(FlowType::Batching) => self.batching_engine.flow_exist(flow_id).await,
736 Some(FlowType::Streaming) => self.streaming_engine.flow_exist(flow_id).await,
737 None => self.streaming_engine.flow_exist(flow_id).await,
738 }
739 }
740
741 async fn list_flows(&self) -> Result<impl IntoIterator<Item = FlowId>, Error> {
742 let stream_flows = self.streaming_engine.list_flows().await?;
743 let batch_flows = self.batching_engine.list_flows().await?;
744
745 Ok(stream_flows.into_iter().chain(batch_flows))
746 }
747
748 async fn handle_flow_inserts(
749 &self,
750 request: api::v1::region::InsertRequests,
751 ) -> Result<(), Error> {
752 self.wait_for_all_flow_recover(request.requests.len())
753 .await?;
754 let mut to_stream_engine = Vec::with_capacity(request.requests.len());
756 let mut to_batch_engine = request.requests;
757
758 let mut batching_row_cnt = 0;
759 let mut streaming_row_cnt = 0;
760
761 {
762 let src_table2flow = self.src_table2flow.read().await;
764 to_batch_engine.retain(|req| {
765 let region_id = RegionId::from(req.region_id);
766 let table_id = region_id.table_id();
767 let is_in_stream = src_table2flow.in_stream(table_id);
768 let is_in_batch = src_table2flow.in_batch(table_id);
769 if is_in_stream {
770 streaming_row_cnt += req.rows.as_ref().map(|rs| rs.rows.len()).unwrap_or(0);
771 to_stream_engine.push(req.clone());
772 }
773 if is_in_batch {
774 batching_row_cnt += req.rows.as_ref().map(|rs| rs.rows.len()).unwrap_or(0);
775 return true;
776 }
777 if !is_in_batch && !is_in_stream {
778 warn!("Table {} is not any flow's source table", table_id)
780 }
781 false
782 });
783 }
786
787 METRIC_FLOW_ROWS
788 .with_label_values(&["in-streaming"])
789 .inc_by(streaming_row_cnt as u64);
790
791 METRIC_FLOW_ROWS
792 .with_label_values(&["in-batching"])
793 .inc_by(batching_row_cnt as u64);
794
795 let streaming_engine = self.streaming_engine.clone();
796 let stream_handler: JoinHandle<Result<(), Error>> =
797 common_runtime::spawn_global(async move {
798 streaming_engine
799 .handle_flow_inserts(api::v1::region::InsertRequests {
800 requests: to_stream_engine,
801 })
802 .await?;
803 Ok(())
804 });
805 self.batching_engine
806 .handle_flow_inserts(api::v1::region::InsertRequests {
807 requests: to_batch_engine,
808 })
809 .await?;
810 stream_handler.await.context(JoinTaskSnafu)??;
811
812 Ok(())
813 }
814
815 async fn handle_mark_window_dirty(
816 &self,
817 req: api::v1::flow::DirtyWindowRequests,
818 ) -> Result<(), Error> {
819 self.batching_engine.handle_mark_window_dirty(req).await
820 }
821}
822
823#[async_trait::async_trait]
824impl common_meta::node_manager::Flownode for FlowDualEngine {
825 async fn handle(&self, request: FlowRequest) -> MetaResult<FlowResponse> {
826 let query_ctx = request
827 .header
828 .and_then(|h| h.query_context)
829 .map(session::context::QueryContext::try_from)
830 .transpose()
831 .map_err(|err| {
832 to_meta_err(snafu::location!())(
833 crate::error::InvalidQuerySnafu {
834 reason: err.to_string(),
835 }
836 .build(),
837 )
838 })?;
839 match request.body {
840 Some(flow_request::Body::Create(CreateRequest {
841 flow_id: Some(task_id),
842 source_table_ids,
843 sink_table_name: Some(sink_table_name),
844 create_if_not_exists,
845 expire_after,
846 eval_interval,
847 comment,
848 sql,
849 mut flow_options,
850 or_replace,
851 })) => {
852 let source_table_ids = source_table_ids.into_iter().map(|id| id.id).collect_vec();
853 let sink_table_name = [
854 sink_table_name.catalog_name,
855 sink_table_name.schema_name,
856 sink_table_name.table_name,
857 ];
858 let expire_after = expire_after.map(|e| e.value);
859
860 let eval_schedule = decode_internal_eval_schedule(&mut flow_options)
861 .map_err(to_meta_err(snafu::location!()))?;
862
863 let args = CreateFlowArgs {
864 flow_id: task_id.id as u64,
865 sink_table_name,
866 source_table_ids,
867 create_if_not_exists,
868 or_replace,
869 expire_after,
870 eval_interval: eval_interval.map(|e| e.seconds),
871 comment: Some(comment),
872 sql: sql.clone(),
873 flow_options,
874 query_ctx,
875 eval_schedule,
876 };
877 let ret = self
878 .create_flow(args)
879 .await
880 .map_err(BoxedError::new)
881 .with_context(|_| CreateFlowSnafu { sql: sql.clone() })
882 .map_err(to_meta_err(snafu::location!()))?;
883 METRIC_FLOW_TASK_COUNT.inc();
884 Ok(FlowResponse {
885 affected_flows: ret
886 .map(|id| greptime_proto::v1::FlowId { id: id as u32 })
887 .into_iter()
888 .collect_vec(),
889 ..Default::default()
890 })
891 }
892 Some(flow_request::Body::Drop(DropRequest {
893 flow_id: Some(flow_id),
894 })) => {
895 self.remove_flow(flow_id.id as u64)
896 .await
897 .map_err(to_meta_err(snafu::location!()))?;
898 METRIC_FLOW_TASK_COUNT.dec();
899 Ok(Default::default())
900 }
901 Some(flow_request::Body::Flush(FlushFlow {
902 flow_id: Some(flow_id),
903 })) => {
904 let row = self
905 .flush_flow(flow_id.id as u64)
906 .await
907 .map_err(to_meta_err(snafu::location!()))?;
908 Ok(FlowResponse {
909 affected_flows: vec![flow_id],
910 affected_rows: row as u64,
911 ..Default::default()
912 })
913 }
914 other => common_meta::error::InvalidFlowRequestBodySnafu { body: other }.fail(),
915 }
916 }
917
918 async fn handle_inserts(&self, request: InsertRequests) -> MetaResult<FlowResponse> {
919 FlowEngine::handle_flow_inserts(self, request)
920 .await
921 .map(|_| Default::default())
922 .map_err(to_meta_err(snafu::location!()))
923 }
924
925 async fn handle_mark_window_dirty(&self, req: DirtyWindowRequests) -> MetaResult<FlowResponse> {
926 self.batching_engine()
927 .handle_mark_dirty_time_window(req)
928 .await
929 .map(|_| FlowResponse::default())
930 .map_err(to_meta_err(snafu::location!()))
931 }
932}
933
934fn decode_internal_eval_schedule(
937 flow_options: &mut HashMap<String, String>,
938) -> Result<Option<FlowScheduleConfig>, Error> {
939 match flow_options.remove(INTERNAL_EVAL_SCHEDULE_KEY) {
940 Some(json) => serde_json::from_str::<FlowScheduleConfig>(&json)
941 .map(Some)
942 .map_err(|err| {
943 InternalSnafu {
944 reason: format!("Invalid internal eval schedule payload: {err}"),
945 }
946 .build()
947 }),
948 None => Ok(None),
949 }
950}
951
952fn to_meta_err(
954 location: snafu::Location,
955) -> impl FnOnce(crate::error::Error) -> common_meta::error::Error {
956 move |err: crate::error::Error| -> common_meta::error::Error {
957 match err {
958 crate::error::Error::FlowNotFound { id, .. } => {
959 common_meta::error::Error::FlowNotFound {
960 flow_name: format!("flow_id={id}"),
961 location,
962 }
963 }
964 _ => common_meta::error::Error::External {
965 location,
966 source: BoxedError::new(err),
967 },
968 }
969 }
970}
971
972impl FlowEngine for StreamingEngine {
973 async fn create_flow(&self, args: CreateFlowArgs) -> Result<Option<FlowId>, Error> {
974 self.create_flow_inner(args).await
975 }
976
977 async fn remove_flow(&self, flow_id: FlowId) -> Result<(), Error> {
978 self.remove_flow_inner(flow_id).await
979 }
980
981 async fn flush_flow(&self, flow_id: FlowId) -> Result<usize, Error> {
982 self.flush_flow_inner(flow_id).await
983 }
984
985 async fn flow_exist(&self, flow_id: FlowId) -> Result<bool, Error> {
986 self.flow_exist_inner(flow_id).await
987 }
988
989 async fn list_flows(&self) -> Result<impl IntoIterator<Item = FlowId>, Error> {
990 Ok(self.stateless_flow_ids().await)
991 }
992
993 async fn handle_flow_inserts(
994 &self,
995 request: api::v1::region::InsertRequests,
996 ) -> Result<(), Error> {
997 self.handle_inserts_inner(request).await
998 }
999
1000 async fn handle_mark_window_dirty(
1001 &self,
1002 _req: api::v1::flow::DirtyWindowRequests,
1003 ) -> Result<(), Error> {
1004 UnsupportedSnafu {
1005 reason: "handle_mark_window_dirty in streaming engine",
1006 }
1007 .fail()
1008 }
1009}
1010
1011#[cfg(test)]
1012mod tests {
1013 use std::collections::HashMap;
1014
1015 use common_meta::ddl::create_flow::INTERNAL_EVAL_SCHEDULE_KEY;
1016
1017 use super::decode_internal_eval_schedule;
1018 use crate::error::Error;
1019
1020 #[test]
1021 fn test_malformed_internal_eval_schedule_json_is_error() {
1022 let mut flow_options = HashMap::new();
1023 flow_options.insert(
1024 INTERNAL_EVAL_SCHEDULE_KEY.to_string(),
1025 "not-json".to_string(),
1026 );
1027
1028 let err = decode_internal_eval_schedule(&mut flow_options).unwrap_err();
1029 assert!(matches!(
1030 err,
1031 Error::Internal { reason, .. }
1032 if reason.contains("Invalid internal eval schedule payload")
1033 ));
1034 assert!(!flow_options.contains_key(INTERNAL_EVAL_SCHEDULE_KEY));
1035 }
1036}