From 46cc5269d683227ecb88c2a759db4a36118d08f8 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Thu, 9 Apr 2026 18:04:15 -0700 Subject: [PATCH] fix(log): log cleanup sql query (#4087) * fix(log): log cleanup sql query * perf(log): use startedAt index for cleanup query filter Switch cleanup WHERE clause from createdAt to startedAt to leverage the existing composite index (workspaceId, startedAt), converting a full table scan to an index range scan. Also remove explanatory comment. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Theodore Li Co-authored-by: Waleed Latif Co-authored-by: Claude Opus 4.6 --- apps/sim/app/api/logs/cleanup/route.ts | 35 ++++++-------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/apps/sim/app/api/logs/cleanup/route.ts b/apps/sim/app/api/logs/cleanup/route.ts index 25a0acabf5..85623e7d2a 100644 --- a/apps/sim/app/api/logs/cleanup/route.ts +++ b/apps/sim/app/api/logs/cleanup/route.ts @@ -1,5 +1,5 @@ import { db } from '@sim/db' -import { subscription, user, workflowExecutionLogs, workspace } from '@sim/db/schema' +import { subscription, workflowExecutionLogs, workspace } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { and, eq, inArray, isNull, lt } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' @@ -26,38 +26,19 @@ export async function GET(request: NextRequest) { const retentionDate = new Date() retentionDate.setDate(retentionDate.getDate() - Number(env.FREE_PLAN_LOG_RETENTION_DAYS || '7')) - const freeUsers = await db - .select({ userId: user.id }) - .from(user) + const freeWorkspacesSubquery = db + .select({ id: workspace.id }) + .from(workspace) .leftJoin( subscription, and( - eq(user.id, subscription.referenceId), + eq(subscription.referenceId, workspace.billedAccountUserId), inArray(subscription.status, ENTITLED_SUBSCRIPTION_STATUSES), sqlIsPaid(subscription.plan) ) ) .where(isNull(subscription.id)) - if (freeUsers.length === 0) { - logger.info('No free users found for log cleanup') - return NextResponse.json({ message: 'No free users found for cleanup' }) - } - - const freeUserIds = freeUsers.map((u) => u.userId) - - const workspacesQuery = await db - .select({ id: workspace.id }) - .from(workspace) - .where(inArray(workspace.billedAccountUserId, freeUserIds)) - - if (workspacesQuery.length === 0) { - logger.info('No workspaces found for free users') - return NextResponse.json({ message: 'No workspaces found for cleanup' }) - } - - const workspaceIds = workspacesQuery.map((w) => w.id) - const results = { enhancedLogs: { total: 0, @@ -83,7 +64,7 @@ export async function GET(request: NextRequest) { let batchesProcessed = 0 let hasMoreLogs = true - logger.info(`Starting enhanced logs cleanup for ${workspaceIds.length} workspaces`) + logger.info('Starting enhanced logs cleanup for free-plan workspaces') while (hasMoreLogs && batchesProcessed < MAX_BATCHES) { const oldEnhancedLogs = await db @@ -105,8 +86,8 @@ export async function GET(request: NextRequest) { .from(workflowExecutionLogs) .where( and( - inArray(workflowExecutionLogs.workspaceId, workspaceIds), - lt(workflowExecutionLogs.createdAt, retentionDate) + inArray(workflowExecutionLogs.workspaceId, freeWorkspacesSubquery), + lt(workflowExecutionLogs.startedAt, retentionDate) ) ) .limit(BATCH_SIZE)