Skip to content

feat(jobs): daily cleanup for expired idempotency_keys#120

Merged
MBombeck merged 1 commit intomainfrom
feat/idempotency-cleanup-job
May 5, 2026
Merged

feat(jobs): daily cleanup for expired idempotency_keys#120
MBombeck merged 1 commit intomainfrom
feat/idempotency-cleanup-job

Conversation

@MBombeck
Copy link
Copy Markdown
Owner

@MBombeck MBombeck commented May 4, 2026

Summary

Closes Security-Review HIGH-2 from PR #117.

withIdempotency() only purges rows lazily on the next lookup with the same (userId, key, method, path) tuple — most rows never see a retry, so without a sweeper the idempotency_keys table grows unbounded with stale 24h-expired entries.

  • New pg-boss queue idempotency-cleanup runs daily at 03:00 Europe/Berlin (parallel to the weekly Sunday data-backup job)
  • Bulk-deletes via prisma.idempotencyKey.deleteMany({ where: { expiresAt: { lt: new Date() } } }) — uses the existing idx on expiresAt
  • Outcome surfaces as Wide Event annotation idempotency_cleanup_deleted so the run is queryable in Loki
  • Cleanup logic extracted to src/lib/jobs/idempotency-cleanup.ts for unit-testable separation from the pg-boss harness

Files

  • src/lib/jobs/idempotency-cleanup.ts (new) — exported cleanupExpiredIdempotencyKeys(prisma) returns deleted count
  • src/lib/jobs/__tests__/idempotency-cleanup.test.ts (new) — 3 tests: success path, empty table, error propagation
  • src/lib/jobs/reminder-worker.ts — register queue, schedule, payload type, handler, worker (+29 LOC, surgical)

Test plan

  • pnpm typecheck clean
  • pnpm test — 282/282 pass (3 new in idempotency-cleanup.test.ts)
  • Verified existing @@index([expiresAt]) on the model — sweep is index-backed, not a full scan
  • After deploy: confirm queue is registered and first 03:00 run logs idempotency_cleanup_deleted in Grafana/Loki

🤖 Generated with Claude Code

`withIdempotency()` only purges rows lazily on the next lookup with
the same `(userId, key, method, path)` tuple — most rows never see a
retry, so without a sweeper the table grows unbounded with stale
24h-expired entries (Security-Review HIGH-2 from PR #117).

Adds a pg-boss queue that runs daily at 03:00 Europe/Berlin (parallel
to the weekly Sunday data-backup job) and bulk-deletes expired rows.
Outcome surfaces as a Wide Event annotation
`idempotency_cleanup_deleted` so the run is queryable in Loki.

Helper extracted to `src/lib/jobs/idempotency-cleanup.ts` for clean
unit testing without mocking pg-boss internals.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@MBombeck MBombeck merged commit b76d579 into main May 5, 2026
6 checks passed
@MBombeck MBombeck deleted the feat/idempotency-cleanup-job branch May 5, 2026 18:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant