Skip to content

Commit 04c8c8d

Browse files
Cleanup orphaned using ids, not timestamp sorting
1 parent be59d2b commit 04c8c8d

File tree

3 files changed

+35
-30
lines changed

3 files changed

+35
-30
lines changed

apps/sim/background/cleanup-soft-deletes.ts

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,12 @@ import {
1515
import { createLogger } from '@sim/logger'
1616
import { task } from '@trigger.dev/sdk'
1717
import { and, inArray, isNotNull, lt } from 'drizzle-orm'
18-
import type { PgColumn, PgTable } from 'drizzle-orm/pg-core'
1918
import { type CleanupJobPayload, resolveCleanupScope } from '@/lib/billing/cleanup-dispatcher'
2019
import {
2120
batchDeleteByWorkspaceAndTimestamp,
2221
DEFAULT_BATCH_SIZE,
2322
DEFAULT_MAX_BATCHES_PER_TABLE,
24-
type TableCleanupResult,
23+
deleteRowsById,
2524
} from '@/lib/cleanup/batch-delete'
2625
import { prepareChatCleanup } from '@/lib/cleanup/chat-cleanup'
2726
import type { StorageContext } from '@/lib/uploads'
@@ -112,25 +111,6 @@ async function cleanupWorkspaceFileStorage(
112111
return stats
113112
}
114113

115-
async function deleteRowsById(
116-
tableDef: PgTable,
117-
idCol: PgColumn,
118-
ids: string[],
119-
tableName: string
120-
): Promise<TableCleanupResult> {
121-
const result: TableCleanupResult = { table: tableName, deleted: 0, failed: 0 }
122-
if (ids.length === 0) return result
123-
try {
124-
const deleted = await db.delete(tableDef).where(inArray(idCol, ids)).returning({ id: idCol })
125-
result.deleted = deleted.length
126-
logger.info(`[${tableName}] Deleted ${deleted.length} rows`)
127-
} catch (error) {
128-
result.failed++
129-
logger.error(`[${tableName}] Delete failed:`, { error })
130-
}
131-
return result
132-
}
133-
134114
/**
135115
* Tables cleaned by the generic workspace-scoped batched DELETE. Tables whose
136116
* hard-delete triggers external side effects (workflow → copilot chats cascade,

apps/sim/background/cleanup-tasks.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
batchDeleteByWorkspaceAndTimestamp,
1616
DEFAULT_BATCH_SIZE,
1717
DEFAULT_MAX_BATCHES_PER_TABLE,
18+
deleteRowsById,
1819
type TableCleanupResult,
1920
} from '@/lib/cleanup/batch-delete'
2021
import { prepareChatCleanup } from '@/lib/cleanup/chat-cleanup'
@@ -161,15 +162,14 @@ export async function runCleanupTasks(payload: CleanupJobPayload): Promise<void>
161162
tableName: `${label}/copilotRuns`,
162163
})
163164

164-
// Delete copilot chats (has workspaceId directly)
165-
const chatsResult = await batchDeleteByWorkspaceAndTimestamp({
166-
tableDef: copilotChats,
167-
workspaceIdCol: copilotChats.workspaceId,
168-
timestampCol: copilotChats.updatedAt,
169-
workspaceIds,
170-
retentionDate,
171-
tableName: `${label}/copilotChats`,
172-
})
165+
// Delete copilot chats using the exact IDs collected above so the chat
166+
// cleanup (S3 + copilot backend) and the DB delete can never disagree.
167+
const chatsResult = await deleteRowsById(
168+
copilotChats,
169+
copilotChats.id,
170+
doomedChatIds,
171+
`${label}/copilotChats`
172+
)
173173

174174
// Delete mothership inbox tasks (has workspaceId directly)
175175
const inboxResult = await batchDeleteByWorkspaceAndTimestamp({

apps/sim/lib/cleanup/batch-delete.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,28 @@ export async function batchDeleteByWorkspaceAndTimestamp({
9292

9393
return result
9494
}
95+
96+
/**
97+
* Delete rows by an explicit list of IDs. Use this when the IDs were selected
98+
* upstream (e.g., to drive external cleanup like S3 deletes or a backend API
99+
* call) so the DB delete cannot drift from the upstream selection. Paired with
100+
* `batchDeleteByWorkspaceAndTimestamp` for tables with no external side effects.
101+
*/
102+
export async function deleteRowsById(
103+
tableDef: PgTable,
104+
idCol: PgColumn,
105+
ids: string[],
106+
tableName: string
107+
): Promise<TableCleanupResult> {
108+
const result: TableCleanupResult = { table: tableName, deleted: 0, failed: 0 }
109+
if (ids.length === 0) return result
110+
try {
111+
const deleted = await db.delete(tableDef).where(inArray(idCol, ids)).returning({ id: idCol })
112+
result.deleted = deleted.length
113+
logger.info(`[${tableName}] Deleted ${deleted.length} rows`)
114+
} catch (error) {
115+
result.failed++
116+
logger.error(`[${tableName}] Delete failed:`, { error })
117+
}
118+
return result
119+
}

0 commit comments

Comments
 (0)