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 if let Some(store) = &self.series_index_store {
130 crate::series_index::delete_catalogs(store, region_id).await;
131 }
132
133 region.version_control.mark_dropped();
135 region.series_index_version_control.mark_dropped();
136 info!(
137 "Region {} is dropped logically, but some files are not deleted yet",
138 region_id
139 );
140
141 self.region_count.dec();
142
143 let hook_payload = match region.manifest_ctx.hook() {
148 Some(hook) => {
149 let region_metadata = region.metadata();
150 hook.on_region_dropped(region_id, ®ion_metadata).await;
151 Some(DropHookPayload {
152 hook,
153 metadata: region_metadata,
154 })
155 }
156 None => None,
157 };
158
159 let object_store = region.access_layer.object_store().clone();
160 let dropping_regions = self.dropping_regions.clone();
161 let listener = self.listener.clone();
162 let intm_manager = self.intermediate_manager.clone();
163 let cache_manager = self.cache_manager.clone();
164 let gc_enabled = self.file_ref_manager.is_gc_enabled();
165
166 common_runtime::spawn_global(async move {
167 let removed = if gc_enabled {
168 later_drop_task_with_global_gc(
169 region_id,
170 region_dir.clone(),
171 path_type,
172 object_store,
173 dropping_regions,
174 partial_drop,
175 hook_payload.as_ref(),
176 )
177 .await
178 } else {
179 let gc_duration = listener
180 .on_later_drop_begin(region_id)
181 .unwrap_or(Duration::from_secs(GC_TASK_INTERVAL_SEC));
182
183 later_drop_task_without_global_gc(
184 region_id,
185 region_dir.clone(),
186 object_store,
187 dropping_regions,
188 gc_duration,
189 hook_payload.as_ref(),
190 )
191 .await
192 };
193
194 cleanup_region_file_artifacts(region_id, &table_dir, &intm_manager, &cache_manager)
195 .await;
196
197 listener.on_later_drop_end(region_id, removed);
198 });
199
200 Ok(0)
201 }
202
203 pub(crate) async fn cleanup_dropped_region_runtime_state(&mut self, region_id: RegionId) {
205 self.flush_scheduler.on_region_dropped(region_id);
207 self.compaction_scheduler.on_region_dropped(region_id);
209 self.index_build_scheduler
211 .on_region_dropped(region_id)
212 .await;
213 }
214}
215
216pub(crate) async fn cleanup_region_file_artifacts(
219 region_id: RegionId,
220 table_dir: &str,
221 intermediate_manager: &IntermediateManager,
222 cache_manager: &CacheManagerRef,
223) {
224 if let Err(err) = intermediate_manager.prune_region_dir(®ion_id).await {
225 warn!(err; "Failed to prune intermediate region directory, region_id: {}", region_id);
226 }
227
228 if let Some(write_cache) = cache_manager.write_cache()
229 && let Some(manifest_cache) = write_cache.manifest_cache()
230 {
231 manifest_cache.clean_manifests(table_dir).await;
232 }
233}
234
235pub(crate) async fn remove_region_dir_for_full_drop(
240 region_path: &str,
241 object_store: &ObjectStore,
242) -> Result<()> {
243 remove_region_dir_once(region_path, object_store, true).await?;
244 Ok(())
245}
246
247struct DropHookPayload {
256 hook: RegionHookRef,
257 metadata: RegionMetadataRef,
258}
259
260async fn later_drop_task_without_global_gc(
271 region_id: RegionId,
272 region_path: String,
273 object_store: ObjectStore,
274 dropping_regions: RegionMapRef,
275 gc_duration: Duration,
276 hook_payload: Option<&DropHookPayload>,
277) -> bool {
278 remove_region_with_retry(
279 region_id,
280 region_path,
281 object_store,
282 dropping_regions,
283 Some(gc_duration),
284 false,
285 hook_payload,
286 )
287 .await
288}
289
290async fn remove_region_with_retry(
291 region_id: RegionId,
292 region_path: String,
293 object_store: ObjectStore,
294 dropping_regions: std::sync::Arc<crate::region::RegionMap>,
295 gc_duration: Option<Duration>,
296 mut force: bool,
297 hook_payload: Option<&DropHookPayload>,
298) -> bool {
299 for _ in 0..MAX_RETRY_TIMES {
300 let result = remove_region_dir_once(®ion_path, &object_store, force).await;
301 match result {
302 Err(err) => {
303 warn!(
304 "Error occurs during trying to GC region dir {}: {}",
305 region_path, err
306 );
307 }
308 Ok(true) => {
309 dropping_regions.remove_region(region_id);
310 info!("Region {} is dropped, force: {}", region_path, force);
311 if let Some(hook_payload) = hook_payload {
315 hook_payload
316 .hook
317 .on_region_files_removed(region_id, &hook_payload.metadata)
318 .await;
319 }
320 return true;
321 }
322 Ok(false) => (),
323 }
324 if let Some(duration) = gc_duration {
325 sleep(duration).await;
326 }
327 force = true;
329 }
330
331 warn!(
332 "Failed to GC region dir {} after {} retries, giving up",
333 region_path, MAX_RETRY_TIMES
334 );
335
336 false
337}
338
339async fn later_drop_task_with_global_gc(
340 region_id: RegionId,
341 region_path: String,
342 path_type: PathType,
343 object_store: ObjectStore,
344 dropping_regions: RegionMapRef,
345 partial_drop: bool,
346 hook_payload: Option<&DropHookPayload>,
347) -> bool {
348 if should_force_remove_region_dir(path_type, partial_drop) {
353 remove_region_with_retry(
354 region_id,
355 region_path,
356 object_store,
357 dropping_regions,
358 None,
359 true,
360 hook_payload,
361 )
362 .await
363 } else {
364 dropping_regions.remove_region(region_id);
366 true
367 }
368}
369
370fn should_force_remove_region_dir(path_type: PathType, partial_drop: bool) -> bool {
371 path_type == PathType::Metadata || !partial_drop
372}
373
374pub(crate) async fn remove_region_dir_once(
378 region_path: &str,
379 object_store: &ObjectStore,
380 force: bool,
381) -> Result<bool> {
382 let mut has_parquet_file = false;
384 let mut files_to_remove_first = vec![];
386 let mut files = object_store
387 .lister_with(region_path)
388 .await
389 .context(OpenDalSnafu)?;
390 while let Some(file) = files.try_next().await.context(OpenDalSnafu)? {
391 if !force && file.path().ends_with(".parquet") {
392 has_parquet_file = true;
394 break;
395 } else if !file.path().ends_with(DROPPING_MARKER_FILE) {
396 let meta = file.metadata();
397 if meta.mode() == EntryMode::FILE {
398 files_to_remove_first.push(file.path().to_string());
399 }
400 }
401 }
402
403 if !has_parquet_file {
404 object_store
407 .delete_iter(files_to_remove_first)
408 .await
409 .context(OpenDalSnafu)?;
410 object_store
412 .delete_with(region_path)
413 .recursive(true)
414 .await
415 .context(OpenDalSnafu)?;
416 Ok(true)
417 } else {
418 Ok(false)
419 }
420}