Skip to main content

common_meta/ddl/
drop_table.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
15pub mod executor;
16mod metadata;
17
18use std::collections::HashMap;
19
20use async_trait::async_trait;
21use common_error::ext::BoxedError;
22#[cfg(feature = "enterprise")]
23use common_error::ext::ErrorExt;
24use common_event_recorder::Event;
25use common_procedure::error::{ExternalSnafu, FromJsonSnafu, ToJsonSnafu};
26use common_procedure::{
27    Context as ProcedureContext, Error as ProcedureError, EventContext, EventTrigger, LockKey,
28    Procedure, Result as ProcedureResult, Status,
29};
30use common_telemetry::info;
31use common_telemetry::tracing::warn;
32#[cfg(feature = "enterprise")]
33use common_time::util::current_time_millis;
34use common_wal::options::WalOptions;
35use serde::{Deserialize, Serialize};
36use snafu::{OptionExt, ResultExt};
37use store_api::storage::RegionNumber;
38use strum::AsRefStr;
39use table::metadata::TableId;
40use table::table_reference::TableReference;
41#[cfg(feature = "enterprise")]
42use uuid::Uuid;
43
44use self::executor::DropTableExecutor;
45use crate::ddl::DdlContext;
46use crate::ddl::event::table::{TableDdlEvent, TableDdlEventType, TableDdlLocator};
47use crate::ddl::utils::{convert_region_routes_to_detecting_regions, map_to_procedure_error};
48use crate::error::{self, Result};
49use crate::key::table_route::TableRouteValue;
50use crate::lock_key::{CatalogLock, SchemaLock, TableLock, TableNameLock};
51use crate::metrics;
52use crate::region_keeper::OperatingRegionGuard;
53use crate::rpc::ddl::DropTableTask;
54use crate::rpc::router::{RegionRoute, operating_leader_region_roles};
55
56#[cfg(feature = "enterprise")]
57fn ensure_retry_later(err: error::Error) -> error::Error {
58    if err.retry_hint().is_retryable() {
59        err
60    } else {
61        error::Error::retry_later(err)
62    }
63}
64
65pub struct DropTableProcedure {
66    /// The context of procedure runtime.
67    pub context: DdlContext,
68    /// The serializable data.
69    pub data: DropTableData,
70    /// The guards of opening regions.
71    pub(crate) dropping_regions: Vec<OperatingRegionGuard>,
72    /// The drop table executor.
73    executor: DropTableExecutor,
74}
75
76impl DropTableProcedure {
77    pub const TYPE_NAME: &'static str = "metasrv-procedure::DropTable";
78
79    pub fn new(task: DropTableTask, context: DdlContext) -> Self {
80        let data = DropTableData::new(
81            task,
82            cfg!(feature = "enterprise") && context.soft_drop_enabled,
83            context.soft_drop_retention,
84        );
85        let executor = data.build_executor();
86        Self {
87            context,
88            data,
89            dropping_regions: vec![],
90            executor,
91        }
92    }
93
94    pub fn from_json(json: &str, context: DdlContext) -> ProcedureResult<Self> {
95        let data: DropTableData = serde_json::from_str(json).context(FromJsonSnafu)?;
96        #[cfg(feature = "enterprise")]
97        let mut data = data;
98        #[cfg(feature = "enterprise")]
99        if data.state == DropTableState::Prepare
100            && data.soft_drop_enabled
101            && data.dropped_at.is_none()
102            && data.soft_drop_retention_millis.is_none()
103        {
104            data.soft_drop_retention_millis = context
105                .soft_drop_retention
106                .and_then(|retention| i64::try_from(retention.as_millis()).ok());
107        }
108        let executor = data.build_executor();
109
110        Ok(Self {
111            context,
112            data,
113            dropping_regions: vec![],
114            executor,
115        })
116    }
117
118    #[cfg(feature = "enterprise")]
119    async fn prepare_soft_drop(&mut self) -> Result<()> {
120        self.executor
121            .check_tombstone_conflict(&self.context, self.data.soft_drop_enabled)
122            .await?;
123        if self.data.soft_drop_enabled && self.data.dropped_at.is_none() {
124            let dropped_at = current_time_millis();
125            let retention_millis =
126                self.data
127                    .soft_drop_retention_millis
128                    .context(error::UnexpectedSnafu {
129                        err_msg: "Soft-drop retention is missing from the procedure".to_string(),
130                    })?;
131            let retention_expires_at = dropped_at.checked_add(retention_millis).context(
132                error::UnexpectedSnafu {
133                    err_msg: format!(
134                        "Soft-drop retention deadline overflows i64: dropped_at={dropped_at}, retention_millis={retention_millis}"
135                    ),
136                },
137            )?;
138            self.data.dropped_at = Some(dropped_at);
139            self.data.retention_expires_at = Some(retention_expires_at);
140        }
141        if self.data.soft_drop_enabled && self.data.drop_generation.is_none() {
142            self.data.drop_generation = Some(Uuid::new_v4().to_string());
143        }
144        Ok(())
145    }
146
147    pub(crate) async fn on_prepare(&mut self) -> Result<Status> {
148        if self.executor.on_prepare(&self.context).await?.stop() {
149            return Ok(Status::done());
150        }
151        self.fill_table_metadata().await?;
152        #[cfg(feature = "enterprise")]
153        self.prepare_soft_drop().await?;
154        self.data.state = DropTableState::DeleteMetadata;
155
156        Ok(Status::executing(true))
157    }
158
159    /// Register dropping regions if doesn't exist.
160    fn register_dropping_regions(&mut self) -> Result<()> {
161        let dropping_regions = operating_leader_region_roles(&self.data.physical_region_routes);
162
163        if !self.dropping_regions.is_empty() {
164            return Ok(());
165        }
166
167        let mut dropping_region_guards = Vec::with_capacity(dropping_regions.len());
168
169        for (region_id, datanode_id, role) in dropping_regions {
170            let guard = self
171                .context
172                .memory_region_keeper
173                .register_with_role(datanode_id, region_id, role)
174                .context(error::RegionOperatingRaceSnafu {
175                    region_id,
176                    peer_id: datanode_id,
177                })?;
178            dropping_region_guards.push(guard);
179        }
180
181        self.dropping_regions = dropping_region_guards;
182        Ok(())
183    }
184
185    #[cfg(not(feature = "enterprise"))]
186    async fn delete_metadata(&mut self) -> Result<()> {
187        let table_route_value = &TableRouteValue::new(
188            self.data.task.table_id,
189            // Safety: checked
190            self.data.physical_table_id.unwrap(),
191            self.data.physical_region_routes.clone(),
192        );
193        self.executor
194            .on_delete_metadata(
195                &self.context,
196                table_route_value,
197                &self.data.region_wal_options,
198            )
199            .await
200    }
201
202    #[cfg(feature = "enterprise")]
203    async fn delete_metadata(&mut self) -> Result<()> {
204        if !self.data.soft_drop_enabled {
205            let table_route_value = &TableRouteValue::new(
206                self.data.task.table_id,
207                // Safety: checked
208                self.data.physical_table_id.unwrap(),
209                self.data.physical_region_routes.clone(),
210            );
211            return self
212                .executor
213                .on_delete_metadata(
214                    &self.context,
215                    table_route_value,
216                    &self.data.region_wal_options,
217                )
218                .await;
219        }
220
221        let storage = self
222            .context
223            .table_metadata_manager
224            .table_route_manager()
225            .table_route_storage();
226        storage
227            .remap_region_routes(&mut self.data.physical_region_routes)
228            .await
229            .map_err(ensure_retry_later)?;
230        let table_route_value = &TableRouteValue::new(
231            self.data.task.table_id,
232            // Safety: checked
233            self.data.physical_table_id.unwrap(),
234            self.data.physical_region_routes.clone(),
235        );
236        self.executor
237            .on_close_regions(
238                &self.context.node_manager,
239                &self.context.leader_region_registry,
240                &self.data.physical_region_routes,
241                true,
242            )
243            .await
244            .map_err(ensure_retry_later)?;
245        self.executor
246            .on_soft_delete_metadata(
247                &self.context,
248                table_route_value,
249                &self.data.region_wal_options,
250                self.data.dropped_at,
251                self.data.retention_expires_at,
252                self.data.drop_generation.as_deref(),
253            )
254            .await
255            .map_err(ensure_retry_later)?;
256        self.data.allow_rollback = false;
257        Ok(())
258    }
259
260    pub(crate) async fn on_delete_metadata(&mut self) -> Result<Status> {
261        self.register_dropping_regions()?;
262        // TODO(weny): Considers introducing a RegionStatus to indicate the region is dropping.
263        let table_id = self.data.table_id();
264        self.delete_metadata().await?;
265        info!("Deleted table metadata for table {table_id}");
266        self.data.state = DropTableState::InvalidateTableCache;
267        Ok(Status::executing(true))
268    }
269
270    /// Broadcasts invalidate table cache instruction.
271    async fn on_broadcast(&mut self) -> Result<Status> {
272        let result = self.executor.invalidate_table_cache(&self.context).await;
273        #[cfg(feature = "enterprise")]
274        if self.data.soft_drop_enabled {
275            result.map_err(ensure_retry_later)?;
276        } else {
277            result?;
278        }
279        #[cfg(not(feature = "enterprise"))]
280        result?;
281
282        self.data.state = DropTableState::DatanodeDropRegions;
283
284        Ok(Status::executing(true))
285    }
286
287    pub async fn on_datanode_drop_regions(&mut self, retrying: bool) -> Result<Status> {
288        if retrying {
289            info!(
290                "Remapping region routes addresses for retrying drop regions for table_id: {}",
291                self.data.table_id()
292            );
293            let storage = self
294                .context
295                .table_metadata_manager
296                .table_route_manager()
297                .table_route_storage();
298            // The peer addresses may change during retries,
299            // so we always remap the region routes.
300            storage
301                .remap_region_routes(&mut self.data.physical_region_routes)
302                .await?;
303        }
304
305        #[cfg(feature = "enterprise")]
306        if self.data.soft_drop_enabled {
307            self.context
308                .deregister_failure_detectors(convert_region_routes_to_detecting_regions(
309                    &self.data.physical_region_routes,
310                ))
311                .await;
312            self.dropping_regions.clear();
313            return Ok(Status::done());
314        }
315
316        self.executor
317            .on_drop_regions(
318                &self.context.node_manager,
319                &self.context.leader_region_registry,
320                &self.data.physical_region_routes,
321                false,
322                false,
323                false,
324            )
325            .await?;
326        self.context
327            .deregister_failure_detectors(convert_region_routes_to_detecting_regions(
328                &self.data.physical_region_routes,
329            ))
330            .await;
331
332        self.data.state = DropTableState::DeleteTombstone;
333        Ok(Status::executing(true))
334    }
335
336    /// Deletes metadata tombstone.
337    async fn on_delete_metadata_tombstone(&mut self) -> Result<Status> {
338        let table_route_value = &TableRouteValue::new(
339            self.data.task.table_id,
340            // Safety: checked
341            self.data.physical_table_id.unwrap(),
342            self.data.physical_region_routes.clone(),
343        );
344        self.executor
345            .on_delete_metadata_tombstone(
346                &self.context,
347                table_route_value,
348                &self.data.region_wal_options,
349            )
350            .await?;
351
352        self.dropping_regions.clear();
353        Ok(Status::done())
354    }
355}
356
357#[async_trait]
358impl Procedure for DropTableProcedure {
359    fn type_name(&self) -> &str {
360        Self::TYPE_NAME
361    }
362
363    fn recover(&mut self) -> ProcedureResult<()> {
364        // Only registers regions if the metadata is deleted.
365        let register_operating_regions = matches!(
366            self.data.state,
367            DropTableState::DeleteMetadata
368                | DropTableState::InvalidateTableCache
369                | DropTableState::DatanodeDropRegions
370        );
371        if register_operating_regions {
372            self.register_dropping_regions()
373                .map_err(BoxedError::new)
374                .context(ExternalSnafu {
375                    clean_poisons: false,
376                })?;
377        }
378
379        Ok(())
380    }
381
382    async fn execute(&mut self, ctx: &ProcedureContext) -> ProcedureResult<Status> {
383        let state = &self.data.state;
384        let _timer = metrics::METRIC_META_PROCEDURE_DROP_TABLE
385            .with_label_values(&[state.as_ref()])
386            .start_timer();
387
388        match self.data.state {
389            DropTableState::Prepare => self.on_prepare().await,
390            DropTableState::DeleteMetadata => self.on_delete_metadata().await,
391            DropTableState::InvalidateTableCache => self.on_broadcast().await,
392            DropTableState::DatanodeDropRegions => {
393                let retrying = ctx.is_retrying().await.unwrap_or(false);
394                self.on_datanode_drop_regions(retrying).await
395            }
396            DropTableState::DeleteTombstone => self.on_delete_metadata_tombstone().await,
397        }
398        .map_err(map_to_procedure_error)
399    }
400
401    fn dump(&self) -> ProcedureResult<String> {
402        serde_json::to_string(&self.data).context(ToJsonSnafu)
403    }
404
405    fn lock_key(&self) -> LockKey {
406        let table_ref = &self.data.table_ref();
407        let table_id = self.data.table_id();
408        let lock_key = vec![
409            CatalogLock::Read(table_ref.catalog).into(),
410            SchemaLock::read(table_ref.catalog, table_ref.schema).into(),
411            TableNameLock::new(table_ref.catalog, table_ref.schema, table_ref.table).into(),
412            TableLock::Write(table_id).into(),
413        ];
414
415        LockKey::new(lock_key)
416    }
417
418    fn event(&self, ctx: &EventContext<'_>) -> Option<Box<dyn Event>> {
419        if !ctx
420            .event_type_filter
421            .allows(TableDdlEventType::DropTable.as_str())
422        {
423            return None;
424        }
425        let task = &self.data.task;
426        let locator = TableDdlLocator::new(&task.catalog, &task.schema, &task.table)
427            .with_table_id(task.table_id);
428        let event = match &ctx.trigger {
429            EventTrigger::Submitted => {
430                TableDdlEvent::drop_table_submitted(locator, task.drop_if_exists)
431            }
432            _ => TableDdlEvent::lifecycle(TableDdlEventType::DropTable, [locator]),
433        };
434
435        Some(Box::new(event))
436    }
437
438    fn rollback_supported(&self) -> bool {
439        !matches!(self.data.state, DropTableState::Prepare) && self.data.allow_rollback
440    }
441
442    async fn rollback(&mut self, _: &ProcedureContext) -> ProcedureResult<()> {
443        warn!(
444            "Rolling back the drop table procedure, table: {}",
445            self.data.table_id()
446        );
447
448        let table_route_value = &TableRouteValue::new(
449            self.data.task.table_id,
450            // Safety: checked
451            self.data.physical_table_id.unwrap(),
452            self.data.physical_region_routes.clone(),
453        );
454        self.executor
455            .on_restore_metadata(
456                &self.context,
457                table_route_value,
458                &self.data.region_wal_options,
459            )
460            .await
461            .map_err(ProcedureError::external)?;
462
463        self.dropping_regions.clear();
464        Ok(())
465    }
466}
467
468#[derive(Debug, Serialize, Deserialize)]
469pub struct DropTableData {
470    pub state: DropTableState,
471    pub task: DropTableTask,
472    pub physical_region_routes: Vec<RegionRoute>,
473    pub physical_table_id: Option<TableId>,
474    #[serde(default)]
475    pub region_wal_options: HashMap<RegionNumber, WalOptions>,
476    #[serde(default)]
477    pub allow_rollback: bool,
478    #[serde(default)]
479    pub soft_drop_enabled: bool,
480    #[serde(default)]
481    pub dropped_at: Option<i64>,
482    #[serde(default)]
483    pub retention_expires_at: Option<i64>,
484    #[serde(default)]
485    pub soft_drop_retention_millis: Option<i64>,
486    #[serde(default)]
487    pub drop_generation: Option<String>,
488}
489
490impl DropTableData {
491    pub fn new(
492        task: DropTableTask,
493        soft_drop_enabled: bool,
494        soft_drop_retention: Option<std::time::Duration>,
495    ) -> Self {
496        Self {
497            state: DropTableState::Prepare,
498            task,
499            physical_region_routes: vec![],
500            physical_table_id: None,
501            region_wal_options: HashMap::new(),
502            allow_rollback: false,
503            soft_drop_enabled,
504            dropped_at: None,
505            retention_expires_at: None,
506            soft_drop_retention_millis: soft_drop_retention_millis(soft_drop_retention),
507            drop_generation: None,
508        }
509    }
510
511    fn table_ref(&self) -> TableReference<'_> {
512        self.task.table_ref()
513    }
514
515    fn table_id(&self) -> TableId {
516        self.task.table_id
517    }
518
519    fn build_executor(&self) -> DropTableExecutor {
520        DropTableExecutor::new(
521            self.task.table_name(),
522            self.task.table_id,
523            self.task.drop_if_exists,
524        )
525    }
526}
527
528#[cfg(feature = "enterprise")]
529fn soft_drop_retention_millis(soft_drop_retention: Option<std::time::Duration>) -> Option<i64> {
530    soft_drop_retention.and_then(|retention| i64::try_from(retention.as_millis()).ok())
531}
532
533#[cfg(not(feature = "enterprise"))]
534fn soft_drop_retention_millis(_: Option<std::time::Duration>) -> Option<i64> {
535    None
536}
537
538/// The state of drop table.
539#[derive(Debug, Serialize, Deserialize, AsRefStr, PartialEq)]
540pub enum DropTableState {
541    /// Prepares to drop the table
542    Prepare,
543    /// Deletes metadata logically
544    DeleteMetadata,
545    /// Invalidates Table Cache
546    InvalidateTableCache,
547    /// Drops regions on Datanode
548    DatanodeDropRegions,
549    /// Deletes metadata tombstone permanently
550    DeleteTombstone,
551}