Merge pull request #4399 from Infisical/audit-log-transaction-fix

fix(audit-logs): move prune audit log transaction inside while loop
This commit is contained in:
Sheen
2025-08-23 12:17:14 +08:00
committed by GitHub
@@ -14,7 +14,7 @@ import { ActorType } from "@app/services/auth/auth-type";
import { EventType, filterableSecretEvents } from "./audit-log-types"; import { EventType, filterableSecretEvents } from "./audit-log-types";
export interface TAuditLogDALFactory extends Omit<TOrmify<TableName.AuditLog>, "find"> { export interface TAuditLogDALFactory extends Omit<TOrmify<TableName.AuditLog>, "find"> {
pruneAuditLog: (tx?: knex.Knex) => Promise<void>; pruneAuditLog: () => Promise<void>;
find: ( find: (
arg: Omit<TFindQuery, "actor" | "eventType"> & { arg: Omit<TFindQuery, "actor" | "eventType"> & {
actorId?: string | undefined; actorId?: string | undefined;
@@ -41,6 +41,10 @@ type TFindQuery = {
offset?: number; offset?: number;
}; };
const QUERY_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
const AUDIT_LOG_PRUNE_BATCH_SIZE = 10000;
const MAX_RETRY_ON_FAILURE = 3;
export const auditLogDALFactory = (db: TDbClient) => { export const auditLogDALFactory = (db: TDbClient) => {
const auditLogOrm = ormify(db, TableName.AuditLog); const auditLogOrm = ormify(db, TableName.AuditLog);
@@ -151,20 +155,20 @@ export const auditLogDALFactory = (db: TDbClient) => {
}; };
// delete all audit log that have expired // delete all audit log that have expired
const pruneAuditLog: TAuditLogDALFactory["pruneAuditLog"] = async (tx) => { const pruneAuditLog: TAuditLogDALFactory["pruneAuditLog"] = async () => {
const runPrune = async (dbClient: knex.Knex) => { const today = new Date();
const AUDIT_LOG_PRUNE_BATCH_SIZE = 10000; let deletedAuditLogIds: { id: string }[] = [];
const MAX_RETRY_ON_FAILURE = 3; let numberOfRetryOnFailure = 0;
let isRetrying = false;
const today = new Date(); logger.info(`${QueueName.DailyResourceCleanUp}: audit log started`);
let deletedAuditLogIds: { id: string }[] = []; do {
let numberOfRetryOnFailure = 0; try {
let isRetrying = false; // eslint-disable-next-line no-await-in-loop
deletedAuditLogIds = await db.transaction(async (trx) => {
await trx.raw(`SET statement_timeout = ${QUERY_TIMEOUT_MS}`);
logger.info(`${QueueName.DailyResourceCleanUp}: audit log started`); const findExpiredLogSubQuery = trx(TableName.AuditLog)
do {
try {
const findExpiredLogSubQuery = dbClient(TableName.AuditLog)
.where("expiresAt", "<", today) .where("expiresAt", "<", today)
.where("createdAt", "<", today) // to use audit log partition .where("createdAt", "<", today) // to use audit log partition
.orderBy(`${TableName.AuditLog}.createdAt`, "desc") .orderBy(`${TableName.AuditLog}.createdAt`, "desc")
@@ -172,35 +176,25 @@ export const auditLogDALFactory = (db: TDbClient) => {
.limit(AUDIT_LOG_PRUNE_BATCH_SIZE); .limit(AUDIT_LOG_PRUNE_BATCH_SIZE);
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
deletedAuditLogIds = await dbClient(TableName.AuditLog) const results = await trx(TableName.AuditLog).whereIn("id", findExpiredLogSubQuery).del().returning("id");
.whereIn("id", findExpiredLogSubQuery)
.del()
.returning("id");
numberOfRetryOnFailure = 0; // reset
} catch (error) {
numberOfRetryOnFailure += 1;
deletedAuditLogIds = [];
logger.error(error, "Failed to delete audit log on pruning");
} finally {
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => {
setTimeout(resolve, 10); // time to breathe for db
});
}
isRetrying = numberOfRetryOnFailure > 0;
} while (deletedAuditLogIds.length > 0 || (isRetrying && numberOfRetryOnFailure < MAX_RETRY_ON_FAILURE));
logger.info(`${QueueName.DailyResourceCleanUp}: audit log completed`);
};
if (tx) { return results;
await runPrune(tx); });
} else {
const QUERY_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes numberOfRetryOnFailure = 0; // reset
await db.transaction(async (trx) => { } catch (error) {
await trx.raw(`SET statement_timeout = ${QUERY_TIMEOUT_MS}`); numberOfRetryOnFailure += 1;
await runPrune(trx); deletedAuditLogIds = [];
}); logger.error(error, "Failed to delete audit log on pruning");
} } finally {
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => {
setTimeout(resolve, 10); // time to breathe for db
});
}
isRetrying = numberOfRetryOnFailure > 0;
} while (deletedAuditLogIds.length > 0 || (isRetrying && numberOfRetryOnFailure < MAX_RETRY_ON_FAILURE));
logger.info(`${QueueName.DailyResourceCleanUp}: audit log completed`);
}; };
const create: TAuditLogDALFactory["create"] = async (tx) => { const create: TAuditLogDALFactory["create"] = async (tx) => {