Skip to content

Commit 9eb49b1

Browse files
committed
perf(db): optimize recurring query paths
1 parent f386769 commit 9eb49b1

20 files changed

Lines changed: 21417 additions & 210 deletions

File tree

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { workflowExecutionLogs } from '@sim/db/schema'
5+
import {
6+
authMockFns,
7+
createMockRequest,
8+
dbChainMockFns,
9+
queueTableRows,
10+
resetDbChainMock,
11+
} from '@sim/testing'
12+
import { beforeEach, describe, expect, it, vi } from 'vitest'
13+
14+
const {
15+
mockCheckWorkspaceAccess,
16+
mockExpandFolderIdsWithDescendants,
17+
mockMapWithConcurrency,
18+
mockMaterializeExecutionDataForDisplay,
19+
} = vi.hoisted(() => ({
20+
mockCheckWorkspaceAccess: vi.fn(),
21+
mockExpandFolderIdsWithDescendants: vi.fn(),
22+
mockMapWithConcurrency: vi.fn(),
23+
mockMaterializeExecutionDataForDisplay: vi.fn(),
24+
}))
25+
26+
vi.mock('@/lib/workspaces/permissions/utils', () => ({
27+
checkWorkspaceAccess: mockCheckWorkspaceAccess,
28+
}))
29+
30+
vi.mock('@/lib/logs/folder-expansion', () => ({
31+
expandFolderIdsWithDescendants: mockExpandFolderIdsWithDescendants,
32+
}))
33+
34+
vi.mock('@/lib/logs/execution/trace-store', () => ({
35+
materializeExecutionDataForDisplay: mockMaterializeExecutionDataForDisplay,
36+
}))
37+
38+
vi.mock('@/lib/core/utils/concurrency', () => ({
39+
MATERIALIZE_CONCURRENCY: 20,
40+
mapWithConcurrency: mockMapWithConcurrency,
41+
}))
42+
43+
import { GET } from '@/app/api/logs/export/route'
44+
45+
const mockGetSession = authMockFns.mockGetSession
46+
const STARTED_AT = new Date('2026-08-23T12:00:00.000Z')
47+
48+
function makeRequest() {
49+
return createMockRequest(
50+
'GET',
51+
undefined,
52+
{},
53+
'http://localhost:3000/api/logs/export?workspaceId=workspace-1'
54+
)
55+
}
56+
57+
function logRow(index: number, overrides: Record<string, unknown> = {}) {
58+
const startedAt = new Date(STARTED_AT.getTime() - index * 1000)
59+
return {
60+
id: `log-${index.toString().padStart(4, '0')}`,
61+
workflowId: 'workflow-1',
62+
executionId: `execution-${index}`,
63+
level: 'info',
64+
trigger: 'manual',
65+
startedAt,
66+
startedAtCursor: startedAt.toISOString(),
67+
endedAt: new Date(STARTED_AT.getTime() - index * 1000 + 500),
68+
totalDurationMs: 500,
69+
costTotal: '0.01',
70+
executionData: { message: `message-${index}` },
71+
workflowName: 'Workflow',
72+
...overrides,
73+
}
74+
}
75+
76+
function flattenConditions(condition: unknown): Array<Record<string, unknown>> {
77+
if (!condition || typeof condition !== 'object') return []
78+
const node = condition as Record<string, unknown>
79+
if (Array.isArray(node.conditions)) {
80+
return node.conditions.flatMap(flattenConditions)
81+
}
82+
return [node]
83+
}
84+
85+
describe('GET /api/logs/export', () => {
86+
beforeEach(() => {
87+
vi.clearAllMocks()
88+
resetDbChainMock()
89+
mockGetSession.mockResolvedValue({ user: { id: 'user-1' } })
90+
mockCheckWorkspaceAccess.mockResolvedValue({ hasAccess: true })
91+
mockExpandFolderIdsWithDescendants.mockImplementation(
92+
async (_workspaceId: string, folderIds: string | undefined) => folderIds
93+
)
94+
mockMaterializeExecutionDataForDisplay.mockImplementation(
95+
async (executionData: Record<string, unknown> | null | undefined) => executionData ?? {}
96+
)
97+
mockMapWithConcurrency.mockImplementation(
98+
async (
99+
items: unknown[],
100+
_limit: number,
101+
mapper: (item: unknown, index: number) => Promise<unknown>
102+
) => Promise.all(items.map(mapper))
103+
)
104+
})
105+
106+
it('materializes one bounded page at a time while preserving CSV row order', async () => {
107+
queueTableRows(workflowExecutionLogs, [logRow(0)])
108+
queueTableRows(workflowExecutionLogs, [logRow(1)])
109+
queueTableRows(workflowExecutionLogs, [logRow(2)])
110+
111+
const response = await GET(makeRequest())
112+
const lines = (await response.text()).trimEnd().split('\n')
113+
114+
expect(response.status).toBe(200)
115+
expect(mockMapWithConcurrency.mock.calls.map(([items]) => items.length)).toEqual([1, 1, 1])
116+
expect(lines).toHaveLength(4)
117+
expect(lines[1]).toContain('execution-0')
118+
expect(lines.at(-1)).toContain('execution-2')
119+
})
120+
121+
it('resumes full pages by startedAt and id without using OFFSET', async () => {
122+
const last = logRow(0, { startedAtCursor: '2026-08-23 12:00:00.000123' })
123+
const secondPage = [
124+
logRow(1, {
125+
id: 'log-0000-second',
126+
startedAt: last.startedAt,
127+
startedAtCursor: '2026-08-23 12:00:00.000122',
128+
}),
129+
]
130+
queueTableRows(workflowExecutionLogs, [last])
131+
queueTableRows(workflowExecutionLogs, secondPage)
132+
133+
const response = await GET(makeRequest())
134+
const lines = (await response.text()).trimEnd().split('\n')
135+
136+
expect(lines).toHaveLength(3)
137+
expect(dbChainMockFns.offset).not.toHaveBeenCalled()
138+
expect(dbChainMockFns.where).toHaveBeenCalledTimes(3)
139+
expect(dbChainMockFns.orderBy).toHaveBeenNthCalledWith(
140+
1,
141+
expect.objectContaining({
142+
type: 'desc',
143+
column: workflowExecutionLogs.startedAt,
144+
}),
145+
expect.objectContaining({
146+
type: 'desc',
147+
column: workflowExecutionLogs.id,
148+
})
149+
)
150+
151+
const cursorConditions = flattenConditions(dbChainMockFns.where.mock.calls[1][0])
152+
const timestampConditions = cursorConditions.filter(
153+
(condition) => condition.left === workflowExecutionLogs.startedAt
154+
)
155+
expect(timestampConditions.map((condition) => condition.type)).toEqual(['lt', 'eq'])
156+
for (const condition of timestampConditions) {
157+
expect(condition.right).not.toBeInstanceOf(Date)
158+
expect(condition.right).toEqual(
159+
expect.objectContaining({ values: expect.arrayContaining([last.startedAtCursor]) })
160+
)
161+
}
162+
expect(cursorConditions).toContainEqual(
163+
expect.objectContaining({
164+
type: 'lt',
165+
left: workflowExecutionLogs.id,
166+
right: last.id,
167+
})
168+
)
169+
})
170+
171+
it('does not load the next database page until the current row is consumed', async () => {
172+
queueTableRows(workflowExecutionLogs, [logRow(0)])
173+
queueTableRows(workflowExecutionLogs, [logRow(1)])
174+
175+
const response = await GET(makeRequest())
176+
const reader = response.body!.getReader()
177+
178+
await reader.read()
179+
expect(dbChainMockFns.where).not.toHaveBeenCalled()
180+
181+
await reader.read()
182+
expect(dbChainMockFns.where).toHaveBeenCalledTimes(1)
183+
184+
await reader.cancel()
185+
expect(dbChainMockFns.where).toHaveBeenCalledTimes(1)
186+
})
187+
})

0 commit comments

Comments
 (0)