janitor

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 21, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package janitor runs the low-urgency hourly cleanup sweep as a River periodic on QueueMaintenance. It replaces the hand-rolled time.Ticker(1h) goroutine that used to live in cmd/e2a/main.go, mirroring the webhook auto-disable janitor (internal/webhookdelivery.MaintenanceJobs) and the inbound retention sweep (internal/inboundprocess.RetentionWorker).

The sweep runs every prune SEQUENTIALLY (not concurrently — a janitor must be gentle on Postgres and there is no latency benefit to parallelism) and CONTINUES PAST any individual prune's error, so one failing DELETE never skips the rest. Sweep accumulates and returns the errors; the worker log-and-swallows them (returning nil) so a transient DB blip doesn't spin River's retry machinery — the next interval picks the work back up.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type DeliveryPruner

type DeliveryPruner interface {
	DeleteExpiredDeliveries(ctx context.Context) (int64, error)
}

DeliveryPruner prunes expired webhook delivery records (*webhook.DeliveryStore).

type IdempotencyPruner

type IdempotencyPruner interface {
	Sweep(ctx context.Context) (int64, error)
}

IdempotencyPruner sweeps idempotency keys past their TTL (*idempotency.Store).

type Janitor

type Janitor struct {
	// contains filtered or unexported fields
}

Janitor holds the prune dependencies and runs the cleanup sweep. All fields are required except oauth, which is nil when the OAuth provider is disabled.

func New

func New(
	messages MessagePruner,
	deliveries DeliveryPruner,
	subscribers SubscriberPruner,
	webhookEvent WebhookEventPruner,
	oauth OAuthPruner,
	idempotency IdempotencyPruner,
	metrics Metrics,
) *Janitor

New builds the Janitor. oauth may be nil (interface, not a typed-nil pointer) to skip the OAuth cleanup pass.

func (*Janitor) Sweep

func (j *Janitor) Sweep(ctx context.Context) error

Sweep runs every prune once, sequentially, continuing past any individual error. It preserves the exact per-prune logging and metrics emission of the old hand-rolled ticker. Errors are accumulated and returned joined; the caller (the worker) logs-and-swallows so one failing prune never aborts the run or spins River's retry.

type JanitorArgs

type JanitorArgs struct{}

JanitorArgs drives the periodic cleanup sweep. No fields — each run prunes the whole expired set.

func (JanitorArgs) Kind

func (JanitorArgs) Kind() string

type MaintenanceJobs

type MaintenanceJobs struct {
	// contains filtered or unexported fields
}

MaintenanceJobs is the jobs.Registrar for the cleanup janitor: it contributes the MaintenanceWorker and a periodic that fires it on QueueMaintenance. No enqueuer needed — the schedule is the only trigger.

func NewMaintenanceJobs

func NewMaintenanceJobs(j *Janitor) *MaintenanceJobs

NewMaintenanceJobs builds the registrar around a Janitor.

func (*MaintenanceJobs) RegisterJobs

func (m *MaintenanceJobs) RegisterJobs(w *river.Workers) []*river.PeriodicJob

RegisterJobs adds the maintenance worker + its periodic schedule. Mirrors the webhook janitor and senderidentity reaper: routed to QueueMaintenance, no UniqueOpts (River's periodic scheduler already inserts at most one per interval and a completed run must not dedup-block the next), RunOnStart:false (first sweep after one interval, matching the old ticker's first-tick-after-1h).

Overlap tradeoff (inherited from the pre-River janitor, not introduced by this migration): with no UniqueOpts and QueueMaintenance's small worker pool, a sweep that runs longer than the 1h interval can overlap the next tick. That is safe — every prune is an idempotent batched DELETE on a distinct table and a partial sweep is simply finished by the next interval; overlapping sweeps only race to delete the same already-doomed rows. It only bites against a large, long-unpruned table (e.g. the first deploy); steady-state sweeps are near-empty. The prunes' unbounded-DELETE hazard is resolved — each is now a ctx-aware ctid-LIMIT drain loop (see the DeleteExpired* store methods) bounded by MaintenanceWorker.Timeout.

type MaintenanceWorker

type MaintenanceWorker struct {
	river.WorkerDefaults[JanitorArgs]
	// contains filtered or unexported fields
}

MaintenanceWorker runs the cleanup sweep once per scheduled job. Sweep's errors are logged (with a janitor prefix) and swallowed — Work returns nil so a transient DB blip never spins River's retry for a best-effort idempotent sweep; the next interval picks it up.

func NewMaintenanceWorker

func NewMaintenanceWorker(j *Janitor) *MaintenanceWorker

NewMaintenanceWorker builds the worker around a Janitor. Exported so tests can drive Work directly (RegisterJobs builds an identical one for the client).

func (*MaintenanceWorker) Timeout

Timeout gives the sweep a bounded 5-minute budget instead of River's 60s default JobTimeout. Every prune is now a ctx-aware batched delete (each 5000-row batch autocommits), so a cut is always safe — partial progress persists and the next hourly tick resumes — but 60s is tight for a first-deploy backlog where `messages` (pruned first) can eat the whole budget and starve the later tables. Five minutes lets a realistic backlog drain in far fewer ticks while still capping how long one sweep holds a QueueMaintenance slot. Deliberately NOT -1 (unbounded) like the HITL send-sweep, which must never be cut mid-SMTP; the janitor's deletes are safe to interrupt.

func (*MaintenanceWorker) Work

type MessagePruner

type MessagePruner interface {
	DeleteExpiredMessages(ctx context.Context) (int64, error)
	DeleteExpiredUserSessions(ctx context.Context) (int64, error)
	PurgeDeletedAgents(ctx context.Context) (int64, error)
}

MessagePruner purges messages past trash retention, expired user sessions, and trashed agents past their retention window (all live on *identity.Store today). DeleteExpiredMessages covers both message arms: live rows past their trashed rows past TrashRetention.

type Metrics

type Metrics interface {
	JanitorRowsDeleted(table string, count int)
}

Metrics is the narrow slice of telemetry.Metrics the janitor emits. Injectable so tests don't need a real backend; satisfied by *telemetry.Log / telemetry.NoOp.

type OAuthPruner

type OAuthPruner interface {
	CleanupExpired(ctx context.Context, now time.Time) (oauth.RetentionResult, error)
}

OAuthPruner cleans up expired OAuth rows (*oauth.Storage). Optional: when the OAuth provider isn't configured the dependency is nil and the pass is skipped (preserving the old `if oauthStorage != nil` guard). Pass a nil interface — NOT a typed nil *oauth.Storage — to skip it.

type SubscriberPruner

type SubscriberPruner interface {
	DeleteExpiredSubscriberDeliveries(ctx context.Context) (int, error)
}

SubscriberPruner prunes expired webhook subscriber deliveries (*webhook.SubscriberStore).

type WebhookEventPruner

type WebhookEventPruner interface {
	DeleteExpiredWebhookEvents(ctx context.Context) (int, error)
}

WebhookEventPruner prunes expired webhook_events rows (webhookpub outbox).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL