1use std::time::Duration;
18
19use bytes::Bytes;
20use common_telemetry::{error, info, warn};
21use futures::TryStreamExt;
22use object_store::util::join_path;
23use object_store::{EntryMode, ObjectStore};
24use snafu::ResultExt;
25use store_api::logstore::LogStore;
26use store_api::metadata::RegionMetadataRef;
27use store_api::region_request::{AffectedRows, PathType, RegionDropRequest};
28use store_api::storage::RegionId;
29use tokio::time::sleep;
30
31use crate::cache::CacheManagerRef;
32use crate::engine::region_hook::RegionHookRef;
33use crate::error::{OpenDalSnafu, Result};
34use crate::region::{MitoRegionRef, RegionLeaderState, RegionMapRef};
35use crate::request::{DdlRequest, OptionOutputTx};
36use crate::sst::index::intermediate::IntermediateManager;
37use crate::worker::{DROPPING_MARKER_FILE, RegionWorkerLoop};
38
39const GC_TASK_INTERVAL_SEC: u64 = 5 * 60; const MAX_RETRY_TIMES: u64 = 12; impl<S> RegionWorkerLoop<S>
43where
44 S: LogStore,
45{
46 pub(crate) async fn handle_drop_request(
47 &mut self,
48 region_id: RegionId,
49 req: RegionDropRequest,
50 sender: OptionOutputTx,
51 ) {
52 let region = match self.regions.writable_region(region_id) {
53 Ok(region) => region,
54 Err(e) => {
55 sender.send(Err(e));
56 return;
57 }
58 };
59
60 let (sender, req) = match self.flush_scheduler.try_cancel_and_add_ddl(
61 region_id,
62 sender,
63 req,
64 DdlRequest::Drop,
65 ) {
66 Ok(()) => {
67 self.listener.on_flush_cancel_requested(region_id);
68 return;
69 }
70 Err(request) => request,
71 };
72
73 let result = self.drop_region(region, req.partial_drop).await;
74 sender.send(result);
75 }
76
77 async fn drop_region(
78 &mut self,
79 region: MitoRegionRef,
80 partial_drop: bool,
81 ) -> Result<AffectedRows> {
82 let region_id = region.region_id;
83
84 info!("Try to drop region: {}, worker: {}", region_id, self.id);
85
86 let is_staging = region.is_staging();
87 let expect_state = if is_staging {
88 RegionLeaderState::Staging
89 } else {
90 RegionLeaderState::Writable
91 };
92 region.set_dropping(expect_state)?;
94 let region_dir = region.access_layer.build_region_dir(region_id);
97 let path_type = region.access_layer.path_type();
98 let table_dir = region.access_layer.table_dir().to_string();
99 let marker_path = join_path(®ion_dir, DROPPING_MARKER_FILE);
100 region
101 .access_layer
102 .object_store()
103 .write(&marker_path, Bytes::new())
104 .await
105 .context(OpenDalSnafu)
106 .inspect_err(|e| {
107 error!(e; "Failed to write the drop marker file for region {}", region_id);
108
109 region.switch_state_to_writable(RegionLeaderState::Dropping);
112 })?;
113
114 region.stop().await;
115 self.regions.remove_region(region_id);
117 self.dropping_regions.insert_region(region.clone());
118 self.cleanup_dropped_region_runtime_state(region_id).await;
123
124 if let Err(e) = self
132 .wal
133 .obsolete(
134 region_id,
135 region.version_control.current().last_entry_id,
136 ®ion.provider,
137 )
138 .await
139 {
140 if region.provider.as_object_store_provider().is_none() {
141 return Err(e);
142 }
143 error!(e; "Failed to obsolete the WAL of dropped region {}", region_id);
144 }
145 if let Err(e) = self.wal.release_region(region_id, ®ion.provider).await {
146 error!(e; "Failed to release the WAL of dropped region {}", region_id);
147 }
148
149 if let Some(store) = &self.series_index_store {
150 crate::series_index::delete_catalogs(store, region_id).await;
151 }
152
153 region.version_control.mark_dropped();
155 region.series_index_version_control.mark_dropped();
156 info!(
157 "Region {} is dropped logically, but some files are not deleted yet",
158 region_id
159 );
160
161 self.region_count.dec();
162
163 let hook_payload = match region.manifest_ctx.hook() {
168 Some(hook) => {
169 let region_metadata = region.metadata();
170 hook.on_region_dropped(region_id, ®ion_metadata).await;
171 Some(DropHookPayload {
172 hook,
173 metadata: region_metadata,
174 })
175 }
176 None => None,
177 };
178
179 let object_store = region.access_layer.object_store().clone();
180 let dropping_regions = self.dropping_regions.clone();
181 let listener = self.listener.clone();
182 let intm_manager = self.intermediate_manager.clone();
183 let cache_manager = self.cache_manager.clone();
184 let gc_enabled = self.file_ref_manager.is_gc_enabled();
185
186 common_runtime::spawn_global(async move {
187 let removed = if gc_enabled {
188 later_drop_task_with_global_gc(
189 region_id,
190 region_dir.clone(),
191 path_type,
192 object_store,
193 dropping_regions,
194 partial_drop,
195 hook_payload.as_ref(),
196 )
197 .await
198 } else {
199 let gc_duration = listener
200 .on_later_drop_begin(region_id)
201 .unwrap_or(Duration::from_secs(GC_TASK_INTERVAL_SEC));
202
203 later_drop_task_without_global_gc(
204 region_id,
205 region_dir.clone(),
206 object_store,
207 dropping_regions,
208 gc_duration,
209 hook_payload.as_ref(),
210 )
211 .await
212 };
213
214 cleanup_region_file_artifacts(region_id, &table_dir, &intm_manager, &cache_manager)
215 .await;
216
217 listener.on_later_drop_end(region_id, removed);
218 });
219
220 Ok(0)
221 }
222
223 pub(crate) async fn cleanup_dropped_region_runtime_state(&mut self, region_id: RegionId) {
225 self.flush_scheduler.on_region_dropped(region_id);
227 self.compaction_scheduler.on_region_dropped(region_id);
229 self.index_build_scheduler
231 .on_region_dropped(region_id)
232 .await;
233 }
234}
235
236pub(crate) async fn cleanup_region_file_artifacts(
239 region_id: RegionId,
240 table_dir: &str,
241 intermediate_manager: &IntermediateManager,
242 cache_manager: &CacheManagerRef,
243) {
244 if let Err(err) = intermediate_manager.prune_region_dir(®ion_id).await {
245 warn!(err; "Failed to prune intermediate region directory, region_id: {}", region_id);
246 }
247
248 if let Some(write_cache) = cache_manager.write_cache()
249 && let Some(manifest_cache) = write_cache.manifest_cache()
250 {
251 manifest_cache.clean_manifests(table_dir).await;
252 }
253}
254
255pub(crate) async fn remove_region_dir_for_full_drop(
260 region_path: &str,
261 object_store: &ObjectStore,
262) -> Result<()> {
263 remove_region_dir_once(region_path, object_store, true).await?;
264 Ok(())
265}
266
267struct DropHookPayload {
276 hook: RegionHookRef,
277 metadata: RegionMetadataRef,
278}
279
280async fn later_drop_task_without_global_gc(
291 region_id: RegionId,
292 region_path: String,
293 object_store: ObjectStore,
294 dropping_regions: RegionMapRef,
295 gc_duration: Duration,
296 hook_payload: Option<&DropHookPayload>,
297) -> bool {
298 remove_region_with_retry(
299 region_id,
300 region_path,
301 object_store,
302 dropping_regions,
303 Some(gc_duration),
304 false,
305 hook_payload,
306 )
307 .await
308}
309
310async fn remove_region_with_retry(
311 region_id: RegionId,
312 region_path: String,
313 object_store: ObjectStore,
314 dropping_regions: std::sync::Arc<crate::region::RegionMap>,
315 gc_duration: Option<Duration>,
316 mut force: bool,
317 hook_payload: Option<&DropHookPayload>,
318) -> bool {
319 for _ in 0..MAX_RETRY_TIMES {
320 let result = remove_region_dir_once(®ion_path, &object_store, force).await;
321 match result {
322 Err(err) => {
323 warn!(
324 "Error occurs during trying to GC region dir {}: {}",
325 region_path, err
326 );
327 }
328 Ok(true) => {
329 dropping_regions.remove_region(region_id);
330 info!("Region {} is dropped, force: {}", region_path, force);
331 if let Some(hook_payload) = hook_payload {
335 hook_payload
336 .hook
337 .on_region_files_removed(region_id, &hook_payload.metadata)
338 .await;
339 }
340 return true;
341 }
342 Ok(false) => (),
343 }
344 if let Some(duration) = gc_duration {
345 sleep(duration).await;
346 }
347 force = true;
349 }
350
351 warn!(
352 "Failed to GC region dir {} after {} retries, giving up",
353 region_path, MAX_RETRY_TIMES
354 );
355
356 false
357}
358
359async fn later_drop_task_with_global_gc(
360 region_id: RegionId,
361 region_path: String,
362 path_type: PathType,
363 object_store: ObjectStore,
364 dropping_regions: RegionMapRef,
365 partial_drop: bool,
366 hook_payload: Option<&DropHookPayload>,
367) -> bool {
368 if should_force_remove_region_dir(path_type, partial_drop) {
373 remove_region_with_retry(
374 region_id,
375 region_path,
376 object_store,
377 dropping_regions,
378 None,
379 true,
380 hook_payload,
381 )
382 .await
383 } else {
384 dropping_regions.remove_region(region_id);
386 true
387 }
388}
389
390fn should_force_remove_region_dir(path_type: PathType, partial_drop: bool) -> bool {
391 path_type == PathType::Metadata || !partial_drop
392}
393
394pub(crate) async fn remove_region_dir_once(
398 region_path: &str,
399 object_store: &ObjectStore,
400 force: bool,
401) -> Result<bool> {
402 let mut has_parquet_file = false;
404 let mut files_to_remove_first = vec![];
406 let mut files = object_store
407 .lister_with(region_path)
408 .await
409 .context(OpenDalSnafu)?;
410 while let Some(file) = files.try_next().await.context(OpenDalSnafu)? {
411 if !force && file.path().ends_with(".parquet") {
412 has_parquet_file = true;
414 break;
415 } else if !file.path().ends_with(DROPPING_MARKER_FILE) {
416 let meta = file.metadata();
417 if meta.mode() == EntryMode::FILE {
418 files_to_remove_first.push(file.path().to_string());
419 }
420 }
421 }
422
423 if !has_parquet_file {
424 object_store
427 .delete_iter(files_to_remove_first)
428 .await
429 .context(OpenDalSnafu)?;
430 object_store
432 .delete_with(region_path)
433 .recursive(true)
434 .await
435 .context(OpenDalSnafu)?;
436 Ok(true)
437 } else {
438 Ok(false)
439 }
440}