Skip to content

Commit 8ee7f85

Browse files
committed
fix(knowledge): estimate a filter's size under a short deadline of its own
1 parent 1b59f04 commit 8ee7f85

2 files changed

Lines changed: 50 additions & 5 deletions

File tree

‎apps/sim/lib/knowledge/search/queries.test.ts‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2199,6 +2199,38 @@ describe('filters on a resolved scope', () => {
21992199
expect(statements().filter((query) => isExactRanking(query.sql))).toHaveLength(1)
22002200
})
22012201

2202+
it("estimates a filter under the leg's deadline and walks when the estimate runs out of time", async () => {
2203+
dbChainMockFns.execute.mockImplementation(async (query) => {
2204+
const statement = render(query).sql
2205+
if (statement.includes('EXPLAIN') && JSON.stringify(render(query)).includes('"type":"gte"'))
2206+
throw Object.assign(new Error('canceling statement due to statement timeout'), {
2207+
code: '57014',
2208+
})
2209+
if (statement.includes('EXPLAIN'))
2210+
return [{ 'QUERY PLAN': [{ Plan: { 'Plan Rows': 1_000_000 } }] }]
2211+
if (statement.includes(') reached')) return [{ n: 250_000 }]
2212+
if (isWalk(statement)) return traversedRows
2213+
if (statement.includes('WITH scored_search_candidates')) return rerankRows
2214+
return []
2215+
})
2216+
const result = await retrieveKnowledgeSearch({
2217+
...params,
2218+
accessProvider: provider,
2219+
searchMode: 'vector',
2220+
query: 'release',
2221+
filters: { modifiedAfter: '2026-09-13T00:00:00.000Z' },
2222+
})
2223+
/** The estimate ran inside a deadline statement; its own timeout chose the walk and cost the leg nothing. */
2224+
const estimateAt = statements().findIndex((query) =>
2225+
JSON.stringify(query).includes('"type":"gte"')
2226+
)
2227+
expect(estimateAt).toBeGreaterThan(0)
2228+
expect(statements()[estimateAt - 1].sql).toContain('statement_timeout')
2229+
expect(statements().filter((query) => query.sql.includes('AS saturated'))).toHaveLength(0)
2230+
expect(statements().filter((query) => isWalk(query.sql))).toHaveLength(1)
2231+
expect(result.retrieval.status).toBe('complete')
2232+
})
2233+
22022234
it('enumerates a date filter only while the planner estimates its documents few', async () => {
22032235
let estimated = 50_000
22042236
dbChainMockFns.execute.mockImplementation(async (query) => {

‎apps/sim/lib/knowledge/search/queries.ts‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,9 +1160,11 @@ const indexDocumentCounts = new LRUCache<string, number>({
11601160
async function estimateFilteredDocuments(
11611161
knowledgeBaseIds: string[],
11621162
filters: WorkspaceSearchFilters,
1163-
plan: SearchAccessPlan
1163+
plan: SearchAccessPlan,
1164+
budget: SearchBudget | undefined
11641165
): Promise<number> {
1165-
const [row] = await db.execute<{ 'QUERY PLAN': Array<{ Plan: { 'Plan Rows': number } }> }>(sql`
1166+
const [row] = await runSearchQuery(budget, 'permitted_documents', (executor) =>
1167+
executor.execute<{ 'QUERY PLAN': Array<{ Plan: { 'Plan Rows': number } }> }>(sql`
11661168
EXPLAIN (FORMAT JSON) SELECT 1 FROM ${document}
11671169
WHERE ${and(
11681170
inArray(document.knowledgeBaseId, knowledgeBaseIds),
@@ -1172,6 +1174,7 @@ async function estimateFilteredDocuments(
11721174
: undefined,
11731175
filters.source ? planSourceCondition(plan) : undefined
11741176
)}`)
1177+
)
11751178
return Number(row?.['QUERY PLAN']?.[0]?.Plan?.['Plan Rows'] ?? 0)
11761179
}
11771180

@@ -2421,11 +2424,21 @@ export async function retrieveKnowledgeSearch(
24212424
* source confined on the row, the date tested through the document — since a set that large
24222425
* holds most of the query's neighbours anyway. The planner's estimate decides which.
24232426
*/
2427+
/** Planning only, so a short cap of its own: running past it answers as the wide window it may be. */
2428+
const estimateBudget = budgets.vector.capped(VECTOR_PROBE_BUDGET_MS)
24242429
const enumerateFiltered =
24252430
accessPlan && (params.filters?.modifiedAfter || params.filters?.source)
2426-
? (await measureSearchStage('permitted_documents', () =>
2427-
estimateFilteredDocuments(knowledgeBaseIds, params.filters!, accessPlan)
2428-
)) <= VECTOR_PROBE_DOCUMENT_LIMIT
2431+
? await estimateFilteredDocuments(
2432+
knowledgeBaseIds,
2433+
params.filters,
2434+
accessPlan,
2435+
estimateBudget
2436+
)
2437+
.then((estimate) => estimate <= VECTOR_PROBE_DOCUMENT_LIMIT)
2438+
.catch((error) => {
2439+
if (!estimateBudget.isTimeout(error)) throw error
2440+
return false
2441+
})
24292442
: false
24302443
const permitted =
24312444
access.kind === 'user' && params.accessProvider && !params.filters?.documentIds?.length

0 commit comments

Comments
 (0)