Skip to content

Commit e84615d

Browse files
committed
fix(search): serialize projection upgrades with active writers
1 parent 9ea663b commit e84615d

3 files changed

Lines changed: 35 additions & 4 deletions

File tree

apps/sim/lib/knowledge/__integration__/search-latency.integration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu
512512
if (capture && captured.length < 300) captured.push({ query, parameters: [...parameters] })
513513
}
514514
diagnosticLog = vi.spyOn(Logger.prototype, 'info')
515-
}, 60 * 60_000)
515+
}, 120 * 60_000)
516516

517517
afterAll(async () => {
518518
diagnosticLog?.mockRestore()

packages/db/script-migrations/0016_backfill_search_vectors.postgres.test.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { backfillEmbeddingSearch } from '@sim/db/script-migrations/0015_backfill
22
import { backfillSearchVectors } from '@sim/db/script-migrations/0016_backfill_search_vectors'
33
import { generateId } from '@sim/utils/id'
44
import postgres, { type Sql } from 'postgres'
5-
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
5+
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
66

77
const databaseUrl = process.env.KNOWLEDGE_ACL_TEST_DATABASE_URL
88
const schemaName = `search_projection_${generateId().replaceAll('-', '')}`
@@ -55,8 +55,37 @@ describe.runIf(Boolean(databaseUrl))('search projection upgrade in PostgreSQL',
5555
}
5656
})
5757

58-
it('upgrades multiple pages while preserving the old projection and every unsupported-model dimension', async () => {
59-
expect(await backfillSearchVectors(sql)).toBe(501)
58+
it('serializes behind existing writers before upgrading the projection in batches', async () => {
59+
const writer = postgres(databaseUrl!, {
60+
max: 1,
61+
connection: { search_path: `${schemaName},public` },
62+
onnotice: () => undefined,
63+
})
64+
const [{ pid }] = await sql`SELECT pg_backend_pid() AS pid`
65+
await writer`BEGIN`
66+
await writer`LOCK TABLE embedding IN ROW EXCLUSIVE MODE`
67+
const upgrade = Promise.allSettled([backfillSearchVectors(sql)])
68+
try {
69+
await vi.waitFor(async () => {
70+
const [{ waiting }] = await admin`SELECT EXISTS (
71+
SELECT 1 FROM pg_locks WHERE pid = ${pid}
72+
AND relation = ${`${schemaName}.embedding`}::regclass AND NOT granted
73+
) AS waiting`
74+
expect(waiting).toBe(true)
75+
})
76+
await writer`UPDATE embedding SET enabled = false WHERE id = 'chunk-1'`
77+
await writer`COMMIT`
78+
const [result] = await upgrade
79+
if (result.status === 'rejected') throw result.reason
80+
expect(result.value).toBe(501)
81+
} finally {
82+
await writer`ROLLBACK`
83+
await upgrade
84+
await writer.end()
85+
}
86+
expect((await sql`SELECT enabled FROM embedding_search WHERE id = 'chunk-1'`)[0].enabled).toBe(
87+
false
88+
)
6089
const rows = await sql.unsafe(`SELECT knowledge_base_id,
6190
vector_dims(coalesce(${fields.join(', ')})) AS dimensions,
6291
num_nonnulls(${fields.join(', ')}) AS populated,

packages/db/script-migrations/0016_backfill_search_vectors.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ function projections(prefix: string, shortened: string): string {
2323
export async function backfillSearchVectors(sql: Sql): Promise<number> {
2424
await sql.begin(async (tx) => {
2525
await tx.unsafe("SET LOCAL lock_timeout = '5s'")
26+
/** Acquire source and projection locks in the same order as embedding writers. */
27+
await tx.unsafe('LOCK TABLE embedding IN SHARE ROW EXCLUSIVE MODE')
2628
await tx.unsafe(
2729
`ALTER TABLE embedding_search ${columns.map((column) => `ALTER COLUMN ${column} SET STORAGE PLAIN`).join(', ')}`
2830
)

0 commit comments

Comments
 (0)