Bound TTL cleanup query for idempotency cache (op-infra Step 12 follow-up)
Context
The hourly TTL cleanup task in crates/canopy-api/src/idempotency.rs issues:
```sql DELETE FROM idempotency_keys WHERE created_at < now() - interval '24 hours' ```
This is unbounded — under sustained POST traffic plus a long enough cleanup-task outage (or a run that lags hours behind), a single tick could attempt to delete tens of thousands of rows in one statement. With idempotency_keys.cache_key as the primary key, the lock window is small per row, but a giant single-statement delete still holds row-level locks and an ACCESS SHARE on the table for the duration.
Acceptance criteria
-
Replace the unbounded
DELETEwith a bounded loop:```sql DELETE FROM idempotency_keys WHERE cache_key IN ( SELECT cache_key FROM idempotency_keys WHERE created_at < now() - interval '24 hours' LIMIT 1000 ) ```
Loop until
rows_affected < 1000, with a brieftokio::time::sleepbetween iterations to yield to the executor. -
Log the total rows deleted per tick at DEBUG (a single tick can now span multiple statements, so the existing
if rows_affected > 0debug log becomes a running total). -
Unit test the bounded loop logic with a fixture pool (or doc-comment the loop bound).
Out of scope
- Time-based partitioning of
idempotency_keys(overkill for the volume we expect — POST throughput is human-scale). - Switching to a separate vacuum strategy (autovacuum + the bounded delete is enough).
Source
Op-infra plan Step 12, code review of the implementation MR — operational hardening that wasn't called out in the original plan but is good practice for any periodic batch delete.