feat(knowledge): project document ACL and chunk changes asynchronously - #8199
Conversation
Document ACL/source changes and chunk writes mark their document in knowledge_projection_dirty (one upsert with a generation bump) in the writer's transaction. A knowledge projector converges every search projection per document in short pages bounded by chunk rows, several documents at once, and removes a mark only on the generation it read. Writers that declare sim.projection_mode = 'async' skip the synchronous embedding and document fan-out triggers. The processing commit and every connector-lease ACL page declare it while the knowledge-async-projection flag is on; every other writer, and every release before this one, keeps writing projection rows itself. Marks are written in both modes so a projector pass can never settle over a concurrent synchronous write. Migrations 0021-0023 keep their trigger body; 0024 alone installs the marking. Search decides a marked document's rows on the document itself, so a pending projection never admits a revoked grant. A document that moved sources joins the new source's per-source ranking after its pass. The projector runs as a Trigger.dev task, requested after writes and swept every minute while there is work, or in-process without Trigger.dev. A pass opens one worker per mark up to KB_CONFIG_PROJECTION_CONCURRENCY. The separate source/ACL backfill is folded into it behind knowledge-projection-fill.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
There was a problem hiding this comment.
All reported issues were addressed across 57 files
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
|
Restores 0016, 0019 and 0021 to their staging bodies; 0024 alone re-creates the projection triggers with the mode guard, in the same transaction that installs the marks. The fill starts only inside the pass budget and reports what it marked as remaining. Passes dispatch to Trigger.dev by the rule document processing uses, and the inline coalescer clears its running flag in the same step it reads the owed flag. Bumps the Helm chart for the projection cron job.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 54 files
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 54 files
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
Summary
knowledge_projection_dirtytable (one upsert, generation bump), and a new knowledge projector convergesembedding_search, the GIN keyword projection and the Tin projection per document, in short transactions of at most 250 chunk rows, several documents at once, removing a mark only on the generation it readsim.projection_mode = 'async', which the app does only while the newknowledge-async-projectionflag is on. Two places pick the mode, each reading the flag once before its transaction opens and requesting a pass once it commits:processDocumentAsyncandaclPageTransaction(every connector-lease ACL page runs through it). Everything else, and every release older than this one, keeps writing projection rows itself. Flag off is the rollout defaultknowledge-projection-fillflag, keeping at most 100 documents marked at once so fresh writes never queue behind much of it. Its cursor only moves past rows whose documents it marked, so a window with more unfilled documents than room is picked up where it stopped0378) and script migration0024.0016,0019and0021are unchanged, and0022/0023change only in comments, so every migration up to0023is fully synchronous and every write between them succeeds on its own.0024alone adds the mode guards and the marking, re-creating the projection triggers with the guard in the same short, lock-timeout-retried transaction that installs the marks, so a guard never exists without the marks.db:pushruns0024lastThroughput
KB_CONFIG_PROJECTION_CONCURRENCYdocuments at once (default 8), each worker on its own connection namedsim-knowledge-projector. A round opens only as many workers as there are marks (a bounded count), so a pass over a few documents holds a few connectionsKB_CONFIG_PROJECTION_CONCURRENCY(each step is one more database connection for the length of a pass). The backlog shows as rows inknowledge_projection_dirtyand their oldestmarked_atRollout
knowledge-async-projectionknowledge-projection-fillwhen there is I/O headroom to fill projection rows written before they carried a source and ACLRollback
knowledge-async-projectionoff is immediate: writers go back to writing projection rows themselves, and the projector keeps converging what is already markedknowledge-async-projectionoff and note the timeTT(the flag cache is about 30s; a processing attempt that read the flag just beforeTcan commit up to its 10-minute limit later)SELECT count(*) FROM knowledge_projection_dirty WHERE marked_at < T + interval '15 minutes'returns 0. A mark keeps the time it was first created while writes bump it, so every mark a deferred write touched is covered; marks created after that point come from synchronous writers whose rows are already writtenCleanup follow-up (after the flag has been on everywhere)
WHENguards andsync_embedding_search/sync_embedding_keyword_search/sync_embedding_keyword_tin/set_projection_source_acltriggers, and the fan-out insync_projection_source_acl(keeping only the mark)sim.projection_mode,DEFER_KNOWLEDGE_PROJECTION, theknowledge-async-projectionflag andKNOWLEDGE_ASYNC_PROJECTIONpagesByProjectionRows,lockProjectionPage,writeProjectionPages) to plain pages ofACL_CHANGE_BATCH_SIZEdocuments; the lease-last fence and deadlines stayType of Change
Testing
processDocumentAsyncwith the flag on through the real service: the commit writes the chunks, a content mark and no projection row, requests a pass, and the pass writes every projection row with the document's source and ACL and settles the mark0023no trigger carries the mode guard, no mark function exists, and even a writer that asks for the asynchronous mode fans out synchronously; after0024every guarded trigger carries the guard and changes mark their document0021's body calling the mark function, and a failed worker cutting the others offcheck:audits,check:migrations,check:api-validation,docs-manifest:check, unit suites, and the CI Postgres stepsChecklist