Skip to main content

purge_unattached

Function purge_unattached 

Source
pub fn purge_unattached(
    ctx: &Ctx,
    prefix: &str,
    table: &str,
    column: &str,
    max_age: i64,
    cursor: Option<&str>,
) -> impl Future<Output = Result<Purged>> + Send + use<>
Expand description

Deletes the objects under prefix older than max_age seconds that no table.column row references, one listing page per call.

Direct uploads that were never attached (a closed tab, a failed form) would stay in R2 forever: Rails’ ActiveStorage::Blob.unattached purge. Each call lists one page of up to 1,000 keys from cursor (None to start), keeps those uploaded more than max_age seconds ago, looks them up with SELECT <column> FROM <table> WHERE <column> IN (...) (100 keys per query) and deletes the others in one call. Pass the returned Purged::cursor to the next call until it is None. table and column are identifiers from your code (letters, digits, _); give the column an index (CREATE UNIQUE INDEX ... ON lessons(video_key)) so each lookup reads only matching rows. Keep max_age well above 10 minutes (the lifetime of an upload URL) plus the time a form stays open: a day is safe.

Free plan: one R2 class A operation per call (the listing), one D1 query per 100 old keys, deletes free; a few ms of CPU per 1,000 keys. Run it from a scheduled task, a few pages per run.

§Errors

Error::Internal (500) when table or column is not a plain identifier, the STORAGE or DB binding is missing, or R2 or D1 fails (nothing is deleted for the page then).

§Examples

use ocre::{Ctx, Result, storage};

// src/schedules/purge_uploads.rs, run every day: at most 5 pages (5 class A operations).
pub async fn run(ctx: &Ctx) -> Result<()> {
    let mut cursor = None;
    for _ in 0..5 {
        let purged = storage::purge_unattached(ctx, "uploads/videos", "lessons", "video_key", 86_400, cursor.as_deref()).await?;
        println!("purge_uploads: {} unattached uploads deleted", purged.deleted.len());
        cursor = purged.cursor;
        if cursor.is_none() {
            break;
        }
    }
    Ok(())
}