Skip to content

Commit 2a16f91

Browse files
committed
fix(knowledge): avoid count-query fan-out for unpaged lists
1 parent ba07b4e commit 2a16f91

2 files changed

Lines changed: 51 additions & 15 deletions

File tree

‎apps/sim/lib/knowledge/__integration__/knowledge-base-list.integration.ts‎

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { getWorkspaceKnowledgeBases } from '@/lib/knowledge/service'
1818
*/
1919
const ids = createKnowledgeAclFixtureIds()
2020
const foreign = createKnowledgeAclFixtureIds()
21+
const manyBases = createKnowledgeAclFixtureIds()
2122
const offPageId = generateId()
2223
const emptyId = generateId()
2324
const archivedId = generateId()
@@ -106,6 +107,16 @@ beforeAll(async () => {
106107
SELECT ${baseId} || '-' || n, ${baseId}, 'bulk.txt', 'https://fixture.invalid/bulk',
107108
1, 'text/plain', ARRAY['ws'], 1 FROM generate_series(1, 10000) AS n`)
108109
}
110+
await seedKnowledgeAclFixture(manyBases, { connectorType: 'google_drive' })
111+
await db.execute(sql`INSERT INTO knowledge_base (id, workspace_id, user_id, name)
112+
SELECT ${manyBases.knowledgeBaseId} || '-' || n, ${manyBases.workspaceId},
113+
${manyBases.aliceId}, 'Scale fixture ' || n FROM generate_series(1, 10000) AS n`)
114+
await db.execute(sql`INSERT INTO document
115+
(id, knowledge_base_id, filename, file_url, file_size, mime_type, acl, token_count)
116+
VALUES (${generateId()}, ${manyBases.knowledgeBaseId}, 'first.txt',
117+
'https://fixture.invalid/first', 1, 'text/plain', ARRAY['ws'], 13),
118+
(${generateId()}, ${`${manyBases.knowledgeBaseId}-10000`}, 'last.txt',
119+
'https://fixture.invalid/last', 1, 'text/plain', ARRAY['ws'], 17)`)
109120
await db.execute(sql`ANALYZE knowledge_base`)
110121
await db.execute(sql`ANALYZE document`)
111122
}, 60_000)
@@ -114,7 +125,7 @@ afterAll(async () => {
114125
const reportPath = process.env.KNOWLEDGE_BASE_LIST_REPORT_PATH
115126
if (reportPath) writeFileSync(reportPath, JSON.stringify(reports, null, 2))
116127
try {
117-
for (const fixture of [ids, foreign]) {
128+
for (const fixture of [ids, foreign, manyBases]) {
118129
await db.delete(workspace).where(eq(workspace.id, fixture.workspaceId))
119130
await db.delete(organization).where(eq(organization.id, fixture.organizationId))
120131
await db.delete(user).where(inArray(user.id, [fixture.aliceId, fixture.bobId]))
@@ -191,4 +202,34 @@ describe('knowledge base list counts on real Postgres', () => {
191202
})
192203
expect(archived.data.map(({ id }) => id)).toEqual([archivedId])
193204
})
205+
206+
it('counts a large unpaged workspace within a fixed database round-trip budget', async () => {
207+
let documentQueries = 0
208+
const previousDebug = db.$client.options.debug
209+
db.$client.options.debug = (_connection, query) => {
210+
if (query.includes('"document"')) documentQueries++
211+
}
212+
try {
213+
const all = await getWorkspaceKnowledgeBases(manyBases.workspaceId, 'active', {
214+
countsFor: access,
215+
})
216+
expect(all.data).toHaveLength(10001)
217+
expect(all.nextCursorKeys).toBeNull()
218+
expect(all.data.find((kb) => kb.id === manyBases.knowledgeBaseId)).toMatchObject({
219+
docCount: 1,
220+
tokenCount: 13,
221+
})
222+
expect(all.data.find((kb) => kb.id === `${manyBases.knowledgeBaseId}-10000`)).toMatchObject({
223+
docCount: 1,
224+
tokenCount: 17,
225+
})
226+
expect(all.data.reduce((total, kb) => total + kb.docCount, 0)).toBe(2)
227+
expect(all.data.reduce((total, kb) => total + kb.tokenCount, 0)).toBe(30)
228+
reports.push({ unpagedBases: all.data.length, documentQueries })
229+
expect(documentQueries).toBeGreaterThan(0)
230+
expect(documentQueries).toBeLessThan(10)
231+
} finally {
232+
db.$client.options.debug = previousDebug
233+
}
234+
})
194235
})

‎apps/sim/lib/knowledge/service.ts‎

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { db } from '@sim/db'
22
import { document, knowledgeBase, knowledgeConnector, workspaceFiles } from '@sim/db/schema'
33
import { createLogger } from '@sim/logger'
44
import { getPostgresConstraintName, getPostgresErrorCode } from '@sim/utils/errors'
5-
import { chunkArray } from '@sim/utils/helpers'
65
import { generateId } from '@sim/utils/id'
76
import { filterUndefined } from '@sim/utils/object'
87
import type { SQL } from 'drizzle-orm'
@@ -34,7 +33,7 @@ import { resourceScopeCondition } from '@/lib/core/resource-scope.server'
3433
import { generateRestoreName } from '@/lib/core/utils/restore-name'
3534
import { findActiveFolder, resolveRestoredFolderId } from '@/lib/folders/queries'
3635
import { isKnowledgeMemberAccessAvailable } from '@/lib/knowledge/access/availability'
37-
import { knowledgeAccessCondition } from '@/lib/knowledge/access/predicate'
36+
import { knowledgeAccessCondition, textArrayLiteral } from '@/lib/knowledge/access/predicate'
3837
import type { KnowledgeAccessProvider } from '@/lib/knowledge/access/types'
3938
import { mirrorsSourceAcls } from '@/lib/knowledge/connectors/access-modes'
4039
import {
@@ -53,7 +52,6 @@ import type {
5352
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
5453

5554
const logger = createLogger('KnowledgeBaseService')
56-
const KNOWLEDGE_BASE_COUNT_BATCH_SIZE = 100
5755

5856
/**
5957
* Every caller-fixable knowledge-base failure is an {@link OrchestrationError},
@@ -203,17 +201,14 @@ async function readCountedKnowledgeBaseRows(
203201
> {
204202
const scope = 'get' in access ? await access.get() : access
205203
const rows = await readKnowledgeBaseRows(where, orderBy, limit)
206-
const counts = new Map<string, { docCount: number; tokenCount: number }>()
207-
for (const batch of chunkArray(rows, KNOWLEDGE_BASE_COUNT_BATCH_SIZE)) {
208-
const totals = await countDocumentsByKnowledgeBase(
209-
inArray(
210-
document.knowledgeBaseId,
211-
batch.map((kb) => kb.id)
212-
),
213-
knowledgeAccessCondition(scope)
214-
)
215-
for (const total of totals) counts.set(total.knowledgeBaseId, total)
216-
}
204+
const totals =
205+
rows.length > 0
206+
? await countDocumentsByKnowledgeBase(
207+
sql`${document.knowledgeBaseId} = ANY(${textArrayLiteral(rows.map((kb) => kb.id))})`,
208+
knowledgeAccessCondition(scope)
209+
)
210+
: []
211+
const counts = new Map(totals.map((total) => [total.knowledgeBaseId, total]))
217212

218213
/**
219214
* The counts above already include everything the reader's stored ACL admits. Only a

0 commit comments

Comments
 (0)