From de5ad47f77d70ada778e9f6557700973b7b96990 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Tue, 19 Aug 2025 16:16:26 +0800 Subject: [PATCH] fix: move prune audit log transaction inside while loop --- .../ee/services/audit-log/audit-log-dal.ts | 76 +++++++++---------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/backend/src/ee/services/audit-log/audit-log-dal.ts b/backend/src/ee/services/audit-log/audit-log-dal.ts index 18a6ba48a..7a986a2d3 100644 --- a/backend/src/ee/services/audit-log/audit-log-dal.ts +++ b/backend/src/ee/services/audit-log/audit-log-dal.ts @@ -14,7 +14,7 @@ import { ActorType } from "@app/services/auth/auth-type"; import { EventType, filterableSecretEvents } from "./audit-log-types"; export interface TAuditLogDALFactory extends Omit, "find"> { - pruneAuditLog: (tx?: knex.Knex) => Promise; + pruneAuditLog: () => Promise; find: ( arg: Omit & { actorId?: string | undefined; @@ -41,6 +41,10 @@ type TFindQuery = { 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) => { const auditLogOrm = ormify(db, TableName.AuditLog); @@ -151,20 +155,20 @@ export const auditLogDALFactory = (db: TDbClient) => { }; // delete all audit log that have expired - const pruneAuditLog: TAuditLogDALFactory["pruneAuditLog"] = async (tx) => { - const runPrune = async (dbClient: knex.Knex) => { - const AUDIT_LOG_PRUNE_BATCH_SIZE = 10000; - const MAX_RETRY_ON_FAILURE = 3; + const pruneAuditLog: TAuditLogDALFactory["pruneAuditLog"] = async () => { + const today = new Date(); + let deletedAuditLogIds: { id: string }[] = []; + let numberOfRetryOnFailure = 0; + let isRetrying = false; - const today = new Date(); - let deletedAuditLogIds: { id: string }[] = []; - let numberOfRetryOnFailure = 0; - let isRetrying = false; + logger.info(`${QueueName.DailyResourceCleanUp}: audit log started`); + do { + try { + // 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`); - do { - try { - const findExpiredLogSubQuery = dbClient(TableName.AuditLog) + const findExpiredLogSubQuery = trx(TableName.AuditLog) .where("expiresAt", "<", today) .where("createdAt", "<", today) // to use audit log partition .orderBy(`${TableName.AuditLog}.createdAt`, "desc") @@ -172,35 +176,25 @@ export const auditLogDALFactory = (db: TDbClient) => { .limit(AUDIT_LOG_PRUNE_BATCH_SIZE); // eslint-disable-next-line no-await-in-loop - deletedAuditLogIds = await dbClient(TableName.AuditLog) - .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`); - }; + const results = await trx(TableName.AuditLog).whereIn("id", findExpiredLogSubQuery).del().returning("id"); - if (tx) { - await runPrune(tx); - } else { - const QUERY_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes - await db.transaction(async (trx) => { - await trx.raw(`SET statement_timeout = ${QUERY_TIMEOUT_MS}`); - await runPrune(trx); - }); - } + return results; + }); + + 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`); }; const create: TAuditLogDALFactory["create"] = async (tx) => {