Skip to main content

catalog/
process_manager.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
15use std::collections::HashMap;
16use std::collections::hash_map::Entry;
17use std::fmt::{Debug, Display, Formatter};
18use std::sync::atomic::{AtomicU32, Ordering};
19use std::sync::{Arc, RwLock};
20use std::time::{Duration, Instant, UNIX_EPOCH};
21
22use api::v1::frontend::{KillProcessRequest, ListProcessRequest, ProcessInfo};
23use common_base::cancellation::CancellationHandle;
24use common_event_recorder::EventRecorderRef;
25use common_frontend::selector::{FrontendSelector, MetaClientSelector};
26use common_frontend::slow_query_event::SlowQueryEvent;
27use common_telemetry::logging::SlowQueriesRecordType;
28use common_telemetry::{debug, info, slow, warn};
29use common_time::util::current_time_millis;
30use meta_client::MetaClientRef;
31use promql_parser::parser::EvalStmt;
32use rand::random;
33use snafu::{OptionExt, ResultExt, ensure};
34use sql::statements::statement::Statement;
35
36use crate::error;
37use crate::metrics::{PROCESS_KILL_COUNT, PROCESS_LIST_COUNT};
38
39pub type ProcessId = u32;
40pub type ProcessManagerRef = Arc<ProcessManager>;
41
42/// Query process manager.
43pub struct ProcessManager {
44    /// Local frontend server address,
45    server_addr: String,
46    /// Next process id for local queries.
47    next_id: AtomicU32,
48    /// Running process per catalog.
49    catalogs: RwLock<HashMap<String, HashMap<ProcessId, CancellableProcess>>>,
50    /// Frontend selector to locate frontend nodes.
51    frontend_selector: Option<MetaClientSelector>,
52}
53
54/// Represents a parsed query statement, functionally equivalent to [query::parser::QueryStatement].
55/// This enum is defined here to avoid cyclic dependencies with the query parser module.
56#[derive(Debug, Clone)]
57pub enum QueryStatement {
58    Sql(Statement),
59    // The optional string is the alias of the PromQL query.
60    Promql(EvalStmt, Option<String>),
61    /// Logical plan with original query string
62    Plan(String),
63}
64
65impl Display for QueryStatement {
66    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
67        match self {
68            QueryStatement::Sql(stmt) => write!(f, "{}", stmt),
69            QueryStatement::Promql(eval_stmt, alias) => {
70                if let Some(alias) = alias {
71                    write!(f, "{} AS {}", eval_stmt, alias)
72                } else {
73                    write!(f, "{}", eval_stmt)
74                }
75            }
76            QueryStatement::Plan(query) => write!(f, "{}", query),
77        }
78    }
79}
80
81impl ProcessManager {
82    /// Create a [ProcessManager] instance with server address and kv client.
83    pub fn new(server_addr: String, meta_client: Option<MetaClientRef>) -> Self {
84        let frontend_selector = meta_client.map(MetaClientSelector::new);
85        Self {
86            server_addr,
87            next_id: Default::default(),
88            catalogs: Default::default(),
89            frontend_selector,
90        }
91    }
92}
93
94impl ProcessManager {
95    /// Registers a submitted query. Use the provided id if present.
96    #[must_use]
97    pub fn register_query(
98        self: &Arc<Self>,
99        catalog: String,
100        schemas: Vec<String>,
101        query: String,
102        client: String,
103        query_id: Option<ProcessId>,
104        _slow_query_timer: Option<SlowQueryTimer>,
105    ) -> Ticket {
106        let id = query_id.unwrap_or_else(|| self.next_id.fetch_add(1, Ordering::Relaxed));
107        let process = ProcessInfo {
108            id,
109            catalog: catalog.clone(),
110            schemas,
111            query,
112            start_timestamp: current_time_millis(),
113            client,
114            frontend: self.server_addr.clone(),
115        };
116        let cancellation_handle = Arc::new(CancellationHandle::default());
117        let cancellable_process = CancellableProcess::new(cancellation_handle.clone(), process);
118
119        self.catalogs
120            .write()
121            .unwrap()
122            .entry(catalog.clone())
123            .or_default()
124            .insert(id, cancellable_process);
125
126        Ticket {
127            catalog,
128            manager: self.clone(),
129            id,
130            cancellation_handle,
131            _slow_query_timer,
132        }
133    }
134
135    /// Generates the next process id.
136    pub fn next_id(&self) -> u32 {
137        self.next_id.fetch_add(1, Ordering::Relaxed)
138    }
139
140    /// De-register a query from process list.
141    pub fn deregister_query(&self, catalog: String, id: ProcessId) {
142        if let Entry::Occupied(mut o) = self.catalogs.write().unwrap().entry(catalog) {
143            let process = o.get_mut().remove(&id);
144            debug!("Deregister process: {:?}", process);
145            if o.get().is_empty() {
146                o.remove();
147            }
148        }
149    }
150
151    /// List local running processes in given catalog.
152    pub fn local_processes(&self, catalog: Option<&str>) -> error::Result<Vec<ProcessInfo>> {
153        let catalogs = self.catalogs.read().unwrap();
154        let result = if let Some(catalog) = catalog {
155            if let Some(catalogs) = catalogs.get(catalog) {
156                catalogs.values().map(|p| p.process.clone()).collect()
157            } else {
158                vec![]
159            }
160        } else {
161            catalogs
162                .values()
163                .flat_map(|v| v.values().map(|p| p.process.clone()))
164                .collect()
165        };
166        Ok(result)
167    }
168
169    pub async fn list_all_processes(
170        &self,
171        catalog: Option<&str>,
172    ) -> error::Result<Vec<ProcessInfo>> {
173        let mut processes = vec![];
174        if let Some(remote_frontend_selector) = self.frontend_selector.as_ref() {
175            let frontends = remote_frontend_selector
176                .select(|peer| peer.addr != self.server_addr)
177                .await
178                .context(error::InvokeFrontendSnafu)?;
179            for mut f in frontends {
180                let result = f
181                    .list_process(ListProcessRequest {
182                        catalog: catalog.unwrap_or_default().to_string(),
183                    })
184                    .await
185                    .context(error::InvokeFrontendSnafu);
186                match result {
187                    Ok(resp) => {
188                        processes.extend(resp.processes);
189                    }
190                    Err(e) => {
191                        warn!(e; "Skipping failing node: {:?}", f)
192                    }
193                }
194            }
195        }
196        processes.extend(self.local_processes(catalog)?);
197        Ok(processes)
198    }
199
200    /// Kills query with provided catalog and id.
201    pub async fn kill_process(
202        &self,
203        server_addr: String,
204        catalog: String,
205        id: ProcessId,
206    ) -> error::Result<bool> {
207        if server_addr == self.server_addr {
208            self.kill_local_process(catalog, id).await
209        } else {
210            let mut nodes = self
211                .frontend_selector
212                .as_ref()
213                .context(error::MetaClientMissingSnafu)?
214                .select(|peer| peer.addr == server_addr)
215                .await
216                .context(error::InvokeFrontendSnafu)?;
217            ensure!(
218                !nodes.is_empty(),
219                error::FrontendNotFoundSnafu { addr: server_addr }
220            );
221
222            let request = KillProcessRequest {
223                server_addr,
224                catalog,
225                process_id: id,
226            };
227            nodes[0]
228                .kill_process(request)
229                .await
230                .context(error::InvokeFrontendSnafu)?;
231            Ok(true)
232        }
233    }
234
235    /// Kills local query with provided catalog and id.
236    pub async fn kill_local_process(&self, catalog: String, id: ProcessId) -> error::Result<bool> {
237        if let Some(catalogs) = self.catalogs.write().unwrap().get_mut(&catalog) {
238            if let Some(process) = catalogs.remove(&id) {
239                process.handle.cancel();
240                info!(
241                    "Killed process, catalog: {}, id: {:?}",
242                    process.process.catalog, process.process.id
243                );
244                PROCESS_KILL_COUNT.with_label_values(&[&catalog]).inc();
245                Ok(true)
246            } else {
247                debug!("Failed to kill process, id not found: {}", id);
248                Ok(false)
249            }
250        } else {
251            debug!("Failed to kill process, catalog not found: {}", catalog);
252            Ok(false)
253        }
254    }
255}
256
257pub struct Ticket {
258    pub(crate) catalog: String,
259    pub(crate) manager: ProcessManagerRef,
260    pub(crate) id: ProcessId,
261    pub cancellation_handle: Arc<CancellationHandle>,
262
263    // Keep the handle of the slow query timer to ensure it will trigger the event recording when dropped.
264    _slow_query_timer: Option<SlowQueryTimer>,
265}
266
267impl Drop for Ticket {
268    fn drop(&mut self) {
269        self.manager
270            .deregister_query(std::mem::take(&mut self.catalog), self.id);
271    }
272}
273
274struct CancellableProcess {
275    handle: Arc<CancellationHandle>,
276    process: ProcessInfo,
277}
278
279impl Drop for CancellableProcess {
280    fn drop(&mut self) {
281        PROCESS_LIST_COUNT
282            .with_label_values(&[&self.process.catalog])
283            .dec();
284    }
285}
286
287impl CancellableProcess {
288    fn new(handle: Arc<CancellationHandle>, process: ProcessInfo) -> Self {
289        PROCESS_LIST_COUNT
290            .with_label_values(&[&process.catalog])
291            .inc();
292        Self { handle, process }
293    }
294}
295
296impl Debug for CancellableProcess {
297    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
298        f.debug_struct("CancellableProcess")
299            .field("cancelled", &self.handle.is_cancelled())
300            .field("process", &self.process)
301            .finish()
302    }
303}
304
305/// SlowQueryTimer is used to log slow query when it's dropped.
306/// In drop(), it will check if the query is slow and send the slow query event to the handler.
307pub struct SlowQueryTimer {
308    start: Instant,
309    stmt: QueryStatement,
310    schema_name: String,
311    threshold: Duration,
312    sample_ratio: f64,
313    record_type: SlowQueriesRecordType,
314    recorder: EventRecorderRef,
315}
316
317impl SlowQueryTimer {
318    pub fn new(
319        stmt: QueryStatement,
320        schema_name: String,
321        threshold: Duration,
322        sample_ratio: f64,
323        record_type: SlowQueriesRecordType,
324        recorder: EventRecorderRef,
325    ) -> Self {
326        Self {
327            start: Instant::now(),
328            stmt,
329            schema_name,
330            threshold,
331            sample_ratio,
332            record_type,
333            recorder,
334        }
335    }
336}
337
338impl SlowQueryTimer {
339    fn send_slow_query_event(&self, elapsed: Duration) {
340        let mut slow_query_event = SlowQueryEvent {
341            cost: elapsed.as_millis() as u64,
342            threshold: self.threshold.as_millis() as u64,
343            query: "".to_string(),
344            schema_name: self.schema_name.clone(),
345
346            // The following fields are only used for PromQL queries.
347            is_promql: false,
348            promql_range: None,
349            promql_step: None,
350            promql_start: None,
351            promql_end: None,
352        };
353
354        match &self.stmt {
355            QueryStatement::Promql(stmt, _alias) => {
356                slow_query_event.is_promql = true;
357                slow_query_event.query = self.stmt.to_string();
358                slow_query_event.promql_step = Some(stmt.interval.as_millis() as u64);
359
360                let start = stmt
361                    .start
362                    .duration_since(UNIX_EPOCH)
363                    .unwrap_or_default()
364                    .as_millis() as i64;
365
366                let end = stmt
367                    .end
368                    .duration_since(UNIX_EPOCH)
369                    .unwrap_or_default()
370                    .as_millis() as i64;
371
372                slow_query_event.promql_range = Some((end - start) as u64);
373                slow_query_event.promql_start = Some(start);
374                slow_query_event.promql_end = Some(end);
375            }
376            QueryStatement::Sql(stmt) => {
377                slow_query_event.query = stmt.to_string();
378            }
379            QueryStatement::Plan(query) => {
380                slow_query_event.query = query.clone();
381            }
382        }
383
384        match self.record_type {
385            // Send the slow query event to the event recorder to persist it as the system table.
386            SlowQueriesRecordType::SystemTable => {
387                self.recorder.record(Box::new(slow_query_event));
388            }
389            // Record the slow query in a specific logs file.
390            SlowQueriesRecordType::Log => {
391                slow!(
392                    cost = slow_query_event.cost,
393                    threshold = slow_query_event.threshold,
394                    query = slow_query_event.query,
395                    schema_name = slow_query_event.schema_name,
396                    is_promql = slow_query_event.is_promql,
397                    promql_range = slow_query_event.promql_range,
398                    promql_step = slow_query_event.promql_step,
399                    promql_start = slow_query_event.promql_start,
400                    promql_end = slow_query_event.promql_end,
401                );
402            }
403        }
404    }
405}
406
407impl Drop for SlowQueryTimer {
408    fn drop(&mut self) {
409        // Calculate the elapsed duration since the timer is created.
410        let elapsed = self.start.elapsed();
411        if elapsed > self.threshold {
412            // Only capture a portion of slow queries based on sample_ratio.
413            // Generate a random number in [0, 1) and compare it with sample_ratio.
414            if self.sample_ratio >= 1.0 || random::<f64>() <= self.sample_ratio {
415                self.send_slow_query_event(elapsed);
416            }
417        }
418    }
419}
420
421#[cfg(test)]
422mod tests {
423    use std::sync::Arc;
424
425    use crate::process_manager::ProcessManager;
426
427    #[tokio::test]
428    async fn test_register_query() {
429        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
430        let ticket = process_manager.clone().register_query(
431            "public".to_string(),
432            vec!["test".to_string()],
433            "SELECT * FROM table".to_string(),
434            "".to_string(),
435            None,
436            None,
437        );
438
439        let running_processes = process_manager.local_processes(None).unwrap();
440        assert_eq!(running_processes.len(), 1);
441        assert_eq!(&running_processes[0].frontend, "127.0.0.1:8000");
442        assert_eq!(running_processes[0].id, ticket.id);
443        assert_eq!(&running_processes[0].query, "SELECT * FROM table");
444
445        drop(ticket);
446        assert_eq!(process_manager.local_processes(None).unwrap().len(), 0);
447    }
448
449    #[tokio::test]
450    async fn test_register_query_with_custom_id() {
451        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
452        let custom_id = 12345;
453
454        let ticket = process_manager.clone().register_query(
455            "public".to_string(),
456            vec!["test".to_string()],
457            "SELECT * FROM table".to_string(),
458            "client1".to_string(),
459            Some(custom_id),
460            None,
461        );
462
463        assert_eq!(ticket.id, custom_id);
464
465        let running_processes = process_manager.local_processes(None).unwrap();
466        assert_eq!(running_processes.len(), 1);
467        assert_eq!(running_processes[0].id, custom_id);
468        assert_eq!(&running_processes[0].client, "client1");
469    }
470
471    #[tokio::test]
472    async fn test_multiple_queries_same_catalog() {
473        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
474
475        let ticket1 = process_manager.clone().register_query(
476            "public".to_string(),
477            vec!["schema1".to_string()],
478            "SELECT * FROM table1".to_string(),
479            "client1".to_string(),
480            None,
481            None,
482        );
483
484        let ticket2 = process_manager.clone().register_query(
485            "public".to_string(),
486            vec!["schema2".to_string()],
487            "SELECT * FROM table2".to_string(),
488            "client2".to_string(),
489            None,
490            None,
491        );
492
493        let running_processes = process_manager.local_processes(Some("public")).unwrap();
494        assert_eq!(running_processes.len(), 2);
495
496        // Verify both processes are present
497        let ids: Vec<u32> = running_processes.iter().map(|p| p.id).collect();
498        assert!(ids.contains(&ticket1.id));
499        assert!(ids.contains(&ticket2.id));
500    }
501
502    #[tokio::test]
503    async fn test_multiple_catalogs() {
504        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
505
506        let _ticket1 = process_manager.clone().register_query(
507            "catalog1".to_string(),
508            vec!["schema1".to_string()],
509            "SELECT * FROM table1".to_string(),
510            "client1".to_string(),
511            None,
512            None,
513        );
514
515        let _ticket2 = process_manager.clone().register_query(
516            "catalog2".to_string(),
517            vec!["schema2".to_string()],
518            "SELECT * FROM table2".to_string(),
519            "client2".to_string(),
520            None,
521            None,
522        );
523
524        // Test listing processes for specific catalog
525        let catalog1_processes = process_manager.local_processes(Some("catalog1")).unwrap();
526        assert_eq!(catalog1_processes.len(), 1);
527        assert_eq!(&catalog1_processes[0].catalog, "catalog1");
528
529        let catalog2_processes = process_manager.local_processes(Some("catalog2")).unwrap();
530        assert_eq!(catalog2_processes.len(), 1);
531        assert_eq!(&catalog2_processes[0].catalog, "catalog2");
532
533        // Test listing all processes
534        let all_processes = process_manager.local_processes(None).unwrap();
535        assert_eq!(all_processes.len(), 2);
536    }
537
538    #[tokio::test]
539    async fn test_deregister_query() {
540        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
541
542        let ticket = process_manager.clone().register_query(
543            "public".to_string(),
544            vec!["test".to_string()],
545            "SELECT * FROM table".to_string(),
546            "client1".to_string(),
547            None,
548            None,
549        );
550        assert_eq!(process_manager.local_processes(None).unwrap().len(), 1);
551        process_manager.deregister_query("public".to_string(), ticket.id);
552        assert_eq!(process_manager.local_processes(None).unwrap().len(), 0);
553    }
554
555    #[tokio::test]
556    async fn test_cancellation_handle() {
557        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
558
559        let ticket = process_manager.clone().register_query(
560            "public".to_string(),
561            vec!["test".to_string()],
562            "SELECT * FROM table".to_string(),
563            "client1".to_string(),
564            None,
565            None,
566        );
567
568        assert!(!ticket.cancellation_handle.is_cancelled());
569        ticket.cancellation_handle.cancel();
570        assert!(ticket.cancellation_handle.is_cancelled());
571    }
572
573    #[tokio::test]
574    async fn test_kill_local_process() {
575        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
576
577        let ticket = process_manager.clone().register_query(
578            "public".to_string(),
579            vec!["test".to_string()],
580            "SELECT * FROM table".to_string(),
581            "client1".to_string(),
582            None,
583            None,
584        );
585        assert!(!ticket.cancellation_handle.is_cancelled());
586        let killed = process_manager
587            .kill_process(
588                "127.0.0.1:8000".to_string(),
589                "public".to_string(),
590                ticket.id,
591            )
592            .await
593            .unwrap();
594
595        assert!(killed);
596        assert_eq!(process_manager.local_processes(None).unwrap().len(), 0);
597    }
598
599    #[tokio::test]
600    async fn test_kill_nonexistent_process() {
601        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
602        let killed = process_manager
603            .kill_process("127.0.0.1:8000".to_string(), "public".to_string(), 999)
604            .await
605            .unwrap();
606        assert!(!killed);
607    }
608
609    #[tokio::test]
610    async fn test_kill_process_nonexistent_catalog() {
611        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
612        let killed = process_manager
613            .kill_process("127.0.0.1:8000".to_string(), "nonexistent".to_string(), 1)
614            .await
615            .unwrap();
616        assert!(!killed);
617    }
618
619    #[tokio::test]
620    async fn test_process_info_fields() {
621        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
622
623        let _ticket = process_manager.clone().register_query(
624            "test_catalog".to_string(),
625            vec!["schema1".to_string(), "schema2".to_string()],
626            "SELECT COUNT(*) FROM users WHERE age > 18".to_string(),
627            "test_client".to_string(),
628            Some(42),
629            None,
630        );
631
632        let processes = process_manager.local_processes(None).unwrap();
633        assert_eq!(processes.len(), 1);
634
635        let process = &processes[0];
636        assert_eq!(process.id, 42);
637        assert_eq!(&process.catalog, "test_catalog");
638        assert_eq!(process.schemas, vec!["schema1", "schema2"]);
639        assert_eq!(&process.query, "SELECT COUNT(*) FROM users WHERE age > 18");
640        assert_eq!(&process.client, "test_client");
641        assert_eq!(&process.frontend, "127.0.0.1:8000");
642        assert!(process.start_timestamp > 0);
643    }
644
645    #[tokio::test]
646    async fn test_ticket_drop_deregisters_process() {
647        let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
648
649        {
650            let _ticket = process_manager.clone().register_query(
651                "public".to_string(),
652                vec!["test".to_string()],
653                "SELECT * FROM table".to_string(),
654                "client1".to_string(),
655                None,
656                None,
657            );
658
659            // Process should be registered
660            assert_eq!(process_manager.local_processes(None).unwrap().len(), 1);
661        } // ticket goes out of scope here
662
663        // Process should be automatically deregistered
664        assert_eq!(process_manager.local_processes(None).unwrap().len(), 0);
665    }
666}