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 region.deactivate_region_manifest_size_metric();
118 self.dropping_regions.insert_region(region.clone());
119 self.cleanup_dropped_region_runtime_state(region_id).await;
124
125 if let Err(e) = self
133 .wal
134 .obsolete(
135 region_id,
136 region.version_control.current().last_entry_id,
137 ®ion.provider,
138 )
139 .await
140 {
141 if region.provider.as_object_store_provider().is_none() {
142 return Err(e);
143 }
144 error!(e; "Failed to obsolete the WAL of dropped region {}", region_id);
145 }
146 if let Err(e) = self.wal.release_region(region_id, ®ion.provider).await {
147 error!(e; "Failed to release the WAL of dropped region {}", region_id);
148 }
149
150 if let Some(store) = &self.series_index_store {
151 crate::series_index::delete_catalogs(store, region_id).await;
152 }
153
154 region.version_control.mark_dropped();
156 region.series_index_version_control.mark_dropped();
157 info!(
158 "Region {} is dropped logically, but some files are not deleted yet",
159 region_id
160 );
161
162 self.region_count.dec();
163
164 let hook_payload = match region.manifest_ctx.hook() {
169 Some(hook) => {
170 let region_metadata = region.metadata();
171 hook.on_region_dropped(region_id, ®ion_metadata).await;
172 Some(DropHookPayload {
173 hook,
174 metadata: region_metadata,
175 })
176 }
177 None => None,
178 };
179
180 let object_store = region.access_layer.object_store().clone();
181 let dropping_regions = self.dropping_regions.clone();
182 let listener = self.listener.clone();
183 let intm_manager = self.intermediate_manager.clone();
184 let cache_manager = self.cache_manager.clone();
185 let gc_enabled = self.file_ref_manager.is_gc_enabled();
186
187 common_runtime::spawn_global(async move {
188 let removed = if gc_enabled {
189 later_drop_task_with_global_gc(
190 region_id,
191 region_dir.clone(),
192 path_type,
193 object_store,
194 dropping_regions,
195 partial_drop,
196 hook_payload.as_ref(),
197 )
198 .await
199 } else {
200 let gc_duration = listener
201 .on_later_drop_begin(region_id)
202 .unwrap_or(Duration::from_secs(GC_TASK_INTERVAL_SEC));
203
204 later_drop_task_without_global_gc(
205 region_id,
206 region_dir.clone(),
207 object_store,
208 dropping_regions,
209 gc_duration,
210 hook_payload.as_ref(),
211 )
212 .await
213 };
214
215 cleanup_region_file_artifacts(region_id, &table_dir, &intm_manager, &cache_manager)
216 .await;
217
218 listener.on_later_drop_end(region_id, removed);
219 });
220
221 Ok(0)
222 }
223
224 pub(crate) async fn cleanup_dropped_region_runtime_state(&mut self, region_id: RegionId) {
226 self.reject_region_edit_queue_as_not_found(region_id);
229 self.flush_scheduler.on_region_dropped(region_id);
231 self.compaction_scheduler.on_region_dropped(region_id);
233 self.index_build_scheduler
235 .on_region_dropped(region_id)
236 .await;
237 }
238}
239
240pub(crate) async fn cleanup_region_file_artifacts(
243 region_id: RegionId,
244 table_dir: &str,
245 intermediate_manager: &IntermediateManager,
246 cache_manager: &CacheManagerRef,
247) {
248 if let Err(err) = intermediate_manager.prune_region_dir(®ion_id).await {
249 warn!(err; "Failed to prune intermediate region directory, region_id: {}", region_id);
250 }
251
252 if let Some(write_cache) = cache_manager.write_cache()
253 && let Some(manifest_cache) = write_cache.manifest_cache()
254 {
255 manifest_cache.clean_manifests(table_dir).await;
256 }
257}
258
259pub(crate) async fn remove_region_dir_for_full_drop(
264 region_path: &str,
265 object_store: &ObjectStore,
266) -> Result<()> {
267 remove_region_dir_once(region_path, object_store, true).await?;
268 Ok(())
269}
270
271struct DropHookPayload {
280 hook: RegionHookRef,
281 metadata: RegionMetadataRef,
282}
283
284async fn later_drop_task_without_global_gc(
295 region_id: RegionId,
296 region_path: String,
297 object_store: ObjectStore,
298 dropping_regions: RegionMapRef,
299 gc_duration: Duration,
300 hook_payload: Option<&DropHookPayload>,
301) -> bool {
302 remove_region_with_retry(
303 region_id,
304 region_path,
305 object_store,
306 dropping_regions,
307 Some(gc_duration),
308 false,
309 hook_payload,
310 )
311 .await
312}
313
314async fn remove_region_with_retry(
315 region_id: RegionId,
316 region_path: String,
317 object_store: ObjectStore,
318 dropping_regions: std::sync::Arc<crate::region::RegionMap>,
319 gc_duration: Option<Duration>,
320 mut force: bool,
321 hook_payload: Option<&DropHookPayload>,
322) -> bool {
323 for _ in 0..MAX_RETRY_TIMES {
324 let result = remove_region_dir_once(®ion_path, &object_store, force).await;
325 match result {
326 Err(err) => {
327 warn!(
328 "Error occurs during trying to GC region dir {}: {}",
329 region_path, err
330 );
331 }
332 Ok(true) => {
333 dropping_regions.remove_region(region_id);
334 info!("Region {} is dropped, force: {}", region_path, force);
335 if let Some(hook_payload) = hook_payload {
339 hook_payload
340 .hook
341 .on_region_files_removed(region_id, &hook_payload.metadata)
342 .await;
343 }
344 return true;
345 }
346 Ok(false) => (),
347 }
348 if let Some(duration) = gc_duration {
349 sleep(duration).await;
350 }
351 force = true;
353 }
354
355 warn!(
356 "Failed to GC region dir {} after {} retries, giving up",
357 region_path, MAX_RETRY_TIMES
358 );
359
360 false
361}
362
363async fn later_drop_task_with_global_gc(
364 region_id: RegionId,
365 region_path: String,
366 path_type: PathType,
367 object_store: ObjectStore,
368 dropping_regions: RegionMapRef,
369 partial_drop: bool,
370 hook_payload: Option<&DropHookPayload>,
371) -> bool {
372 if should_force_remove_region_dir(path_type, partial_drop) {
377 remove_region_with_retry(
378 region_id,
379 region_path,
380 object_store,
381 dropping_regions,
382 None,
383 true,
384 hook_payload,
385 )
386 .await
387 } else {
388 dropping_regions.remove_region(region_id);
390 true
391 }
392}
393
394fn should_force_remove_region_dir(path_type: PathType, partial_drop: bool) -> bool {
395 path_type == PathType::Metadata || !partial_drop
396}
397
398pub(crate) async fn remove_region_dir_once(
402 region_path: &str,
403 object_store: &ObjectStore,
404 force: bool,
405) -> Result<bool> {
406 let mut has_parquet_file = false;
408 let mut files_to_remove_first = vec![];
410 let mut files = object_store
411 .lister_with(region_path)
412 .await
413 .context(OpenDalSnafu)?;
414 while let Some(file) = files.try_next().await.context(OpenDalSnafu)? {
415 if !force && file.path().ends_with(".parquet") {
416 has_parquet_file = true;
418 break;
419 } else if !file.path().ends_with(DROPPING_MARKER_FILE) {
420 let meta = file.metadata();
421 if meta.mode() == EntryMode::FILE {
422 files_to_remove_first.push(file.path().to_string());
423 }
424 }
425 }
426
427 if !has_parquet_file {
428 object_store
431 .delete_iter(files_to_remove_first)
432 .await
433 .context(OpenDalSnafu)?;
434 object_store
436 .delete_with(region_path)
437 .recursive(true)
438 .await
439 .context(OpenDalSnafu)?;
440 Ok(true)
441 } else {
442 Ok(false)
443 }
444}