Lesson 27 / 28

Case Study: Choosing and Running a Store for a SaaS Knowledge Base

Walk through the decision and the design for 300 tenants.

Decision and design

Situation: a SaaS product has 300 customer tenants, 6 million chunks in total (768 dimensions), a team already running PostgreSQL, and a requirement that no tenant ever sees another's data. Decision: start with PostgreSQL + pgvector because it keeps vectors next to existing data, supports row-level security and transactions, and the team already operates it; revisit a dedicated engine only if measured needs (scale, latency, features) exceed it. Sizing: 6 million × 768 × 4 bytes is about 18 GB of vectors, plus an HNSW index and payload, so plan roughly 50 to 60 GB with headroom on a machine with enough RAM, and consider halfvec to halve it. Schema: chunks(id, tenant_id, doc_id, content_hash, text, embedding, model, created_at) with a deterministic ID. Indexes: HNSW on embedding with the matching operator class, a B-tree on tenant_id; iterative scans for per-tenant filtered queries, or a partial index for a few very large tenants. Search: tenant filter always applied, hybrid full-text + vector with RRF, rerank, score threshold. Operations: batch idempotent upserts, nightly incremental refresh by content hash, replicas for reads, point-in-time recovery, a model-upgrade runbook with a parallel table. Quality and safety: a golden set with recall@k gating changes, automated tenant-isolation tests, p95 latency and recall dashboards.

A measured, secure, rebuildable store

Start simple and exact, measure recall and latency, then add the index, filters and scale you actually need.

Four habits: start exact, measure, isolate, rebuild.
Figure 8.1 — Start exact, measure, isolate and rebuild.

The design on one page

Each line maps to a section of this course.

Engine      PostgreSQL + pgvector (vectors beside data, RLS, transactions, familiar ops)   (Sec 1, 2)
Sizing      6M x 768 x 4B ~ 18 GB vectors (+ index + payload) -> ~50-60 GB; halfvec if needed (Sec 6)
Indexes     HNSW (matching opclass) + B-tree on tenant_id; iterative scan / partial index    (Sec 3, 4)
Search      tenant filter ALWAYS, full-text + vector with RRF, rerank, score threshold        (Sec 4)
Ingest      batched idempotent upserts by deterministic id; nightly refresh by content hash  (Sec 1, 6)
Resilience  read replicas, PITR backups, model-upgrade runbook with a parallel table         (Sec 6, 7)
Quality     golden set recall@k gates changes; automated tenant-isolation tests; dashboards   (Sec 3, 7)

Prove it with a pilot

Load one real tenant's data, run the golden set and a load test, and only then commit to the design.

Quick check: Why does the case study start with PostgreSQL + pgvector?

  • Vector search is impossible elsewhere
  • PostgreSQL is always the fastest option
  • Dedicated engines cannot filter
  • It keeps vectors with existing data, supports row-level security, and the team already runs it
Answer

It keeps vectors with existing data, supports row-level security, and the team already runs it — Start with what you operate; move only when measured needs demand it.