diff --git a/models/AuditLogModel.php b/models/AuditLogModel.php index f0eea92..dd691cc 100644 --- a/models/AuditLogModel.php +++ b/models/AuditLogModel.php @@ -309,17 +309,28 @@ class AuditLogModel * @param int $daysToKeep Number of days of logs to keep * @return int Number of deleted records */ - public function deleteOldLogs($daysToKeep = 90) + public function deleteOldLogs($daysToKeep = 90, $batchSize = 1000) { + // Batched to bound how long each statement holds row locks — an + // unbounded single DELETE on a large backlog (e.g. the first run after + // enabling/changing retention, or after the cron silently missed runs) + // would otherwise contend with the frequent concurrent INSERTs the + // audit log receives from live traffic. $stmt = $this->conn->prepare( - "DELETE FROM audit_log WHERE created_at < DATE_SUB(NOW(), INTERVAL ? DAY)" + "DELETE FROM audit_log WHERE created_at < DATE_SUB(NOW(), INTERVAL ? DAY) ORDER BY audit_id LIMIT ?" ); - $stmt->bind_param("i", $daysToKeep); - $stmt->execute(); - $affectedRows = $stmt->affected_rows; + $stmt->bind_param("ii", $daysToKeep, $batchSize); + + $totalDeleted = 0; + do { + $stmt->execute(); + $affected = $stmt->affected_rows; + $totalDeleted += $affected; + } while ($affected > 0); + $stmt->close(); - return $affectedRows; + return $totalDeleted; } /**