Skip to main content

flow/adapter/
flownode_impl.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! impl `FlowNode` trait for FlowNodeManager so standalone can call them
16use 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
57/// Ref to [`FlowDualEngine`]
58pub type FlowDualEngineRef = Arc<FlowDualEngine>;
59
60/// Manage both streaming and batching mode engine
61///
62/// including create/drop/flush flow
63/// and redirect insert requests to the appropriate engine
64pub struct FlowDualEngine {
65    streaming_engine: Arc<StreamingEngine>,
66    batching_engine: Arc<BatchingEngine>,
67    /// receive a oneshot sender to send state report
68    state_report_handler: RwLock<Option<StateReportHandler>>,
69    /// helper struct for faster query flow by table id or vice versa
70    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    /// Set `done_recovering` to true
100    /// indicate that we are ready to handle requests
101    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    /// Check if `done_recovering` is true
108    pub fn is_recover_done(&self) -> bool {
109        self.done_recovering
110            .load(std::sync::atomic::Ordering::Acquire)
111    }
112
113    /// wait for recovering to be done, this will only happen when flownode just started
114    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        // wait 3 seconds, check every 1 second
124        // TODO(discord9): make this configurable
125        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        // TODO(discord9): also put to centralized logging for flow once it implemented
141        Ok(())
142    }
143
144    pub fn plugins(&self) -> &Plugins {
145        &self.plugins
146    }
147
148    /// Determine if the engine is in distributed mode
149    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    /// Start state report task, which receives a sender from heartbeat task and sends report back.
170    ///
171    /// if heartbeat task is shutdown, this future exits too.
172    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    /// In distributed mode, scan periodically(1s) until all advertised frontends
191    /// accept unauthenticated queries, or timeout. In standalone mode, return
192    /// immediately.
193    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    /// Try to sync with check task, this is only used in drop flow&flush flow, so a flow id is required
236    ///
237    /// the need to sync is to make sure flush flow actually get called
238    async fn try_sync_with_check_task(
239        &self,
240        flow_id: FlowId,
241        allow_drop: bool,
242    ) -> Result<(), Error> {
243        // this function rarely get called so adding some log is helpful
244        info!("Try to sync with check task for flow {}", flow_id);
245        let mut retry = 0;
246        let max_retry = 10;
247        // keep trying to trigger consistent check
248        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    /// Spawn a task to consistently check if all flow tasks in metasrv is created on flownode,
274    /// so on startup, this will create all missing flow tasks, and constantly check at a interval
275    async fn check_flow_consistent(
276        &self,
277        allow_create: bool,
278        allow_drop: bool,
279    ) -> Result<(), Error> {
280        // use nodeid to determine if this is standalone/distributed mode, and retrieve all flows in this node(in distributed mode)/or all flows(in standalone mode)
281        let nodeid = self.streaming_engine.node_id;
282        let should_exists: Vec<_> = if let Some(nodeid) = nodeid {
283            // nodeid is available, so we only need to check flows on this node
284            // which also means we are in distributed mode
285            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            // nodeid is not available, so we need to check all flows
297            // which also means we are in standalone mode
298            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                        // because recover should only happen on restart the `create_if_not_exists` and `or_replace` can be arbitrary value(since flow doesn't exist)
363                        // but for the sake of consistency and to make sure recover of flow actually happen, we set both to true
364                        // (which is also fine since checks for not allow both to be true is on metasrv and we already pass that)
365                        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 use default QueryContext with catalog_name from info
385                            // to keep compatibility with old version
386                            .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    // TODO(discord9): consider sync this with heartbeat(might become necessary in the future)
443    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    /// Reconciles in-memory flow tasks from persisted metadata.
473    pub async fn reconcile_flows_from_metadata(&self) -> Result<(), Error> {
474        self.check_flow_consistent(true, true).await
475    }
476
477    /// TODO(discord9): also add a `exists` api using flow metadata manager's `exists` method
478    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            // Recover flows after the startup frontend probe succeeds.
514            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            // then do check flows, with configurable allow_create and allow_drop
528            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        // abort so no need to wait
584        self.handle.abort();
585        Ok(())
586    }
587}
588
589#[derive(Default)]
590struct SrcTableToFlow {
591    /// mapping of table ids to flow ids for streaming mode
592    stream: HashMap<TableId, HashSet<FlowId>>,
593    /// mapping of table ids to flow ids for batching mode
594    batch: HashMap<TableId, HashSet<FlowId>>,
595    /// mapping of flow ids to (flow type, source table ids)
596    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                // this can happen if flownode just restart, and is stilling creating the flow
698                // since now that this flow should dropped, we need to trigger the consistent check and allow drop
699                // this rely on drop flow ddl delete metadata first, see src/common/meta/src/ddl/drop_flow.rs
700                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        // remove mapping
710        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        // sync with check task
716        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        // Check the underlying registry so stateless flows survive a missing
733        // routing-map entry during recovery.
734        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        // TODO(discord9): make as little clone as possible
755        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            // not locking this, or recover flows will be starved when also handling flow inserts
763            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                    // TODO(discord9): also put to centralized logging for flow once it implemented
779                    warn!("Table {} is not any flow's source table", table_id)
780                }
781                false
782            });
783            // drop(src_table2flow);
784            // can't use drop due to https://github.com/rust-lang/rust/pull/128846
785        }
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
934/// Decode typed schedule config from the internal transient key emitted by metasrv.
935/// Malformed JSON is an internal error rather than a reason to silently fall back.
936fn 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
952/// return a function to convert `crate::error::Error` to `common_meta::error::Error`
953fn 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}