@@ -9,8 +9,12 @@ const logger = createLogger('OutboxRetention')
99
1010/** How long a completed event stays readable for operators after it was enqueued. */
1111export const COMPLETED_OUTBOX_RETENTION_MS = 7 * 24 * 60 * 60_000
12- export const OUTBOX_PRUNE_BATCH_SIZE = 5_000
13- const OUTBOX_PRUNE_BUDGET_MS = 10_000
12+ /**
13+ * Rows deleted per type per run. The processor runs once a minute, so each type drains at most
14+ * 1,000 rows × 1,440 runs = 1.44M rows a day: a steady trickle whose WAL and dead tuples
15+ * autovacuum absorbs, yet five times what recovery can enqueue (200 per run × 1,440 runs).
16+ */
17+ export const OUTBOX_PRUNE_BATCH_SIZE = 1_000
1418
1519/**
1620 * Event types whose completed rows nothing reads again: each carries a fresh random id and is
@@ -44,24 +48,16 @@ async function pruneCompletedBatch(eventType: string, cutoff: Date): Promise<num
4448}
4549
4650/**
47- * Deletes completed prunable events enqueued before the retention window, one oldest-first
48- * batch per type in turn through the type/creation index, until the budget is spent. Pending,
49- * processing and dead-letter rows are never deleted.
51+ * Deletes one oldest-first batch of completed prunable events per type, enqueued before the
52+ * retention window, through the type/creation index. One bounded batch per run keeps a large
53+ * backlog from turning into a burst of deletes; overlapping runs skip each other's locked rows.
54+ * Pending, processing and dead-letter rows are never deleted.
5055 */
5156export async function pruneCompletedOutboxEvents ( now = new Date ( ) ) : Promise < number > {
52- const deadlineAt = Date . now ( ) + OUTBOX_PRUNE_BUDGET_MS
5357 const cutoff = new Date ( now . getTime ( ) - COMPLETED_OUTBOX_RETENTION_MS )
5458 let pruned = 0
55- let backlogged : string [ ] = [ ...PRUNABLE_OUTBOX_EVENT_TYPES ]
56- while ( backlogged . length > 0 && Date . now ( ) < deadlineAt ) {
57- const remaining : string [ ] = [ ]
58- for ( const eventType of backlogged ) {
59- if ( Date . now ( ) >= deadlineAt ) break
60- const deleted = await pruneCompletedBatch ( eventType , cutoff )
61- pruned += deleted
62- if ( deleted === OUTBOX_PRUNE_BATCH_SIZE ) remaining . push ( eventType )
63- }
64- backlogged = remaining
59+ for ( const eventType of PRUNABLE_OUTBOX_EVENT_TYPES ) {
60+ pruned += await pruneCompletedBatch ( eventType , cutoff )
6561 }
6662 if ( pruned > 0 ) logger . info ( 'Pruned completed outbox events' , { pruned } )
6763 return pruned
0 commit comments