improvement(knowledge): resolve connector and member state once per search - #8040
Conversation
|
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 21 files
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Fix all with cubic | Re-trigger cubic
|
3c2bb77 to
575cb52
Compare
…in its own index Two costs dominated organization search, and both came from deriving per candidate what is true of a whole source. Connector state — deletion, archival, a pending access rewrite, the organization's integration approval, the access mode — is a fact about a connector, so a search resolves it once and filters candidates by the resulting ids. For a member reading ~25k documents, the candidate predicate's connector lookup drops from one per document examined to one per document of a source that still needs this request's live proof: 24,571 evaluations to 610, and the access check from ~390ms to ~160ms. Ranking then follows sources. pgvector post-filters, so one walk over every source spends its scan budget on the sources a caller cannot read: measured recall for a member reading half an index ranged from 0.00 to 1.00, averaging 0.80 over nine queries, two of which returned none of the exact page. A member of a source reads essentially all of it, so that source is walked through an index covering it alone, built after a sync grows it past the threshold and dropped with the connector. Every other source is sliced — mirrored permissions give a caller their own mail, their own files — and those slices are ranked exactly in one statement. Recall over the same queries rises to 0.95 with none below 0.75. Retrieval never waits on an index existing: a source without one is ranked exactly, so a build that is skipped, fails, or has not happened yet costs recall nothing.
…he rest of the plan A members-mode document is readable while one of the caller's active members observes it, freshly, and which members those are is a fact about the caller. Resolving them with the connectors — one query, one plan — turns each candidate's check into a lookup on the observation key instead of a join to the member behind it, and lets the vector planner read the sources the caller belongs to from the same resolution rather than asking again.
- ask a live-proof source for the caller's grants once, before either leg, and only when the scope actually reads one, instead of per candidate page - drop the per-page source exclusion and refill loop that followed from the per-page proof - read search result metadata under the stored predicate the rows already passed - end candidate paging on a short page whether or not the leg reorders its results - batch the embedding_search.connector_id backfill in independently committed keyset pages, matching the other projection backfills
575cb52 to
edf9679
Compare
… and registry expectations
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
… hydration - fall back to a graph walk when the sliced sources hold more readable documents than one exact ranking may enumerate, since that enumeration has no order and would otherwise rank an arbitrary subset - read content under the full predicate, which re-reads each connector's own lifecycle and approval, so a source deleted, archived or unapproved mid-search stops answering at the gate that returns content
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
… its candidates is read Resolving every gated source's grants up front charged a search for sources it never ranked. The grants are now resolved on first need — when a page of ranked candidates actually contains one of those sources — and memoized for the rest of the search, so a scope that ranks none never asks and one that ranks many asks once.
|
@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 27 files
You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
…ource fan-out, serialize a source's index build - an observation vouches for a document only from a member of the document's own connector: a document that changed hands keeps its old observations, which no longer carry it as a candidate - a source whose search runs out of budget marks the leg partial while the other sources' results stand - a source's index is built under a session lock on the one reserved connection that also builds it, so two syncs cannot interleave the invalidity check and the drop; 0021 drops an invalid leftover before building, and analyzes both projections after the backfill - a broad reader's walk widens when it found fewer candidates than the smallest pool worth reranking
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
… has left The rerank and hydration of whatever is found — the first walk's candidates at least — keep the rest, so a wider walk that runs out of its share can no longer take the leg's results with it.
|
@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 27 files
You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Fix all with cubic | Re-trigger cubic
…rrow reader's keyword window stepwise, apply scan settings with the deadline - an on-row walk keeps walking, up to a 100k-tuple cap, until its limit is met; the separate wider walk and its diagnostic are gone - the on-row predicate tests the mirrored ACL alone — a member's source is no longer admitted whole, so a document re-owned after its chunk was mirrored is refused at the row - a reach count that ran out of time decides that search only; it is not remembered - a narrow reader ranks the narrowest keyword window first and widens to the wide one only when the page is short; resolved scopes leave the widest window short instead of ranking every match - the HNSW scan settings ride in the deadline statement, one round trip fewer per vector statement
|
@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.
No issues found across 28 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
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.
No issues found across 29 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
Summary
embedding_search,embedding_keyword_tin), set by each projection's own trigger under a share lock on the document and fanned out when a document's source or ACL changes; script migration 0021 installs the triggers, backfills both projections in keyset pages, and builds a partial GIN on eachacland a partial btree on the vector projection's source concurrentlydocumentper visited or ranked chunk. Content is still read under the full predicate at hydration, and result metadata under the scope the results were read underembedding_search.connector_id), maintained by triggers, with script migration 0021 backfilling in independently committed keyset pages under document share locksBehaviour
The on-row predicate admits a superset of the per-row document predicate — a member's source whole, and mirrored ACLs within eligible sources — and never refuses what that predicate admits (asserted against a database); whatever it admits beyond that is refused at hydration. The resolved connector predicate admits exactly the documents the per-row predicate admits. Hydration re-reads each connector's lifecycle and approval per row, so a source deleted, archived or unapproved while a search runs stops answering at the gate that returns content. A live-proof source is still proven before its content is read — once per search rather than once per page. Uploads are ranked even when every connector source is walked. v1 fails a partial retrieval as before.
Type of Change
Measurements
On a production-shaped copy, server time p50 by query class (common / medium / rare) and recall@20 against exact ranking, for a reader who reaches 63% of the index and one who reaches 3%:
The narrow reader's after column sums the vector leg's statements (four source walks, the sliced sources' exact ranking, the rerank); those run concurrently on separate connections, so the leg's own critical path is about 95 ms, and its keyword leg is 32–48 ms on terms the reader can read anywhere (213 ms only where they can read a term nowhere and both windows run). A broad reader whose query lands in a neighbourhood they mostly cannot read completes at 218–244 ms warm; cold, that walk is bound by a heap fetch per visited tuple, which only an index that carries the ACL would remove.
The decisive experiment behind the design: the same graph walk, same scan budget, same qualifying neighbours cost 6,726 ms with the permission test joined to
documentper visited tuple and 64 ms with it on the row.Testing
Tested manually. Unit suites for knowledge and the v1/internal knowledge routes pass (3,265 tests); a PostgreSQL test asserts the per-query and per-row predicates admit the same documents over admin, members, workspace and upload rows; the real-database knowledge integration suites (ACL end-to-end, KB-block fan-out, connector permissions) pass. New tests cover the reach-share decision and its caching, the broad-walk fallback and its budget, the lazy grant resolution (never asked without a gated candidate; asked once with one), exclusion and refill after a denied source, the upload slice, the saturated-slice walk, source index builds and DDL identifier guards, and the v1 partial-retrieval failure — each verified to fail without its change. The 0021 triggers and backfill were exercised against a database: insert fills the source, a document changing hands fans out to enabled chunks, a re-enabled chunk re-reads its source, the share-locked backfill fills a cleared row and reruns as a no-op.
bun run lint,bun run type-check,bun run check:auditsandbun run check:migrations origin/stagingpass.Checklist