Batch the audit log retention DELETE to bound lock hold time (#66)
deleteOldLogs() ran a single unbounded DELETE. created_at is indexed so row selection itself is cheap, but on a large qualifying set (first run after enabling/changing AUDIT_LOG_RETENTION_DAYS, or after the cron silently missed runs) an unbounded single-statement DELETE holds row locks for the full duration — risking contention with the frequent concurrent INSERTs the audit log receives from live traffic. Now deletes in batches of 1000 (parameterized), looping until nothing qualifies. Verified against a local MariaDB instance with a batch size of 10 forcing multiple loop iterations: deleted exactly the stale rows, left recent rows untouched, correct total count returned. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MGDKHiU5RJdo3dqQUDow3X
This commit is contained in:
@@ -309,17 +309,28 @@ class AuditLogModel
|
|||||||
* @param int $daysToKeep Number of days of logs to keep
|
* @param int $daysToKeep Number of days of logs to keep
|
||||||
* @return int Number of deleted records
|
* @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(
|
$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->bind_param("ii", $daysToKeep, $batchSize);
|
||||||
$stmt->execute();
|
|
||||||
$affectedRows = $stmt->affected_rows;
|
$totalDeleted = 0;
|
||||||
|
do {
|
||||||
|
$stmt->execute();
|
||||||
|
$affected = $stmt->affected_rows;
|
||||||
|
$totalDeleted += $affected;
|
||||||
|
} while ($affected > 0);
|
||||||
|
|
||||||
$stmt->close();
|
$stmt->close();
|
||||||
|
|
||||||
return $affectedRows;
|
return $totalDeleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user