Enterprise RAG Knowledge Assistant — Trade-offs and Decisions
RAG architecture trade-offs for an enterprise assistant: 14 decisions across hybrid versus vector-only search, chunking, permission pushdown versus post-filtering, tenant index partitioning, reranking and cache keys. Each states the options, selection criteria and when another choice wins, for a 3,000-employee tenant with 750,000 chunks on a ~500-tenant platform.
Retrieval Method: BM25 vs. Dense Vectors vs. Hybrid
Employees ask in their own words ("do we still reconcile payments by hand at month-end?") about text that says "automated nightly run with an exception queue", and they also ask about RUN-8, which must never be answered from RUN-5. Both kinds of miss produce wrong answers, and retrieval has ~80 ms of the budget.
| Option | Benefits | Limits |
|---|---|---|
| BM25 only | Exact terms and identifiers; cheap; interpretable scores; no embedding pipeline | Misses paraphrase: "by hand" and "month-end" share no terms with the policy text that answers the question |
| Dense vectors only | Matches meaning without shared words | Short identifiers blur (RUN-8 and RUN-5 embed close together); every model upgrade means a corpus re-embedding |
| Hybrid: BM25 ∥ ANN, reciprocal rank fusion (RRF) | Covers both failure modes; RRF uses ranks only, so the two scorers never need a common scale | Two searches per query (run in parallel), two index structures to keep consistent, a fusion constant to justify |
Selection criteria. Recall@k on paraphrase and identifier questions separately; robustness when one scorer changes; latency; the cost of keeping two representations consistent.
When another option wins. BM25 only for identifier-lookup tools (ticket or part-number search) where users type the document's own vocabulary. Dense only for narrative corpora without codes, or for cross-lingual search, where queries and documents rarely share terms. Learned sparse retrieval (e.g. SPLADE) when one index matters more than an extra ingestion-time model; weighted score blending only with stable, normalised scorers.
Remaining risk. A standard tokenizer splits RUN-8 into run and 8, silently erasing the identifier advantage unless the analyzer preserves identifiers (Architecture Deep Dive, Component 04). RRF ignores score magnitude, so a clear single-list winner can lose to consensus candidates; the reranker corrects most such cases.
Chunking Strategy: Fixed-Size vs. Structure-Aware vs. Semantic
Chunks are the unit of retrieval, citation and permission tagging, and only four fit the context (~1,600 tokens). Sources differ in shape — headed wiki policies, drive documents with tables, chat threads without headings — and a boundary through the sentence that states a rule makes both halves useless.
| Option | Benefits | Limits |
|---|---|---|
| Fixed-size token windows | Predictable chunk count (750,000 at 400/100); trivial for every format | Cuts sentences, table rows and heading-to-rule links |
| Structure-aware (heading sections, capped windows) | Chunks match policy sections; better embeddings, reranking and citations | A layout-aware parser per format; uneven chunk sizes (~300-400 tokens) |
| Semantic (topic-shift detection) | Fits long unstructured text such as transcripts | An embedding pass per sentence at ingestion; boundaries are hard to predict or debug |
| Parent-child (match small, return the parent section) | Precise matching with wide context for the generator | Two granularities to index; a 1,200-token parent uses three chunks' worth of budget |
Selection criteria. Recall@k per source type; predictable sizes for capacity and token budgets; ingestion cost; whether a citation points at a meaningful passage.
When another option wins. Fixed-size windows for a deadline-bound bulk import, with a planned re-chunk afterwards. Parent-child when answers need whole sections (contracts, procedures) and the context budget can afford it. Semantic chunking for long transcripts with no structure at all.
Remaining risk. Any chunking change is corpus-wide: it re-chunks and re-embeds all 750,000 chunks and must be published as a staged index version (Decision 14). Boundary regressions show up only as recall@k drops per source type (Architecture Deep Dive, Component 10), not as errors.
Sync Trigger: Webhooks vs. Polling vs. Reconciliation vs. Full Recrawl
Index lag must stay within 5 minutes p95 for small edits and 15 minutes for large documents; document-ACL removals must apply within 2 minutes. Source APIs are rate-limited, webhooks are best-effort, and a deletion that produces no event must still be found.
| Option | Benefits | Limits |
|---|---|---|
| Webhooks (a trigger that causes a fetch) | Seconds of detection latency; no wasted calls | Dropped, duplicated or reordered; not offered by every source; carry neither content nor permissions |
| Cursor-based polling | Works with any change API; catches missed webhooks | Detection latency equals the interval; a shorter interval spends rate limit |
| Nightly reconciliation sweep (list ids, versions and ACL hashes; compare with the documents table) | Finds anything both triggers missed, including silent deletions; fetches only what differs | Detection up to 24 h; a listing pass over 150,000 documents |
| Full recrawl (re-fetch all content) | Rebuilds state with no dependency on change APIs | Re-fetches 150,000 documents to find a few hundred changes |
Selection criteria. Lag and revocation budgets; each source's delivery guarantees; API quotas; whether deletions and ACL edits are observable.
When another option wins. Polling alone for sources without webhooks or with low churn. A full recrawl as the primary mechanism for small sources with no change API at all. Dropping the sweep never pays: it is cheap and is the only layer that finds silent deletions.
Remaining risk. A source whose change feed omits permission edits can surface an ACL removal only through the sweep, which is outside the 2-minute budget; such connectors need a dedicated permissions poll or an explicit exception. Polls compete with user traffic for the source's rate limit during bulk edits.
Authorization Freshness: Live Checks vs. Cached Principals vs. Cache with Invalidation
Group membership lives in the identity provider (IdP) and is resolved per query. A group removal must take effect within 60 s, principal resolution has ~15 ms of the budget, and an IdP outage must neither leak content nor take down a 99.9%-available query path.
| Option | Benefits | Limits |
|---|---|---|
| Live IdP lookup on every query | Smallest staleness window | IdP latency on every query at 150-200 qps platform peak; an IdP outage becomes a query outage |
| Principal-set cache, TTL ≤ 60 s | ~15 ms; absorbs IdP blips | Membership up to 60 s stale |
| Cache plus push invalidation from IdP membership events | Typical window of seconds; the TTL still bounds the worst case | Event plumbing per IdP; lost events fall back to the TTL |
Selection criteria. The revocation budget; added latency; coupling of query availability to the IdP; behaviour when the IdP fails, which must be closed.
When another option wins. A live check per query for a regulated tenant whose contract requires removal within seconds — applied to that tenant only, accepting its latency and availability cost. A longer TTL only if the product explicitly relaxes the revocation budget; the TTL follows the budget, never the reverse.
Remaining risk. Up to 60 s of exposure after a group removal when no event arrives. During an IdP outage users lose group-granted content, which shows up as a quality drop rather than a leak. The revocation-lag canary in Architecture Deep Dive, Component 10 verifies the bound continuously.
Index Partitioning: Per-Tenant vs. Shared Indexes
About 500 tenants of very different sizes share the platform; the largest has 750,000 chunks (~5 GB of vectors). Cross-tenant reads must be impossible, and one tenant's spike must not degrade others.
| Option | Benefits | Limits |
|---|---|---|
Shared index with a tenant_id filter | One index to run; cheap for tiny tenants | One forgotten predicate in a feature, admin tool or script is a cross-tenant leak; a small tenant is a tiny filtered fraction of a huge HNSW graph, the worst case for filtered recall |
| Per-tenant partitions (index or namespace) | Structural isolation; graphs capped at the tenant's size; per-tenant migration, purge and deletion | Many partitions to operate; fixed overhead per small tenant |
| Per-tenant partitions plus dedicated capacity for the largest tenants | Also isolates compute for the tenants most likely to spike | Reserved capacity costs money when idle |
Selection criteria. Isolation enforced by structure rather than convention; blast radius of a bug; per-tenant operations; compute isolation; overhead for the long tail of small tenants.
When another option wins. Only the granularity changes, never logical isolation: thousands of tiny tenants fit as namespaces on shared clusters, provided the engine enforces the namespace rather than a query predicate. A single-tenant deployment makes the question moot. A shared filterable index is not an acceptable option for mutually distrusting tenants.
Remaining risk. Partitions do not stop contention for shared rerankers or LLM quota; every shared resource needs its own per-tenant limit. Isolation also depends on deriving the tenant only from the authenticated token, never from a request parameter.
Search Infrastructure: One Engine or Two, Managed or Self-Hosted
Per tenant partition, retrieval needs BM25 with custom analyzers, permission-filtered ANN that keeps recall under selective filters, cheap acl_principals/status tag updates and an atomic alias switch — with BM25 and vectors never disagreeing about which chunks exist.
| Option | Benefits | Limits |
|---|---|---|
| One engine storing text, vector and tags on the same record | A chunk is written once; no cross-store drift; one filter language | Its ANN, analyzers and filter planner must all be good enough |
| Separate vector database and lexical engine | Best-of-breed vector features | Two stores need a shared commit point and a reconciliation job; a chunk deleted from one but returned by the other is a classic bug |
| Managed service | The vendor runs upgrades, sharding and replicas; faster to production | Less control over HNSW parameters and analyzers; pricing grows with storage and queries across ~500 tenants |
| Self-hosted engine | Full control of analyzers, ef, exact-scan fallback and cost at scale | The team owns capacity planning, upgrades and on-call for a distributed search system |
Selection criteria. A checklist: filtered ANN with an exact-scan fallback; tag-only updates; identifier-preserving analyzers; atomic aliases; per-tenant namespaces; then operating capacity and cost.
When another option wins. Self-hosting when managed pricing across the platform exceeds the cost of an infrastructure team, or when a checklist item (a custom analyzer, filter-planner control) is not exposed. Two stores when a required vector capability exists only in a dedicated vector database — accepting the commit point and reconciliation job that come with it.
Remaining risk. In segment-based engines a tag update re-indexes the whole record, vector included, so a bulk ACL change is real write load (Architecture Deep Dive, Component 06). Moving between engines later is a corpus-wide migration published like Decision 14.
Reranking: Always vs. Fusion-Only vs. Conditional
Fusion yields ~40 candidates, but only four fit in the context. First-pass scorers judge the question and each chunk separately, so a glossary entry that mentions reconciliation can rank next to the passage describing the change. A cross-encoder costs ~120 ms per query.
| Option | Benefits | Limits |
|---|---|---|
| Always rerank (cross-encoder over ~40 pairs) | Highest precision in the four context slots | ~120 ms and one model inference per pair on every query |
| Fusion order only | Saves ~120 ms and a model dependency | Vocabulary overlap can beat the chunk that actually answers |
| Conditional (skip when one candidate clearly dominates) | Pays only when reranking is likely to change the top four | Needs a calibrated threshold; a wrong threshold regresses precision silently |
Selection criteria. The measured gap between fusion order and reranked order on the evaluation set (nDCG and context precision per question type, plus groundedness of the resulting answers); the latency budget; reranker cost relative to generation, which dominates per-query cost.
When another option wins. Conditional reranking for very high-volume tenants once reranker spend becomes material and the threshold is validated on that tenant's queries. Fusion-only as the normal mode when the evaluation gap is negligible (a narrow identifier-lookup corpus). An LLM as reranker only where its flexibility is worth a slower, costlier judgement per pair.
Remaining risk. A reranker version change can bury answers without any error; it is gated on the evaluation set like any model change (Architecture Deep Dive, Component 10). Degraded mode is lower precision by design, so its duration is an alerting metric.
Embeddings: Precompute, and How to Migrate Models
Every one of 750,000 chunks has a 1,536-dimension vector; steady churn is only 6,000 embeddings a day, but a model upgrade changes every vector, and vectors from different models are not comparable. Embedding the corpus at query time is not an option at this size, so the real choice is the migration strategy.
| Option | Benefits | Limits |
|---|---|---|
| In-place re-embedding of the live index | No extra storage | Old and new vector spaces mix inside one graph mid-migration; nearest neighbours are silently wrong. Excluded |
| Shadow namespace: dual-write, backfill, parity check, atomic alias flip, bake period | Queries see one consistent model; instant rollback | Double storage (~5 GB per large tenant) and a backfill of ~300 million tokens |
| Pin the current model | No migration cost | Forced migration later when the provider retires the model |
Selection criteria. Correctness (one vector space per search); rollback speed; temporary storage and embedding cost; how long the migration lasts while 1,200 revisions a day keep arriving.
When another option wins. Pinning the model is right until evaluation shows a meaningful gain or the provider announces retirement. Querying both models and fusing during the transition suits a cautious rollout, at two embeddings and two ANN searches per query until the cutover.
Remaining risk. Events that arrive during the backfill must reach both namespaces through the same version-fenced writer, or the new namespace is born stale. The query embedder and the index alias must switch together; a mismatch is the in-place failure again.
Cache Keys vs. Leakage
Two users in the same tenant asking the identical question may be entitled to different answers. Any cache placed after authorization must therefore reuse a value only for the same entitlement and corpus state, within the 60 s revocation budget. Per-identity answer caches tend to have low hit rates.
| Option | Benefits | Limits |
|---|---|---|
| Answer keyed by query text | Maximum hit rate | Serves a payments engineer's answer to a sales user — a security defect, not a trade-off. Excluded |
| Query-embedding cache only | Shared safely: depends only on text and model | No saving on retrieval or generation |
| Retrieval and answer caches keyed by tenant, query, principal fingerprint and corpus state; TTL ≤ 60 s | Reuse only between identical entitlements; per-document version checks on a hit let entries survive unrelated edits | Low hit rate; a tenant-wide corpus counter would invalidate about every 30 s at this churn; a ~20 ms validation per hit |
Selection criteria. Leakage must be impossible by construction; then hit rate, staleness bounds and the cost of validating a hit.
When another option wins. Skip answer caching entirely when principal sets are so varied that fingerprints rarely repeat — the embedding cache then captures most of the saving. Hit rates rise naturally where entitlements are uniform (most users share one fingerprint), and the same keys stay safe.
Remaining risk. The fingerprint must cover every input to authorization; adding a new entitlement dimension (say, a clearance attribute) without adding it to the key reintroduces the leak. A cached result cannot see a newly published document that would now outrank its evidence; the TTL bounds that.
Long-Context Stuffing vs. Retrieval
The corpus is ~225 million tokens (150,000 documents × 1,500 tokens), and each query is budgeted at ~2,200 input tokens, LLM tokens being the dominant cost. Every passage given to the model must also be one the user may read.
| Option | Benefits | Limits |
|---|---|---|
| Put the whole corpus in context | Nothing can be missed | Orders of magnitude beyond any context window. Excluded |
| Coarse selection, long context (e.g. top 20 whole documents, ~30,000 tokens) | Fewer misses from chunk boundaries | ~14× the input tokens and a slower first token; models use information in the middle of long contexts less reliably; per-document ACLs still required |
| Precise retrieval (four reranked, authorized chunks, ~1,600 tokens) | Cost and latency roughly constant as the corpus grows; citations point at passages | Answer quality depends on recall@k: the right chunk must reach the top four |
Selection criteria. Token cost at 4,400 queries a day per tenant; time to first token; groundedness; citation granularity.
When another option wins. Long context wins for single-document "chat with this file" features and for small corpora of a few dozen documents with uniform access, where the retrieval machinery costs more than it saves.
Remaining risk. A question whose answer spans more than four chunks gets a partial answer; the support check and abstention keep it honest, and recall@k on multi-chunk questions is tracked separately (Architecture Deep Dive, Component 10).
Generator Model Size: Small and Fast vs. Large and Capable
Generation handles ~2,200 input and 350 output tokens per query with ~1,000 concurrent generations at platform peak. Retrieved text is untrusted, so the model must treat it as data, not instructions, and groundedness must stay at or above 95%.
| Option | Benefits | Limits |
|---|---|---|
| Large generator | Better multi-passage synthesis; more consistent instruction-following against injected text | Highest per-token cost and latency; scarcer quota at peak |
| Small or distilled generator | Lower cost and latency; more concurrency per quota | Weaker instruction-following raises injection risk; subtle mis-synthesis is harder to detect than an outright error |
| Routing by question complexity | Spends large-model tokens only where needed | A router to build and evaluate; misrouted hard questions degrade silently |
Selection criteria. Measured groundedness and citation precision; an injection test suite; cost per query; time to first token under peak load.
When another option wins. A smaller generator for a tenant once its groundedness, citation precision and injection-suite results on that tenant's evaluation set meet the bar — a measured, per-deployment decision. Routing pays off at very high volume with many simple lookups.
Remaining risk. No model size makes prompt injection impossible. Structural separation of instructions and evidence, citation validation and a generator without tool privileges must hold whichever model runs (Architecture Deep Dive, Component 08).
Ingestion Path: Synchronous vs. Asynchronous
Connectors deliver at-least-once and in bursts: the initial sync is 750,000 chunks, and bulk edits arrive in waves. The query path must stay 99.9% available whatever ingestion is doing, and freshness is a stated lag budget rather than "immediately".
| Option | Benefits | Limits |
|---|---|---|
| Synchronous (accepted means indexed) | Simple mental model; no status to poll | Couples connectors to embedder and index health; bursts stall acceptance and retries amplify the load |
Asynchronous (durable accept, 202, queue, workers) | Bursts become backlog, which is measurable lag; query and ingestion resources stay separate | A visible window in which an accepted revision is not yet retrievable; needs a status endpoint |
Selection criteria. Isolation of query availability from ingestion; behaviour under bursts; whether freshness is a monitored number; failure handling for at-least-once delivery.
When another option wins. A synchronous confirmation only as an admin "publish and verify" tool that polls revision status with a timeout — never by making the ingest path itself blocking. Small single-tenant systems with trivial volume can afford synchronous writes.
Remaining risk. A large backlog consumes the lag budget silently unless lag is measured per tenant and per lane; bootstrap syncs should run in their own lane so they cannot delay incremental edits.
Index Publication: In-Place Updates vs. Staged Publication
Two kinds of change reach the index. About 1,200 revisions a day each touch one document; an embedding-model, chunking or analyzer change touches all 750,000 chunks. Queries must never see a half-applied corpus-wide change.
| Option | Benefits | Limits |
|---|---|---|
| In-place writes | No extra storage; changes visible in seconds | A corpus-wide change applied this way leaves a mixed index that live queries search |
| Staged publication: build a new index version, check parity, flip an alias atomically, keep the old one for rollback | Every query sees entirely the old or entirely the new version | Double storage and compute during the build; dual-writes while it runs |
Selection criteria. Whether the change touches one document or the whole corpus; consistency of what a query sees; rollback time; temporary cost.
When another option wins. Never apply a corpus-wide change in place to an index that is serving queries. In-place is acceptable for such a change only when the index is not serving — a new tenant before launch, or a planned outage short enough to fit the freshness budget.
Remaining risk. Revisions arriving during the build must be dual-written through the fenced writer; the bake period must be long enough to catch quality regressions before the old version is deleted.
Decision Map: Invariants and Workload-Dependent Choices
The 14 decisions fall into two groups. Some protect invariants and have no legitimate alternative for access-controlled enterprise content; the rest follow from this workload's numbers and should be revisited when those numbers change.
| Decision | Choice | Another option wins when |
|---|---|---|
| Decision 03 Authorization | Pushdown + live re-check, fail-closed | Never for access-controlled content |
| Decision 06 Partitioning | Per-tenant partitions + quotas | Only partition granularity varies |
| Decision 10 Cache keys | Fingerprint + corpus-state keys, TTL ≤ 60 s | Never keyed by query text; answer caching optional |
| Decision 14 Publication | Staged for corpus-wide changes | Never in place on a serving index |
| Decision 01 Retrieval | BM25 ∥ ANN, RRF k = 60 | Pure identifier or pure narrative corpora |
| Decision 02 Chunking | Structure-aware, 400/100 | Bulk import deadline; whole-section answers |
| Decision 04 Sync trigger | Webhooks + 2-min poll + nightly sweep | Sources without webhooks or change APIs |
| Decision 05 Auth freshness | Cache ≤ 60 s + IdP events, fail closed | Contractual seconds-level revocation |
| Decision 07 Infrastructure | One managed engine | Cost at scale or a missing capability |
| Decision 08 Reranking | Always; fusion-only when degraded | Negligible eval gap or very high volume |
| Decision 09 Embeddings | Precompute; shadow migration | No measured gain from a new model |
| Decision 11 Context | Four retrieved chunks | Single-document or tiny corpora |
| Decision 12 Generator | Mid-to-large model | Smaller model passes the eval and injection suite |
| Decision 13 Ingestion path | Asynchronous, priority lane | Admin verification via status polling |
- Every decision runs the same argument: workload constraint, options and limits, criteria, choice, and the condition under which another option wins.
- Authorization placement, tenant isolation, cache keys and corpus-wide publication protect invariants; their alternatives are bugs, not trade-offs. Lead with them in an interview.
- The other choices follow this workload's numbers; when the numbers change, re-derive them rather than reuse them.