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(|ctx| ctx.into());
830 match request.body {
831 Some(flow_request::Body::Create(CreateRequest {
832 flow_id: Some(task_id),
833 source_table_ids,
834 sink_table_name: Some(sink_table_name),
835 create_if_not_exists,
836 expire_after,
837 eval_interval,
838 comment,
839 sql,
840 mut flow_options,
841 or_replace,
842 })) => {
843 let source_table_ids = source_table_ids.into_iter().map(|id| id.id).collect_vec();
844 let sink_table_name = [
845 sink_table_name.catalog_name,
846 sink_table_name.schema_name,
847 sink_table_name.table_name,
848 ];
849 let expire_after = expire_after.map(|e| e.value);
850
851 let eval_schedule = decode_internal_eval_schedule(&mut flow_options)
852 .map_err(to_meta_err(snafu::location!()))?;
853
854 let args = CreateFlowArgs {
855 flow_id: task_id.id as u64,
856 sink_table_name,
857 source_table_ids,
858 create_if_not_exists,
859 or_replace,
860 expire_after,
861 eval_interval: eval_interval.map(|e| e.seconds),
862 comment: Some(comment),
863 sql: sql.clone(),
864 flow_options,
865 query_ctx,
866 eval_schedule,
867 };
868 let ret = self
869 .create_flow(args)
870 .await
871 .map_err(BoxedError::new)
872 .with_context(|_| CreateFlowSnafu { sql: sql.clone() })
873 .map_err(to_meta_err(snafu::location!()))?;
874 METRIC_FLOW_TASK_COUNT.inc();
875 Ok(FlowResponse {
876 affected_flows: ret
877 .map(|id| greptime_proto::v1::FlowId { id: id as u32 })
878 .into_iter()
879 .collect_vec(),
880 ..Default::default()
881 })
882 }
883 Some(flow_request::Body::Drop(DropRequest {
884 flow_id: Some(flow_id),
885 })) => {
886 self.remove_flow(flow_id.id as u64)
887 .await
888 .map_err(to_meta_err(snafu::location!()))?;
889 METRIC_FLOW_TASK_COUNT.dec();
890 Ok(Default::default())
891 }
892 Some(flow_request::Body::Flush(FlushFlow {
893 flow_id: Some(flow_id),
894 })) => {
895 let row = self
896 .flush_flow(flow_id.id as u64)
897 .await
898 .map_err(to_meta_err(snafu::location!()))?;
899 Ok(FlowResponse {
900 affected_flows: vec![flow_id],
901 affected_rows: row as u64,
902 ..Default::default()
903 })
904 }
905 other => common_meta::error::InvalidFlowRequestBodySnafu { body: other }.fail(),
906 }
907 }
908
909 async fn handle_inserts(&self, request: InsertRequests) -> MetaResult<FlowResponse> {
910 FlowEngine::handle_flow_inserts(self, request)
911 .await
912 .map(|_| Default::default())
913 .map_err(to_meta_err(snafu::location!()))
914 }
915
916 async fn handle_mark_window_dirty(&self, req: DirtyWindowRequests) -> MetaResult<FlowResponse> {
917 self.batching_engine()
918 .handle_mark_dirty_time_window(req)
919 .await
920 .map(|_| FlowResponse::default())
921 .map_err(to_meta_err(snafu::location!()))
922 }
923}
924
925fn decode_internal_eval_schedule(
928 flow_options: &mut HashMap<String, String>,
929) -> Result<Option<FlowScheduleConfig>, Error> {
930 match flow_options.remove(INTERNAL_EVAL_SCHEDULE_KEY) {
931 Some(json) => serde_json::from_str::<FlowScheduleConfig>(&json)
932 .map(Some)
933 .map_err(|err| {
934 InternalSnafu {
935 reason: format!("Invalid internal eval schedule payload: {err}"),
936 }
937 .build()
938 }),
939 None => Ok(None),
940 }
941}
942
943fn to_meta_err(
945 location: snafu::Location,
946) -> impl FnOnce(crate::error::Error) -> common_meta::error::Error {
947 move |err: crate::error::Error| -> common_meta::error::Error {
948 match err {
949 crate::error::Error::FlowNotFound { id, .. } => {
950 common_meta::error::Error::FlowNotFound {
951 flow_name: format!("flow_id={id}"),
952 location,
953 }
954 }
955 _ => common_meta::error::Error::External {
956 location,
957 source: BoxedError::new(err),
958 },
959 }
960 }
961}
962
963impl FlowEngine for StreamingEngine {
964 async fn create_flow(&self, args: CreateFlowArgs) -> Result<Option<FlowId>, Error> {
965 self.create_flow_inner(args).await
966 }
967
968 async fn remove_flow(&self, flow_id: FlowId) -> Result<(), Error> {
969 self.remove_flow_inner(flow_id).await
970 }
971
972 async fn flush_flow(&self, flow_id: FlowId) -> Result<usize, Error> {
973 self.flush_flow_inner(flow_id).await
974 }
975
976 async fn flow_exist(&self, flow_id: FlowId) -> Result<bool, Error> {
977 self.flow_exist_inner(flow_id).await
978 }
979
980 async fn list_flows(&self) -> Result<impl IntoIterator<Item = FlowId>, Error> {
981 Ok(self.stateless_flow_ids().await)
982 }
983
984 async fn handle_flow_inserts(
985 &self,
986 request: api::v1::region::InsertRequests,
987 ) -> Result<(), Error> {
988 self.handle_inserts_inner(request).await
989 }
990
991 async fn handle_mark_window_dirty(
992 &self,
993 _req: api::v1::flow::DirtyWindowRequests,
994 ) -> Result<(), Error> {
995 UnsupportedSnafu {
996 reason: "handle_mark_window_dirty in streaming engine",
997 }
998 .fail()
999 }
1000}
1001
1002#[cfg(test)]
1003mod tests {
1004 use std::collections::HashMap;
1005
1006 use common_meta::ddl::create_flow::INTERNAL_EVAL_SCHEDULE_KEY;
1007
1008 use super::decode_internal_eval_schedule;
1009 use crate::error::Error;
1010
1011 #[test]
1012 fn test_malformed_internal_eval_schedule_json_is_error() {
1013 let mut flow_options = HashMap::new();
1014 flow_options.insert(
1015 INTERNAL_EVAL_SCHEDULE_KEY.to_string(),
1016 "not-json".to_string(),
1017 );
1018
1019 let err = decode_internal_eval_schedule(&mut flow_options).unwrap_err();
1020 assert!(matches!(
1021 err,
1022 Error::Internal { reason, .. }
1023 if reason.contains("Invalid internal eval schedule payload")
1024 ));
1025 assert!(!flow_options.contains_key(INTERNAL_EVAL_SCHEDULE_KEY));
1026 }
1027}