Implement recoverable exact result deletion
This commit is contained in:
parent
cdeef3cc1a
commit
791e00f4c0
6 changed files with 2521 additions and 1 deletions
25
crates/remora-deletion/src/error.rs
Normal file
25
crates/remora-deletion/src/error.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use remora_durable::DurableError;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum DeletionError {
|
||||
#[error("resource not found")]
|
||||
NotFound,
|
||||
#[error("job is not terminal")]
|
||||
NotTerminal,
|
||||
#[error("hard deletion has already started")]
|
||||
HardDeleteStarted,
|
||||
#[error("committed result inventory is corrupt")]
|
||||
CorruptInventory,
|
||||
#[error("deletion claim is no longer authoritative")]
|
||||
ClaimLost,
|
||||
#[error("invalid deletion configuration: {0}")]
|
||||
InvalidConfig(String),
|
||||
#[error("durable deletion operation failed: {0}")]
|
||||
Durable(#[from] DurableError),
|
||||
}
|
||||
|
||||
impl From<sqlx::Error> for DeletionError {
|
||||
fn from(error: sqlx::Error) -> Self {
|
||||
Self::Durable(DurableError::Database(error))
|
||||
}
|
||||
}
|
||||
16
crates/remora-deletion/src/lib.rs
Normal file
16
crates/remora-deletion/src/lib.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
//! Durable soft-delete visibility and recoverable exact hard deletion.
|
||||
|
||||
mod error;
|
||||
mod migrate;
|
||||
mod registry;
|
||||
mod service;
|
||||
mod types;
|
||||
|
||||
pub use error::DeletionError;
|
||||
pub use migrate::{migrate, migrator};
|
||||
pub use registry::TargetRegistry;
|
||||
pub use service::DeletionService;
|
||||
pub use types::{
|
||||
DeleteClaim, DeleteIdentity, DeletionConfig, DeletionOperation, DeletionProblem,
|
||||
DeletionProblemClass, DeletionState, DrainOutcome, HardDeleteOutcome, LifecycleMutation,
|
||||
};
|
||||
17
crates/remora-deletion/src/migrate.rs
Normal file
17
crates/remora-deletion/src/migrate.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
use remora_durable::DurableError;
|
||||
use sqlx::PgPool;
|
||||
|
||||
const MIGRATION_TABLE: &str = "_remora_deletion_migrations";
|
||||
|
||||
pub async fn migrate(pool: &PgPool) -> Result<(), DurableError> {
|
||||
let mut migrator = sqlx::migrate!("./migrations");
|
||||
migrator.dangerous_set_table_name(MIGRATION_TABLE);
|
||||
migrator.run(pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn migrator() -> sqlx::migrate::Migrator {
|
||||
let mut migrator = sqlx::migrate!("./migrations");
|
||||
migrator.dangerous_set_table_name(MIGRATION_TABLE);
|
||||
migrator
|
||||
}
|
||||
46
crates/remora-deletion/src/registry.rs
Normal file
46
crates/remora-deletion/src/registry.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use remora_target::TargetStore;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct TargetRegistry {
|
||||
stores: Arc<RwLock<HashMap<(String, String), TargetStore>>>,
|
||||
}
|
||||
|
||||
impl TargetRegistry {
|
||||
#[must_use]
|
||||
pub fn new(stores: impl IntoIterator<Item = TargetStore>) -> Self {
|
||||
let registry = Self::default();
|
||||
for store in stores {
|
||||
registry.register(store);
|
||||
}
|
||||
registry
|
||||
}
|
||||
|
||||
pub fn register(&self, store: TargetStore) {
|
||||
let key = (
|
||||
store.config().target_store_id.clone(),
|
||||
store.config().bucket.clone(),
|
||||
);
|
||||
self.stores
|
||||
.write()
|
||||
.expect("target registry poisoned")
|
||||
.insert(key, store);
|
||||
}
|
||||
|
||||
pub fn remove(&self, target_store: &str, bucket: &str) -> Option<TargetStore> {
|
||||
self.stores
|
||||
.write()
|
||||
.expect("target registry poisoned")
|
||||
.remove(&(target_store.to_owned(), bucket.to_owned()))
|
||||
}
|
||||
|
||||
pub(crate) fn resolve(&self, target_store: &str, bucket: &str) -> Option<TargetStore> {
|
||||
self.stores
|
||||
.read()
|
||||
.expect("target registry poisoned")
|
||||
.get(&(target_store.to_owned(), bucket.to_owned()))
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
1134
crates/remora-deletion/src/service.rs
Normal file
1134
crates/remora-deletion/src/service.rs
Normal file
File diff suppressed because it is too large
Load diff
88
crates/remora-deletion/src/types.rs
Normal file
88
crates/remora-deletion/src/types.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use remora_domain::OpaqueId;
|
||||
use remora_target::{MultipartIdentity, ObjectIdentity};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DeletionConfig {
|
||||
pub claim_ttl: Duration,
|
||||
}
|
||||
|
||||
impl Default for DeletionConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
claim_ttl: Duration::from_secs(30),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum LifecycleMutation {
|
||||
Changed,
|
||||
Unchanged,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum DeletionState {
|
||||
Queued,
|
||||
Running,
|
||||
RetryWait,
|
||||
Blocked,
|
||||
Completed,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum DeletionProblemClass {
|
||||
Authorization,
|
||||
TargetUnavailable,
|
||||
Configuration,
|
||||
Internal,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct DeletionProblem {
|
||||
pub class: DeletionProblemClass,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct DeletionOperation {
|
||||
pub id: OpaqueId,
|
||||
pub job_id: OpaqueId,
|
||||
pub state: DeletionState,
|
||||
pub problem: Option<DeletionProblem>,
|
||||
pub created_at: OffsetDateTime,
|
||||
pub updated_at: OffsetDateTime,
|
||||
pub completed_at: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct HardDeleteOutcome {
|
||||
pub mutation: LifecycleMutation,
|
||||
pub operation: DeletionOperation,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum DeleteIdentity {
|
||||
WriteIntent(ObjectIdentity),
|
||||
Object(ObjectIdentity),
|
||||
Manifest(ObjectIdentity),
|
||||
Multipart(MultipartIdentity),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct DeleteClaim {
|
||||
pub item_id: u64,
|
||||
pub operation_id: OpaqueId,
|
||||
pub claim_token: OpaqueId,
|
||||
pub identity: DeleteIdentity,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum DrainOutcome {
|
||||
Idle,
|
||||
ItemCompleted,
|
||||
Blocked,
|
||||
Completed,
|
||||
}
|
||||
Loading…
Reference in a new issue