Prune processed-webhook dedup markers (bounded growth)
processed_webhook_events gained one row per Stripe event and was never
pruned — the daily scheduler prunes its siblings but not this table, so
it grew for the life of the deployment (Run #21 Performance SERIOUS).
Add prune_processed_events and run it daily at 30 days, the retention the
table was created with (migration 065); Stripe won't redeliver events that
old, so the dedup guarantee is unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 files changed,
+29 insertions,
-0 deletions
| 57 |
57 |
|
Ok(())
|
| 58 |
58 |
|
}
|
| 59 |
59 |
|
|
|
60 |
+ |
/// Delete processed-event dedup markers older than `days`. These markers only
|
|
61 |
+ |
/// guard against Stripe *redelivering* an event, which it stops doing within a
|
|
62 |
+ |
/// few days; 30 days is the retention the table was created with (migration
|
|
63 |
+ |
/// 065) but never enforced — without this prune the table grows one row per
|
|
64 |
+ |
/// webhook for the life of the deployment (Run #21 Performance SERIOUS).
|
|
65 |
+ |
#[tracing::instrument(skip_all)]
|
|
66 |
+ |
pub async fn prune_processed_events(pool: &PgPool, days: i64) -> Result<u64> {
|
|
67 |
+ |
let result = sqlx::query(
|
|
68 |
+ |
"DELETE FROM processed_webhook_events \
|
|
69 |
+ |
WHERE processed_at < NOW() - make_interval(days => $1::int)",
|
|
70 |
+ |
)
|
|
71 |
+ |
.bind(days as i32)
|
|
72 |
+ |
.execute(pool)
|
|
73 |
+ |
.await?;
|
|
74 |
+ |
|
|
75 |
+ |
Ok(result.rows_affected())
|
|
76 |
+ |
}
|
|
77 |
+ |
|
| 60 |
78 |
|
/// Insert a failed webhook event for later retry.
|
| 61 |
79 |
|
#[tracing::instrument(skip_all)]
|
| 62 |
80 |
|
pub async fn insert_failed_event(
|
| 286 |
286 |
|
}
|
| 287 |
287 |
|
Err(e) => tracing::error!(error = ?e, "failed to prune page views"),
|
| 288 |
288 |
|
}
|
|
289 |
+ |
|
|
290 |
+ |
// Prune processed-webhook dedup markers older than 30 days. Stripe
|
|
291 |
+ |
// won't redeliver events that old, so they no longer prevent a
|
|
292 |
+ |
// duplicate; the table is otherwise append-only and grows one row
|
|
293 |
+ |
// per webhook forever (Run #21 Performance SERIOUS).
|
|
294 |
+ |
match db::webhook_events::prune_processed_events(&state.db, 30).await {
|
|
295 |
+ |
Ok(n) => {
|
|
296 |
+ |
if n > 0 { tracing::info!(pruned = n, "pruned old processed-webhook markers"); }
|
|
297 |
+ |
}
|
|
298 |
+ |
Err(e) => tracing::error!(error = ?e, "failed to prune processed-webhook markers"),
|
|
299 |
+ |
}
|
| 289 |
300 |
|
}
|
| 290 |
301 |
|
|
| 291 |
302 |
|
// Explicitly release the advisory lock (defense-in-depth: also released
|