Skip to main content

mito2/worker/
handle_catchup.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//! Handling catchup request.
16
17use std::sync::Arc;
18
19use common_telemetry::tracing::warn;
20use common_telemetry::{debug, error, info};
21use store_api::logstore::LogStore;
22use store_api::region_engine::{RegionRole, SettableRegionRoleState};
23use store_api::region_request::RegionCatchupRequest;
24use store_api::storage::RegionId;
25
26use crate::error::{self, Result};
27use crate::region::MitoRegion;
28use crate::region::catchup::RegionCatchupTask;
29use crate::region::opener::RegionOpener;
30use crate::request::OptionOutputTx;
31#[cfg(feature = "hdfs-object-store")]
32use crate::temp_file_sweeper;
33use crate::wal::entry_distributor::WalEntryReceiver;
34use crate::worker::RegionWorkerLoop;
35
36impl<S: LogStore> RegionWorkerLoop<S> {
37    pub(crate) async fn handle_catchup_request(
38        &mut self,
39        region_id: RegionId,
40        request: RegionCatchupRequest,
41        entry_receiver: Option<WalEntryReceiver>,
42        sender: OptionOutputTx,
43    ) {
44        let Some(region) = self.regions.get_region(region_id) else {
45            sender.send(Err(error::RegionNotFoundSnafu { region_id }.build()));
46            return;
47        };
48
49        if region.is_writable() {
50            debug!("Region {region_id} is writable, skip catchup");
51            sender.send(Ok(0));
52            return;
53        }
54
55        if self.catchup_regions.is_region_exists(region_id) {
56            warn!("Region {region_id} under catching up");
57            sender.send(Err(error::RegionBusySnafu { region_id }.build()));
58            return;
59        }
60
61        // If the memtable is not empty or the manifest has been updated, we need to reopen the region.
62        let region = match self.reopen_region_if_needed(region).await {
63            Ok(region) => region,
64            Err(e) => {
65                sender.send(Err(e));
66                return;
67            }
68        };
69
70        self.catchup_regions.insert_region(region_id);
71        let catchup_regions = self.catchup_regions.clone();
72        let wal = self.wal.clone();
73        let allow_stale_entries = self.config.allow_stale_entries;
74        #[cfg(feature = "hdfs-object-store")]
75        let sweep_older_than = self.config.gc.unknown_file_lingering_time;
76        #[cfg(feature = "hdfs-object-store")]
77        let sweep_limiter = self.sweep_limiter.clone();
78        let node_lease = self.node_lease.clone();
79        common_runtime::spawn_global(async move {
80            let mut task = RegionCatchupTask::new(region.clone(), wal, allow_stale_entries)
81                .with_entry_receiver(entry_receiver)
82                .with_expected_last_entry_id(request.entry_id)
83                .with_replay_checkpoint_entry_id(request.checkpoint.map(|c| c.entry_id));
84
85            match task.run().await {
86                Ok(_) => {
87                    if request.set_writable {
88                        // The region takes writes under the node lease until its next Leader
89                        // grant.
90                        node_lease.seed(&region.manifest_ctx.leader_deadline);
91                        region.set_role(RegionRole::Leader);
92                        // Finalize leadership: persist backfilled metadata.
93                        match region
94                            .set_role_state_gracefully(SettableRegionRoleState::Leader)
95                            .await
96                        {
97                            Ok(()) => {
98                                #[cfg(feature = "hdfs-object-store")]
99                                temp_file_sweeper::spawn_region_sweep(
100                                    region.access_layer(),
101                                    region_id,
102                                    sweep_older_than,
103                                    sweep_limiter,
104                                );
105                            }
106                            Err(err) => {
107                                error!(err; "Failed to set region {region_id} to leader");
108                            }
109                        }
110                    }
111                    catchup_regions.remove_region(region_id);
112                    sender.send(Ok(0));
113                }
114                Err(err) => {
115                    error!(err; "Failed to catchup region {region_id}");
116                    catchup_regions.remove_region(region_id);
117                    sender.send(Err(err));
118                }
119            }
120        });
121    }
122
123    /// Reopens a region.
124    pub(crate) async fn reopen_region(
125        &mut self,
126        region: &Arc<MitoRegion>,
127    ) -> Result<Arc<MitoRegion>> {
128        let region_id = region.region_id;
129        let manifest_version = region.manifest_ctx.manifest_version().await;
130        let flushed_entry_id = region.version_control.current().last_entry_id;
131        info!(
132            "Reopening the region: {region_id}, manifest version: {manifest_version}, flushed entry id: {flushed_entry_id}"
133        );
134        let reopened_region = RegionOpener::new(
135            region_id,
136            region.table_dir(),
137            region.access_layer.path_type(),
138            self.node_lease.clone(),
139            self.memtable_builder_provider.clone(),
140            self.object_store_manager.clone(),
141            self.purge_scheduler.clone(),
142            self.puffin_manager_factory.clone(),
143            self.intermediate_manager.clone(),
144            self.time_provider.clone(),
145            self.file_ref_manager.clone(),
146            self.partition_expr_fetcher.clone(),
147        )
148        .cache(Some(self.cache_manager.clone()))
149        .series_index_store(self.series_index_store.clone())
150        .series_index_purger(self.series_index_purger.clone())
151        .hook(self.plugins.get())
152        .options(region.version().options.clone())?
153        .skip_wal_replay(true)
154        .open(&self.config, &self.wal)
155        .await?;
156        debug_assert!(!reopened_region.is_writable());
157        // Preserve allocated sequences without publishing the old memtables.
158        reopened_region
159            .version_control
160            .update_last_sequence(region.version_control.current().last_sequence);
161        self.register_region(reopened_region.clone());
162        if let Some(state) = &self.series_index_task_state {
163            state.wake();
164        }
165
166        Ok(reopened_region)
167    }
168
169    async fn reopen_region_if_needed(
170        &mut self,
171        region: Arc<MitoRegion>,
172    ) -> Result<Arc<MitoRegion>> {
173        // Note: Currently, We protect the split brain by ensuring the memtable table is empty.
174        // It's expensive to execute catch-up requests without `set_writable=true` multiple times.
175        let version = region.version();
176        let is_empty_memtable = version.memtables.is_empty();
177
178        // Utilizes the short circuit evaluation.
179        let region = if !is_empty_memtable || region.manifest_ctx.has_update().await? {
180            if !is_empty_memtable {
181                warn!(
182                    "Region {} memtables is not empty, which should not happen, manifest version: {}, last entry id: {}",
183                    region.region_id,
184                    region.manifest_ctx.manifest_version().await,
185                    region.version_control.current().last_entry_id
186                );
187            }
188            self.reopen_region(&region).await?
189        } else {
190            region
191        };
192
193        Ok(region)
194    }
195}