cleanup_audit_log.php: unbounded single-statement DELETE risks lock contention on a large backlog #66

Open
opened 2026-08-31 23:57:38 -04:00 by jared · 0 comments
Owner

Severity: Low

models/AuditLogModel.php::deleteOldLogs() (~lines 312-323), called daily by cron/cleanup_audit_log.php — a single unbounded DELETE FROM audit_log WHERE created_at < ?. created_at is indexed so row selection itself is efficient, but on a large qualifying row set (e.g. the first run after enabling/changing AUDIT_LOG_RETENTION_DAYS, or after the cron job silently failed to run for a while) an unbounded single-statement DELETE holds row locks for the full duration of the transaction — risking lock contention with the frequent concurrent INSERTs the audit log receives from live traffic (nearly every user action logs a row).

Fix: Batch the delete (e.g. DELETE ... ORDER BY created_at LIMIT 1000 in a loop until no rows remain) to bound lock hold time per statement.

**Severity:** Low `models/AuditLogModel.php::deleteOldLogs()` (~lines 312-323), called daily by `cron/cleanup_audit_log.php` — a single unbounded `DELETE FROM audit_log WHERE created_at < ?`. `created_at` is indexed so row selection itself is efficient, but on a large qualifying row set (e.g. the first run after enabling/changing `AUDIT_LOG_RETENTION_DAYS`, or after the cron job silently failed to run for a while) an unbounded single-statement `DELETE` holds row locks for the full duration of the transaction — risking lock contention with the frequent concurrent `INSERT`s the audit log receives from live traffic (nearly every user action logs a row). **Fix:** Batch the delete (e.g. `DELETE ... ORDER BY created_at LIMIT 1000` in a loop until no rows remain) to bound lock hold time per statement.
jared added the performancepriority/low labels 2026-09-08 10:15:47 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/tinker_tickets#66