Skip to main content

mito2/worker/
handle_enter_staging.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::time::Instant;
16
17use common_telemetry::{error, info, warn};
18use store_api::logstore::LogStore;
19use store_api::region_request::{EnterStagingRequest, StagingPartitionDirective};
20use store_api::storage::RegionId;
21
22use crate::error::{RegionNotFoundSnafu, Result, StagingPartitionExprMismatchSnafu};
23use crate::flush::FlushReason;
24use crate::manifest::action::{RegionMetaAction, RegionMetaActionList, RegionPartitionExprChange};
25use crate::region::{MitoRegionRef, RegionLeaderState, StagingPartitionInfo};
26use crate::request::{
27    BackgroundNotify, DdlRequest, EnterStagingResult, OptionOutputTx, SenderDdlRequest,
28    WorkerRequest, WorkerRequestWithTime,
29};
30use crate::worker::RegionWorkerLoop;
31
32impl<S: LogStore> RegionWorkerLoop<S> {
33    pub(crate) async fn handle_enter_staging_request(
34        &mut self,
35        region_id: RegionId,
36        partition_directive: StagingPartitionDirective,
37        mut sender: OptionOutputTx,
38    ) {
39        let Some(region) = self.regions.writable_region_or(region_id, &mut sender) else {
40            return;
41        };
42
43        // If the region is already in staging mode, verify the partition directive matches.
44        if region.is_staging() {
45            let staging_partition_info = region.manifest_ctx.staging_partition_info();
46            // If the partition directive mismatches, return error.
47            if staging_partition_info
48                .as_ref()
49                .map(|info| &info.partition_directive)
50                != Some(&partition_directive)
51            {
52                sender.send(Err(StagingPartitionExprMismatchSnafu {
53                    manifest_expr: staging_partition_info
54                        .as_ref()
55                        .and_then(|info| info.partition_expr().map(ToString::to_string)),
56                    request_expr: format!("{:?}", partition_directive),
57                }
58                .build()));
59                return;
60            }
61
62            // If the partition expr matches, return success.
63            sender.send(Ok(0));
64            return;
65        }
66
67        // Retain the compaction fence while the DDL later waits for flush.
68        let (sender, partition_directive) = match self.compaction_scheduler.try_cancel_and_add_ddl(
69            region_id,
70            sender,
71            partition_directive,
72            |partition_directive| {
73                DdlRequest::EnterStaging(EnterStagingRequest {
74                    partition_directive,
75                })
76            },
77        ) {
78            Ok(()) => {
79                self.listener.on_compaction_cancel_requested(region_id);
80                return;
81            }
82            Err(request) => request,
83        };
84
85        let version = region.version();
86        if !version.memtables.is_empty() {
87            // If memtable is not empty, we can't enter staging directly and need to flush
88            // all memtables first.
89            info!("Flush region: {} before entering staging", region_id);
90            debug_assert!(!region.is_staging());
91            let task = self.new_flush_task(
92                &region,
93                FlushReason::EnterStaging,
94                None,
95                self.config.clone(),
96            );
97            if let Err(e) =
98                self.flush_scheduler
99                    .schedule_flush(region.region_id, &region.version_control, task)
100            {
101                // Unable to flush the region, send error to waiter.
102                sender.send(Err(e));
103                return;
104            }
105
106            // Safety: We have requested flush.
107            self.flush_scheduler
108                .add_ddl_request_to_pending(SenderDdlRequest {
109                    region_id,
110                    sender,
111                    request: DdlRequest::EnterStaging(EnterStagingRequest {
112                        partition_directive: partition_directive.clone(),
113                    }),
114                });
115
116            return;
117        }
118
119        self.handle_enter_staging(region, partition_directive, sender);
120    }
121
122    async fn enter_staging(
123        region: &MitoRegionRef,
124        partition_directive: &StagingPartitionDirective,
125    ) -> Result<()> {
126        let now = Instant::now();
127        // First step: clear all staging manifest files.
128        {
129            let mut manager = region.manifest_ctx.manifest_manager.write().await;
130            // `set_entering_staging` has already stopped new normal manifest
131            // publications. Wait for an older checkpoint to finish cleanup
132            // before exposing staging to repartition remap readers.
133            manager.wait_for_pending_checkpoint().await;
134            manager
135                .clear_staging_manifest_and_dir()
136                .await
137                .inspect_err(|e| {
138                    error!(
139                        e;
140                        "Failed to clear staging manifest files for region {}",
141                        region.region_id
142                    );
143                })?;
144
145            info!(
146                "Cleared all staging manifest files for region {}, elapsed: {:?}",
147                region.region_id,
148                now.elapsed(),
149            );
150        }
151
152        let partition_expr = match partition_directive {
153            StagingPartitionDirective::UpdatePartitionExpr(partition_expr) => {
154                partition_expr.clone()
155            }
156            StagingPartitionDirective::RejectAllWrites => {
157                info!(
158                    "Enter staging with reject all writes, region_id: {}",
159                    region.region_id
160                );
161                // Rejects all writes just a memory flag, no need to write new staging manifest.
162                return Ok(());
163            }
164        };
165
166        // Second step: write new staging manifest.
167        let change = RegionPartitionExprChange {
168            partition_expr: Some(partition_expr.clone()),
169        };
170        let action_list =
171            RegionMetaActionList::with_action(RegionMetaAction::PartitionExprChange(change));
172        region
173            .manifest_ctx
174            .update_manifest(RegionLeaderState::EnteringStaging, action_list, true)
175            .await?;
176
177        Ok(())
178    }
179
180    fn handle_enter_staging(
181        &self,
182        region: MitoRegionRef,
183        partition_directive: StagingPartitionDirective,
184        sender: OptionOutputTx,
185    ) {
186        if let Err(e) = region.set_entering_staging() {
187            sender.send(Err(e));
188            return;
189        }
190
191        let listener = self.listener.clone();
192        let request_sender = self.sender.clone();
193        common_runtime::spawn_global(async move {
194            let now = Instant::now();
195            let result = Self::enter_staging(&region, &partition_directive).await;
196            match result {
197                Ok(_) => {
198                    info!(
199                        "Created staging manifest for region {}, elapsed: {:?}",
200                        region.region_id,
201                        now.elapsed(),
202                    );
203                }
204                Err(ref e) => {
205                    // Unset the staging manifest
206                    region
207                        .manifest_ctx
208                        .manifest_manager
209                        .write()
210                        .await
211                        .unset_staging_manifest();
212                    error!(
213                        "Failed to create staging manifest for region {}: {:?}, elapsed: {:?}",
214                        region.region_id,
215                        e,
216                        now.elapsed(),
217                    );
218                }
219            }
220
221            let notify = WorkerRequest::Background {
222                region_id: region.region_id,
223                notify: BackgroundNotify::EnterStaging(EnterStagingResult {
224                    region_id: region.region_id,
225                    sender,
226                    result,
227                    partition_directive,
228                }),
229            };
230            listener
231                .on_enter_staging_result_begin(region.region_id)
232                .await;
233
234            if let Err(res) = request_sender
235                .send(WorkerRequestWithTime::new(notify))
236                .await
237            {
238                warn!(
239                    "Failed to send enter staging result back to the worker, region_id: {}, res: {:?}",
240                    region.region_id, res
241                );
242            }
243        });
244    }
245
246    /// Handles enter staging result.
247    pub(crate) async fn handle_enter_staging_result(
248        &mut self,
249        enter_staging_result: EnterStagingResult,
250    ) {
251        let region = match self.regions.get_region(enter_staging_result.region_id) {
252            Some(region) => region,
253            None => {
254                self.reject_region_stalled_requests(&enter_staging_result.region_id);
255                enter_staging_result.sender.send(
256                    RegionNotFoundSnafu {
257                        region_id: enter_staging_result.region_id,
258                    }
259                    .fail(),
260                );
261                return;
262            }
263        };
264
265        if enter_staging_result.result.is_ok() {
266            info!(
267                "Updating region {} staging partition directive to {:?}",
268                region.region_id, enter_staging_result.partition_directive
269            );
270            Self::update_region_staging_partition_info(
271                &region,
272                enter_staging_result.partition_directive,
273            );
274            region.switch_state_to_staging(RegionLeaderState::EnteringStaging);
275        } else {
276            region.switch_state_to_writable(RegionLeaderState::EnteringStaging);
277        }
278        enter_staging_result
279            .sender
280            .send(enter_staging_result.result.map(|_| 0));
281        // Handles the stalled requests.
282        self.handle_region_stalled_requests(&enter_staging_result.region_id, true)
283            .await;
284    }
285
286    fn update_region_staging_partition_info(
287        region: &MitoRegionRef,
288        partition_directive: StagingPartitionDirective,
289    ) {
290        region.manifest_ctx.set_staging_partition_info(
291            StagingPartitionInfo::from_partition_directive(partition_directive),
292        );
293    }
294}