MailglassInbound.Internal.Prune (MailglassInbound v0.2.0)

Copy Markdown View Source

Oban-independent batched retention sweep for inbound tables (IOPS-03, D-49-25..30). This is the workhorse: mix mailglass.inbound.prune and the optional MailglassInbound.Prune.Worker cron both call prune/0.

Mirrors Mailglass.Webhook.Pruner's STRUCTURE (:infinity disables a class, per-table telemetry) but UPGRADES the unbounded delete_all to a batched idiom (D-49-27): each table deletes LIMIT 1000 rows at a time (FOR UPDATE SKIP LOCKED), looping until a batch deletes < 1000. The whole sweep is serialized by a session pg_try_advisory_lock — a concurrent second run returns {:ok, :locked_out} and deletes nothing.

Window split (D-49-25) — three physical tables, four windows

  • mailglass_inbound_replay_runs WHERE source = :replay AND age > replay_runs_days (30d)
  • mailglass_inbound_replay_runs WHERE source = :fresh AND age > execution_runs_days (90d)
  • mailglass_inbound_evidence WHERE age > evidence_days (90d)
  • mailglass_inbound_records WHERE age > records_days (90d)

Deletes run child-first (D-49-26): replay_runs (both source filters) -> evidence -> records. FKs are on_delete: :nothing, so a mis-ordered delete fails loudly on the FK (the designed safety net — do NOT switch to CASCADE).

Because the FKs are :nothing, a parent window can never be shorter than a child that references it, or the child-first sweep would leave a surviving child whose parent the next delete tries to remove — tripping a foreign_key_violation (CR-02). MailglassInbound.Config.retention/0 enforces this by clamping evidence_days >= max(execution_runs_days, replay_runs_days) and records_days >= evidence_days, so the default windows (evidence 90d, not the former 30d) never invert against :fresh runs aged 30-90 days.

source is filtered via ExecutionRun (which maps the :source column); NEVER ReplayRun (no :source field — Pitfall 4).

Summary

Functions

The session advisory-lock key the prune sweep acquires. Exposed so tests can acquire it from a separate connection to exercise the single-run guard.

Run the retention sweep. Returns {:ok, counts} where counts carries the per-table deletion counts (+ per-table batch iteration counts) and :status, or {:ok, :locked_out} when another sweep holds the advisory lock.

Functions

lock_key()

@spec lock_key() :: integer()

The session advisory-lock key the prune sweep acquires. Exposed so tests can acquire it from a separate connection to exercise the single-run guard.

prune(opts \\ [])

@spec prune(keyword()) :: {:ok, map()} | {:ok, :locked_out}

Run the retention sweep. Returns {:ok, counts} where counts carries the per-table deletion counts (+ per-table batch iteration counts) and :status, or {:ok, :locked_out} when another sweep holds the advisory lock.