Skip to content

✨ feat(uploads): idempotent remove + age-graced sweep of multi-path-unreferenced GridFS blobs #4013

Description

@PierreBrisorgueil

What — two additions to the uploads module, promoting a pattern proven in a downstream consumer:

  1. remove() becomes idempotent: a lookup that matches no record (e.g. a filename already deleted by a previous pass) is a debug-logged NO-OP returning { deletedCount: 0, notFound: true } — not a thrown error. A genuine GridFS bucket failure on an existing file still throws.
  2. New sweepUnreferenced(kind, collection, paths, minAgeMs): sweep GridFS blobs of a given kind that are unreferenced by ANY of several reference paths on another collection (scalar or array-of-subdocuments paths, normalised before the OR check), respecting a minimum-age grace window so a blob written just before its referencing document persists is never swept.

Why — retention/cleanup jobs that re-run over the same window need remove() to treat "already gone" as success (otherwise every re-pass logs phantom errors). And purge() cannot express "referenced by path A OR B": its $lookup equality join is right for exactly one reference key; extending it would either lose the indexed join for existing callers or reintroduce per-row sub-queries. Multi-path referencing is generic (any consumer storing one blob referenced from several fields).

Scope — modules/uploads/repositories/uploads.repository.js (+ service wiring if the repo layer is not exported directly):

  • remove() no-op semantics as above (logger.debug, not warn).
  • sweepUnreferenced: ONE streaming pass over the referencing collection building an in-memory Set of referenced filenames across all paths, then one streaming pass over age-eligible candidate uploads checking Set membership. Deliberately a separate function from purge() (different, non-interchangeable query strategies — document that in the JSDoc).
  • Unit tests: no-op remove (missing record) · genuine failure still throws · sweep respects the grace window · scalar path · array-of-subdocs path · blob referenced by only ONE of several paths is KEPT (the data-loss guard).

Scope: validated 2026-08-04

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions