Skip to main content

operator/
request.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::sync::Arc;
16
17use api::helper::to_pb_time_unit;
18use api::v1::region::region_request::Body as RegionRequestBody;
19use api::v1::region::{
20    BuildIndexRequest, CompactRequest, CompactionTimeRange, FlushRequest, RegionRequestHeader,
21};
22use catalog::CatalogManagerRef;
23use common_catalog::build_db_string;
24use common_meta::node_manager::{AffectedRows, NodeManagerRef};
25use common_meta::peer::Peer;
26use common_telemetry::tracing_context::TracingContext;
27use common_telemetry::{debug, error, info};
28use common_time::range::TimestampRange;
29use common_time::timestamp::TimeUnit as TimestampUnit;
30use futures_util::future;
31use partition::cache::PhysicalPartitionInfo;
32use partition::manager::PartitionRuleManagerRef;
33use session::context::QueryContextRef;
34use snafu::prelude::*;
35use store_api::storage::RegionId;
36use table::requests::{BuildIndexTableRequest, CompactTableRequest, FlushTableRequest};
37
38use crate::error::{
39    CatalogSnafu, FindRegionLeaderSnafu, FindTablePartitionRuleSnafu, JoinTaskSnafu,
40    RequestRegionSnafu, Result, TableNotFoundSnafu, UnsupportedRegionRequestSnafu,
41};
42use crate::region_req_factory::RegionRequestFactory;
43
44/// Region requester which processes flush, compact requests etc.
45pub struct Requester {
46    catalog_manager: CatalogManagerRef,
47    partition_manager: PartitionRuleManagerRef,
48    node_manager: NodeManagerRef,
49}
50
51pub type RequesterRef = Arc<Requester>;
52
53impl Requester {
54    pub fn new(
55        catalog_manager: CatalogManagerRef,
56        partition_manager: PartitionRuleManagerRef,
57        node_manager: NodeManagerRef,
58    ) -> Self {
59        Self {
60            catalog_manager,
61            partition_manager,
62            node_manager,
63        }
64    }
65
66    /// Handle the request to flush table.
67    pub async fn handle_table_flush(
68        &self,
69        request: FlushTableRequest,
70        ctx: QueryContextRef,
71    ) -> Result<AffectedRows> {
72        let partitions = &self
73            .get_table_partition_info(
74                &request.catalog_name,
75                &request.schema_name,
76                &request.table_name,
77            )
78            .await?
79            .partitions;
80
81        let requests = partitions
82            .iter()
83            .map(|partition| {
84                RegionRequestBody::Flush(FlushRequest {
85                    region_id: partition.id.into(),
86                })
87            })
88            .collect();
89
90        info!("Handle table manual flush request: {:?}", request);
91
92        self.do_request(
93            requests,
94            Some(build_db_string(&request.catalog_name, &request.schema_name)),
95            &ctx,
96        )
97        .await
98    }
99
100    /// Handle the request to build index for table.
101    pub async fn handle_table_build_index(
102        &self,
103        request: BuildIndexTableRequest,
104        ctx: QueryContextRef,
105    ) -> Result<AffectedRows> {
106        let partitions = &self
107            .get_table_partition_info(
108                &request.catalog_name,
109                &request.schema_name,
110                &request.table_name,
111            )
112            .await?
113            .partitions;
114
115        let requests = partitions
116            .iter()
117            .map(|partition| {
118                RegionRequestBody::BuildIndex(BuildIndexRequest {
119                    region_id: partition.id.into(),
120                })
121            })
122            .collect();
123
124        info!(
125            "Handle table manual build index for table {}",
126            request.table_name
127        );
128        debug!("Request details: {:?}", request);
129
130        self.do_request(
131            requests,
132            Some(build_db_string(&request.catalog_name, &request.schema_name)),
133            &ctx,
134        )
135        .await
136    }
137
138    /// Handle the request to compact table.
139    pub async fn handle_table_compaction(
140        &self,
141        request: CompactTableRequest,
142        ctx: QueryContextRef,
143    ) -> Result<AffectedRows> {
144        let partitions = &self
145            .get_table_partition_info(
146                &request.catalog_name,
147                &request.schema_name,
148                &request.table_name,
149            )
150            .await?
151            .partitions;
152
153        let time_range = request
154            .time_range
155            .map(to_pb_compaction_time_range)
156            .transpose()?;
157        let requests = partitions
158            .iter()
159            .map(|partition| {
160                RegionRequestBody::Compact(CompactRequest {
161                    region_id: partition.id.into(),
162                    parallelism: request.parallelism,
163                    options: Some(request.compact_options),
164                    time_range,
165                })
166            })
167            .collect();
168
169        info!("Handle table manual compaction request: {:?}", request);
170
171        self.do_request(
172            requests,
173            Some(build_db_string(&request.catalog_name, &request.schema_name)),
174            &ctx,
175        )
176        .await
177    }
178
179    /// Handle the request to flush the region.
180    pub async fn handle_region_flush(
181        &self,
182        region_id: RegionId,
183        ctx: QueryContextRef,
184    ) -> Result<AffectedRows> {
185        let request = RegionRequestBody::Flush(FlushRequest {
186            region_id: region_id.into(),
187        });
188
189        info!("Handle region manual flush request: {region_id}");
190        self.do_request(vec![request], None, &ctx).await
191    }
192
193    /// Handle the request to compact the region.
194    pub async fn handle_region_compaction(
195        &self,
196        region_id: RegionId,
197        ctx: QueryContextRef,
198    ) -> Result<AffectedRows> {
199        let request = RegionRequestBody::Compact(CompactRequest {
200            region_id: region_id.into(),
201            parallelism: 1,
202            options: None, // todo(hl): maybe also support parameters in region compaction.
203            time_range: None,
204        });
205
206        info!("Handle region manual compaction request: {region_id}");
207        self.do_request(vec![request], None, &ctx).await
208    }
209}
210
211fn to_pb_compaction_time_range(range: TimestampRange) -> Result<CompactionTimeRange> {
212    let (Some(start), Some(end)) = (*range.start(), *range.end()) else {
213        return crate::error::InvalidTimestampRangeSnafu {
214            start: format!("{:?}", range.start()),
215            end: format!("{:?}", range.end()),
216        }
217        .fail();
218    };
219    let original_start = start;
220    let original_end = end;
221    let start = start.convert_to(TimestampUnit::Second).with_context(|| {
222        crate::error::InvalidTimestampRangeSnafu {
223            start: format!("{original_start:?}"),
224            end: format!("{original_end:?}"),
225        }
226    })?;
227    let end = end
228        .convert_to_ceil(TimestampUnit::Second)
229        .with_context(|| crate::error::InvalidTimestampRangeSnafu {
230            start: format!("{original_start:?}"),
231            end: format!("{original_end:?}"),
232        })?;
233    ensure!(
234        start < end,
235        crate::error::InvalidTimestampRangeSnafu {
236            start: format!("{original_start:?}"),
237            end: format!("{original_end:?}"),
238        }
239    );
240
241    Ok(CompactionTimeRange {
242        start: start.value(),
243        end: end.value(),
244        time_unit: to_pb_time_unit(TimestampUnit::Second) as i32,
245    })
246}
247
248impl Requester {
249    async fn do_request(
250        &self,
251        requests: Vec<RegionRequestBody>,
252        db_string: Option<String>,
253        ctx: &QueryContextRef,
254    ) -> Result<AffectedRows> {
255        let request_factory = RegionRequestFactory::new(RegionRequestHeader {
256            tracing_context: TracingContext::from_current_span().to_w3c(),
257            dbname: db_string.unwrap_or_else(|| ctx.get_db_string()),
258            ..Default::default()
259        });
260
261        let tasks = requests.into_iter().map(|req_body| {
262            let request = request_factory.build_request(req_body.clone());
263            let partition_manager = self.partition_manager.clone();
264            let node_manager = self.node_manager.clone();
265            common_runtime::spawn_global(async move {
266                let peer =
267                    Self::find_region_leader_by_request(partition_manager, &req_body).await?;
268                node_manager
269                    .datanode(&peer)
270                    .await
271                    .handle(request)
272                    .await
273                    .context(RequestRegionSnafu)
274            })
275        });
276        let results = future::try_join_all(tasks).await.context(JoinTaskSnafu)?;
277
278        let affected_rows = results
279            .into_iter()
280            .map(|resp| resp.map(|r| r.affected_rows))
281            .sum::<Result<AffectedRows>>()?;
282
283        Ok(affected_rows)
284    }
285
286    async fn find_region_leader_by_request(
287        partition_manager: PartitionRuleManagerRef,
288        req: &RegionRequestBody,
289    ) -> Result<Peer> {
290        let region_id = match req {
291            RegionRequestBody::Flush(req) => req.region_id,
292            RegionRequestBody::Compact(req) => req.region_id,
293            RegionRequestBody::BuildIndex(req) => req.region_id,
294            _ => {
295                error!("Unsupported region request: {:?}", req);
296                return UnsupportedRegionRequestSnafu {}.fail();
297            }
298        };
299
300        partition_manager
301            .find_region_leader(region_id.into())
302            .await
303            .context(FindRegionLeaderSnafu)
304    }
305
306    async fn get_table_partition_info(
307        &self,
308        catalog: &str,
309        schema: &str,
310        table_name: &str,
311    ) -> Result<Arc<PhysicalPartitionInfo>> {
312        let table = self
313            .catalog_manager
314            .table(catalog, schema, table_name, None)
315            .await
316            .context(CatalogSnafu)?;
317
318        let table = table.with_context(|| TableNotFoundSnafu {
319            table_name: common_catalog::format_full_table_name(catalog, schema, table_name),
320        })?;
321        let table_info = table.table_info();
322
323        self.partition_manager
324            .find_physical_partition_info(table_info.ident.table_id)
325            .await
326            .with_context(|_| FindTablePartitionRuleSnafu {
327                table_name: common_catalog::format_full_table_name(catalog, schema, table_name),
328            })
329    }
330}
331
332#[cfg(test)]
333mod tests {
334    use api::v1::TimeUnit;
335    use common_time::Timestamp;
336    use common_time::range::TimestampRange;
337
338    use super::*;
339
340    #[test]
341    fn test_to_pb_compaction_time_range_normalizes_mixed_units() {
342        let range = TimestampRange::new(
343            Timestamp::new_millisecond(1_500),
344            Timestamp::new_microsecond(2_500_000),
345        )
346        .unwrap();
347
348        let pb_range = to_pb_compaction_time_range(range).unwrap();
349        assert_eq!(1, pb_range.start);
350        assert_eq!(3, pb_range.end);
351        assert_eq!(TimeUnit::Second as i32, pb_range.time_unit);
352    }
353
354    #[test]
355    fn test_to_pb_compaction_time_range() {
356        let range = TimestampRange::new(
357            Timestamp::new_microsecond(1_000),
358            Timestamp::new_microsecond(2_000),
359        )
360        .unwrap();
361
362        let pb_range = to_pb_compaction_time_range(range).unwrap();
363        assert_eq!(0, pb_range.start);
364        assert_eq!(1, pb_range.end);
365        assert_eq!(TimeUnit::Second as i32, pb_range.time_unit);
366    }
367}