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
119 self.wal
121 .obsolete(
122 region_id,
123 region.version_control.current().last_entry_id,
124 ®ion.provider,
125 )
126 .await?;
127 self.cleanup_dropped_region_runtime_state(region_id).await;
128
129 region.version_control.mark_dropped();
131 info!(
132 "Region {} is dropped logically, but some files are not deleted yet",
133 region_id
134 );
135
136 self.region_count.dec();
137
138 let hook_payload = match region.manifest_ctx.hook() {
143 Some(hook) => {
144 let region_metadata = region.metadata();
145 hook.on_region_dropped(region_id, ®ion_metadata).await;
146 Some(DropHookPayload {
147 hook,
148 metadata: region_metadata,
149 })
150 }
151 None => None,
152 };
153
154 let object_store = region.access_layer.object_store().clone();
155 let dropping_regions = self.dropping_regions.clone();
156 let listener = self.listener.clone();
157 let intm_manager = self.intermediate_manager.clone();
158 let cache_manager = self.cache_manager.clone();
159 let gc_enabled = self.file_ref_manager.is_gc_enabled();
160
161 common_runtime::spawn_global(async move {
162 let removed = if gc_enabled {
163 later_drop_task_with_global_gc(
164 region_id,
165 region_dir.clone(),
166 path_type,
167 object_store,
168 dropping_regions,
169 partial_drop,
170 hook_payload.as_ref(),
171 )
172 .await
173 } else {
174 let gc_duration = listener
175 .on_later_drop_begin(region_id)
176 .unwrap_or(Duration::from_secs(GC_TASK_INTERVAL_SEC));
177
178 later_drop_task_without_global_gc(
179 region_id,
180 region_dir.clone(),
181 object_store,
182 dropping_regions,
183 gc_duration,
184 hook_payload.as_ref(),
185 )
186 .await
187 };
188
189 cleanup_region_file_artifacts(region_id, &table_dir, &intm_manager, &cache_manager)
190 .await;
191
192 listener.on_later_drop_end(region_id, removed);
193 });
194
195 Ok(0)
196 }
197
198 pub(crate) async fn cleanup_dropped_region_runtime_state(&mut self, region_id: RegionId) {
200 self.flush_scheduler.on_region_dropped(region_id);
202 self.compaction_scheduler.on_region_dropped(region_id);
204 self.index_build_scheduler
206 .on_region_dropped(region_id)
207 .await;
208 }
209}
210
211pub(crate) async fn cleanup_region_file_artifacts(
214 region_id: RegionId,
215 table_dir: &str,
216 intermediate_manager: &IntermediateManager,
217 cache_manager: &CacheManagerRef,
218) {
219 if let Err(err) = intermediate_manager.prune_region_dir(®ion_id).await {
220 warn!(err; "Failed to prune intermediate region directory, region_id: {}", region_id);
221 }
222
223 if let Some(write_cache) = cache_manager.write_cache()
224 && let Some(manifest_cache) = write_cache.manifest_cache()
225 {
226 manifest_cache.clean_manifests(table_dir).await;
227 }
228}
229
230pub(crate) async fn remove_region_dir_for_full_drop(
235 region_path: &str,
236 object_store: &ObjectStore,
237) -> Result<()> {
238 remove_region_dir_once(region_path, object_store, true).await?;
239 Ok(())
240}
241
242struct DropHookPayload {
251 hook: RegionHookRef,
252 metadata: RegionMetadataRef,
253}
254
255async fn later_drop_task_without_global_gc(
266 region_id: RegionId,
267 region_path: String,
268 object_store: ObjectStore,
269 dropping_regions: RegionMapRef,
270 gc_duration: Duration,
271 hook_payload: Option<&DropHookPayload>,
272) -> bool {
273 remove_region_with_retry(
274 region_id,
275 region_path,
276 object_store,
277 dropping_regions,
278 Some(gc_duration),
279 false,
280 hook_payload,
281 )
282 .await
283}
284
285async fn remove_region_with_retry(
286 region_id: RegionId,
287 region_path: String,
288 object_store: ObjectStore,
289 dropping_regions: std::sync::Arc<crate::region::RegionMap>,
290 gc_duration: Option<Duration>,
291 mut force: bool,
292 hook_payload: Option<&DropHookPayload>,
293) -> bool {
294 for _ in 0..MAX_RETRY_TIMES {
295 let result = remove_region_dir_once(®ion_path, &object_store, force).await;
296 match result {
297 Err(err) => {
298 warn!(
299 "Error occurs during trying to GC region dir {}: {}",
300 region_path, err
301 );
302 }
303 Ok(true) => {
304 dropping_regions.remove_region(region_id);
305 info!("Region {} is dropped, force: {}", region_path, force);
306 if let Some(hook_payload) = hook_payload {
310 hook_payload
311 .hook
312 .on_region_files_removed(region_id, &hook_payload.metadata)
313 .await;
314 }
315 return true;
316 }
317 Ok(false) => (),
318 }
319 if let Some(duration) = gc_duration {
320 sleep(duration).await;
321 }
322 force = true;
324 }
325
326 warn!(
327 "Failed to GC region dir {} after {} retries, giving up",
328 region_path, MAX_RETRY_TIMES
329 );
330
331 false
332}
333
334async fn later_drop_task_with_global_gc(
335 region_id: RegionId,
336 region_path: String,
337 path_type: PathType,
338 object_store: ObjectStore,
339 dropping_regions: RegionMapRef,
340 partial_drop: bool,
341 hook_payload: Option<&DropHookPayload>,
342) -> bool {
343 if should_force_remove_region_dir(path_type, partial_drop) {
348 remove_region_with_retry(
349 region_id,
350 region_path,
351 object_store,
352 dropping_regions,
353 None,
354 true,
355 hook_payload,
356 )
357 .await
358 } else {
359 dropping_regions.remove_region(region_id);
361 true
362 }
363}
364
365fn should_force_remove_region_dir(path_type: PathType, partial_drop: bool) -> bool {
366 path_type == PathType::Metadata || !partial_drop
367}
368
369pub(crate) async fn remove_region_dir_once(
373 region_path: &str,
374 object_store: &ObjectStore,
375 force: bool,
376) -> Result<bool> {
377 let mut has_parquet_file = false;
379 let mut files_to_remove_first = vec![];
381 let mut files = object_store
382 .lister_with(region_path)
383 .await
384 .context(OpenDalSnafu)?;
385 while let Some(file) = files.try_next().await.context(OpenDalSnafu)? {
386 if !force && file.path().ends_with(".parquet") {
387 has_parquet_file = true;
389 break;
390 } else if !file.path().ends_with(DROPPING_MARKER_FILE) {
391 let meta = file.metadata();
392 if meta.mode() == EntryMode::FILE {
393 files_to_remove_first.push(file.path().to_string());
394 }
395 }
396 }
397
398 if !has_parquet_file {
399 object_store
402 .delete_iter(files_to_remove_first)
403 .await
404 .context(OpenDalSnafu)?;
405 object_store
407 .delete_with(region_path)
408 .recursive(true)
409 .await
410 .context(OpenDalSnafu)?;
411 Ok(true)
412 } else {
413 Ok(false)
414 }
415}