Skip to content

Commit 3f7698c

Browse files
authored
perf(db): reduce read/write fanout across hot paths (#4704)
* perf(db): reduce read/write fanout across hot paths * fix(templates): include name/workflowId/status/tags in DELETE projection * fix(db): address review feedback (joinedAt backfill, mcp delete error, chatDeploy projection, sql newline) * fix(webhooks): include NULL failedCount in markWebhookSuccess guard
1 parent d7ed3c2 commit 3f7698c

28 files changed

Lines changed: 17014 additions & 141 deletions

File tree

apps/sim/app/api/templates/[id]/route.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,17 @@ export const DELETE = withRouteHandler(
311311
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
312312
}
313313

314-
const existing = await db.select().from(templates).where(eq(templates.id, id)).limit(1)
314+
const existing = await db
315+
.select({
316+
name: templates.name,
317+
workflowId: templates.workflowId,
318+
creatorId: templates.creatorId,
319+
status: templates.status,
320+
tags: templates.tags,
321+
})
322+
.from(templates)
323+
.where(eq(templates.id, id))
324+
.limit(1)
315325
if (existing.length === 0) {
316326
logger.warn(`[${requestId}] Template not found for delete: ${id}`)
317327
return NextResponse.json({ error: 'Template not found' }, { status: 404 })

apps/sim/app/api/users/me/settings/route.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,24 @@ export const GET = withRouteHandler(async () => {
3939
}
4040

4141
const userId = session.user.id
42-
const result = await db.select().from(settings).where(eq(settings.userId, userId)).limit(1)
42+
const result = await db
43+
.select({
44+
theme: settings.theme,
45+
autoConnect: settings.autoConnect,
46+
telemetryEnabled: settings.telemetryEnabled,
47+
emailPreferences: settings.emailPreferences,
48+
billingUsageNotificationsEnabled: settings.billingUsageNotificationsEnabled,
49+
showTrainingControls: settings.showTrainingControls,
50+
superUserModeEnabled: settings.superUserModeEnabled,
51+
mothershipEnvironment: settings.mothershipEnvironment,
52+
errorNotificationsEnabled: settings.errorNotificationsEnabled,
53+
snapToGridSize: settings.snapToGridSize,
54+
showActionBar: settings.showActionBar,
55+
lastActiveWorkspaceId: settings.lastActiveWorkspaceId,
56+
})
57+
.from(settings)
58+
.where(eq(settings.userId, userId))
59+
.limit(1)
4360

4461
if (!result.length) {
4562
return NextResponse.json({ data: defaultSettings }, { status: 200 })

apps/sim/app/api/v1/admin/organizations/[id]/billing/route.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,11 @@ export const GET = withRouteHandler(
5555
try {
5656
if (!isBillingEnabled) {
5757
const [[orgData], [memberCount]] = await Promise.all([
58-
db.select().from(organization).where(eq(organization.id, organizationId)).limit(1),
58+
db
59+
.select({ id: organization.id, name: organization.name })
60+
.from(organization)
61+
.where(eq(organization.id, organizationId))
62+
.limit(1),
5963
db
6064
.select({ count: count() })
6165
.from(member)

apps/sim/app/api/v1/admin/organizations/route.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,22 @@ export const GET = withRouteHandler(
7171
try {
7272
const [countResult, organizations] = await Promise.all([
7373
db.select({ total: count() }).from(organization),
74-
db.select().from(organization).orderBy(organization.name).limit(limit).offset(offset),
74+
db
75+
.select({
76+
id: organization.id,
77+
name: organization.name,
78+
slug: organization.slug,
79+
logo: organization.logo,
80+
orgUsageLimit: organization.orgUsageLimit,
81+
storageUsedBytes: organization.storageUsedBytes,
82+
departedMemberUsage: organization.departedMemberUsage,
83+
createdAt: organization.createdAt,
84+
updatedAt: organization.updatedAt,
85+
})
86+
.from(organization)
87+
.orderBy(organization.name)
88+
.limit(limit)
89+
.offset(offset),
7590
])
7691

7792
const total = countResult[0].total

apps/sim/app/api/v1/admin/types.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,23 @@ export interface AdminWorkflowDetail extends AdminWorkflow {
198198
edgeCount: number
199199
}
200200

201-
export function toAdminWorkflow(dbWorkflow: DbWorkflow): AdminWorkflow {
201+
export type AdminWorkflowSource = Pick<
202+
DbWorkflow,
203+
| 'id'
204+
| 'name'
205+
| 'description'
206+
| 'color'
207+
| 'workspaceId'
208+
| 'folderId'
209+
| 'isDeployed'
210+
| 'deployedAt'
211+
| 'runCount'
212+
| 'lastRunAt'
213+
| 'createdAt'
214+
| 'updatedAt'
215+
>
216+
217+
export function toAdminWorkflow(dbWorkflow: AdminWorkflowSource): AdminWorkflow {
202218
return {
203219
id: dbWorkflow.id,
204220
name: dbWorkflow.name,
@@ -443,7 +459,20 @@ export interface AdminOrganizationDetail extends AdminOrganization {
443459
subscription: AdminSubscription | null
444460
}
445461

446-
export function toAdminOrganization(dbOrg: DbOrganization): AdminOrganization {
462+
export type AdminOrganizationSource = Pick<
463+
DbOrganization,
464+
| 'id'
465+
| 'name'
466+
| 'slug'
467+
| 'logo'
468+
| 'orgUsageLimit'
469+
| 'storageUsedBytes'
470+
| 'departedMemberUsage'
471+
| 'createdAt'
472+
| 'updatedAt'
473+
>
474+
475+
export function toAdminOrganization(dbOrg: AdminOrganizationSource): AdminOrganization {
447476
return {
448477
id: dbOrg.id,
449478
name: dbOrg.name,

apps/sim/app/api/v1/admin/workflows/route.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,25 @@ export const GET = withRouteHandler(
3333
try {
3434
const [countResult, workflows] = await Promise.all([
3535
db.select({ total: count() }).from(workflow),
36-
db.select().from(workflow).orderBy(workflow.name).limit(limit).offset(offset),
36+
db
37+
.select({
38+
id: workflow.id,
39+
name: workflow.name,
40+
description: workflow.description,
41+
color: workflow.color,
42+
workspaceId: workflow.workspaceId,
43+
folderId: workflow.folderId,
44+
isDeployed: workflow.isDeployed,
45+
deployedAt: workflow.deployedAt,
46+
runCount: workflow.runCount,
47+
lastRunAt: workflow.lastRunAt,
48+
createdAt: workflow.createdAt,
49+
updatedAt: workflow.updatedAt,
50+
})
51+
.from(workflow)
52+
.orderBy(workflow.name)
53+
.limit(limit)
54+
.offset(offset),
3755
])
3856

3957
const total = countResult[0].total

apps/sim/app/api/v1/admin/workspaces/[id]/workflows/route.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,20 @@ export const GET = withRouteHandler(
6363
.from(workflow)
6464
.where(and(eq(workflow.workspaceId, workspaceId), isNull(workflow.archivedAt))),
6565
db
66-
.select()
66+
.select({
67+
id: workflow.id,
68+
name: workflow.name,
69+
description: workflow.description,
70+
color: workflow.color,
71+
workspaceId: workflow.workspaceId,
72+
folderId: workflow.folderId,
73+
isDeployed: workflow.isDeployed,
74+
deployedAt: workflow.deployedAt,
75+
runCount: workflow.runCount,
76+
lastRunAt: workflow.lastRunAt,
77+
createdAt: workflow.createdAt,
78+
updatedAt: workflow.updatedAt,
79+
})
6780
.from(workflow)
6881
.where(and(eq(workflow.workspaceId, workspaceId), isNull(workflow.archivedAt)))
6982
.orderBy(workflow.name)

apps/sim/app/api/v1/tables/[tableId]/rows/[rowId]/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ export const DELETE = withRouteHandler(async (request: NextRequest, context: Row
230230
eq(userTableRows.workspaceId, workspaceId)
231231
)
232232
)
233-
.returning()
233+
.returning({ id: userTableRows.id })
234234

235235
if (!deletedRow) {
236236
return NextResponse.json({ error: 'Row not found' }, { status: 404 })

apps/sim/app/api/webhooks/[id]/route.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,23 @@ export const PATCH = withRouteHandler(
137137
}
138138
await assertWorkflowMutable(webhookData.workflow.id)
139139

140+
const setClause: Partial<typeof webhook.$inferInsert> = {}
141+
if (isActive !== undefined && isActive !== webhooks[0].webhook.isActive) {
142+
setClause.isActive = isActive
143+
}
144+
if (failedCount !== undefined && failedCount !== webhooks[0].webhook.failedCount) {
145+
setClause.failedCount = failedCount
146+
}
147+
148+
if (Object.keys(setClause).length === 0) {
149+
logger.info(`[${requestId}] No-op webhook PATCH (no field changes): ${id}`)
150+
return NextResponse.json({ webhook: webhooks[0].webhook }, { status: 200 })
151+
}
152+
153+
setClause.updatedAt = new Date()
140154
const updatedWebhook = await db
141155
.update(webhook)
142-
.set({
143-
isActive: isActive !== undefined ? isActive : webhooks[0].webhook.isActive,
144-
failedCount: failedCount !== undefined ? failedCount : webhooks[0].webhook.failedCount,
145-
updatedAt: new Date(),
146-
})
156+
.set(setClause)
147157
.where(eq(webhook.id, id))
148158
.returning()
149159

apps/sim/app/workspace/[workspaceId]/logs/logs.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ import {
103103
const LOGS_PER_PAGE = 50 as const
104104
const SORTABLE_COLUMNS: readonly LogSortBy[] = ['date', 'duration', 'cost', 'status'] as const
105105
const REFRESH_SPINNER_DURATION_MS = 1000 as const
106+
const LIVE_REFRESH_INTERVAL_MS = 10_000 as const
107+
const ACTIVE_RUN_DETAIL_REFRESH_MS = 3_000 as const
106108

107109
const LOG_COLUMNS: ResourceColumn[] = [
108110
{ id: 'workflow', header: 'Workflow' },
@@ -317,7 +319,7 @@ export default function Logs() {
317319
(query: { state: { data?: WorkflowLogDetail } }) => {
318320
if (!isLive) return false
319321
const status = query.state.data?.status
320-
return status === 'running' || status === 'pending' ? 3000 : false
322+
return status === 'running' || status === 'pending' ? ACTIVE_RUN_DETAIL_REFRESH_MS : false
321323
},
322324
[isLive]
323325
)
@@ -365,7 +367,7 @@ export default function Logs() {
365367
)
366368

367369
const logsQuery = useLogsList(workspaceId, logFilters, {
368-
refetchInterval: isLive ? 3000 : false,
370+
refetchInterval: isLive ? LIVE_REFRESH_INTERVAL_MS : false,
369371
})
370372

371373
const dashboardFilters = useMemo(
@@ -383,7 +385,7 @@ export default function Logs() {
383385
)
384386

385387
const dashboardStatsQuery = useDashboardStats(workspaceId, dashboardFilters, {
386-
refetchInterval: isLive ? 3000 : false,
388+
refetchInterval: isLive ? LIVE_REFRESH_INTERVAL_MS : false,
387389
})
388390

389391
const logs = useMemo(() => {

0 commit comments

Comments
 (0)