AI Engineering Playbook
Vector Databases

Scaling & Operations

Sharding, index rebuild/refresh, multi-tenancy patterns, backup and consistency.

Prerequisites

  • ANN indexes — HNSW lives in RAM and hates mutation; ops is largely about living with that.
  • Vector database landscape — which engines you operate changes memory, compaction, and tenancy knobs.
  • Filtering and metadata — shared-collection multi-tenancy makes every query a filtered query; isolation bugs are security bugs.

The intuition

A vector store is a database with two twists: the hot path wants the index in memory, and the graph index was not designed for frequent hard deletes. Everything hard about operating one at scale is a consequence of those two facts.

Think of a city's road map printed on a huge poster on the wall (the HNSW graph in RAM). Adding a new street is easy — draw it in. Removing a neighborhood is not: you tape over intersections (tombstones) so drivers skip them, but the poster still takes the same wall space until a night crew reprints whole districts (compaction). If half the city is taped over, navigation gets worse and the wall is still full. Sometimes you print a fresh poster in the back room and swap it in (blue/green rebuild).

Scale-out is then ordinary distributed systems: hang more posters (shards), keep copies (replicas), decide whether each tenant gets their own poster or a sticker on a shared map.

Key insight

Memory sizing and tombstone/compaction health predict more outages than QPS charts. If you only monitor request rate, you will discover churn and working-set growth as mysterious latency and recall decay.

Why it exists

Getting ANN search working on a laptop is easy. Running it for a product with continuous upserts, deletes, multi-tenant isolation, and disaster recovery is a different job.

Hard constraints:

  1. RAM bounds the graph. Float32 vectors plus HNSW links dominate cost; past a node's capacity you must quantize, spill to disk, or shard — not just "add a bit of swap."
  2. Deletes are soft. Hard removal from HNSW can sever paths; engines tombstone and compact later. High churn without optimizer headroom degrades search while memory stays high.
  3. The index is derived data. Source documents and the embedding pipeline can rebuild it — but re-embedding 10M chunks costs real money and hours; snapshots are the fast path, rebuild is the correctness path.
  4. Dual-write is not a transaction. App DB and vector index drift unless CDC/outbox and reconciliation exist.
  5. Multi-tenancy is a product requirement. Isolation vs cost vs tenant-count ceiling forces an explicit pattern, not a default shared collection.

Alternatives lose differently. Ignore compaction → silent recall/latency death. Shard first → expensive machines before quantization would have fit. Snapshot-only DR without rebuild → embed model or pipeline bugs leave you unable to regenerate. Metadata-filter tenancy without server-side injection → one missed filter is a breach. Operations practice exists to keep memory, mutation, consistency, and isolation under control as N and churn grow.

The core idea

Operating a vector store is mostly ordinary database operations plus two vector-specific twists: the index lives in RAM, and the index hates mutation.

Memory first. The sizing rule of thumb: raw vectors = count × dimensions × 4 bytes (float32), and total RAM ≈ raw × 1.5 to cover graph links, metadata indexes, and temporary segments during optimization (Qdrant's official formula). 10M OpenAI-sized 1536-dim vectors = ~61 GB raw, ~92 GB provisioned. That number drives everything: node size, shard count, and whether you reach for quantization (int8 = 4× reduction, binary = up to 32×) or disk-backed indexes before you reach for more machines.

Mutation second. HNSW inserts are incremental, but deletes are not: removing a node can sever graph paths, so every engine soft-deletes — a tombstone marks the vector, queries skip it, and a background optimizer (segment merge/compaction/vacuum) later rewrites segments without the dead entries. Consequence: a high-churn collection silently accumulates tombstones, memory doesn't come back on delete, and recall/latency degrade until compaction runs. Full index rebuild is the escape hatch — build a fresh index in the background while serving from the old one (blue/green), then swap.

Scale-out is standard sharding: hash vectors across shards, fan out the query, merge per-shard top-k; replication gives HA and read throughput. Multi-tenancy has three patterns — namespace per tenant, collection per tenant, or shared collection with a tenant filter — trading isolation against cost and tenant-count ceiling. And backup has a twist: the most reliable recovery for a vector index is often not a snapshot but re-embedding from the source-of-truth store, so the rebuild pipeline is part of your disaster-recovery story.

How it actually works

Write path, segments, and what search sees:

Sharding and replication. A collection is split into shards (by ID hash, or explicitly by tenant/partition key); each shard holds its own segments and HNSW graphs. Query path: scatter to all shards (unless a partition key prunes), each returns its local top-k with scores, coordinator merges — correct because similarity scores are globally comparable. Writes route by key. Replication is per-shard (replica count N); consensus (e.g., Raft) coordinates cluster metadata, while data replication is usually eventually consistent with tunable write/read consistency factors. More shards = faster parallel search but higher fan-out overhead and more merge work; shards sized so each fits comfortably in its node's RAM.

Segments and the write path. Writes land in a WAL, then a mutable in-memory segment (searched by brute force — this is why fresh points are findable before indexing); when a segment reaches threshold size it's sealed and an HNSW graph is built for it; the optimizer merges small sealed segments into bigger ones in the background. Search = union over all segments (indexed and unindexed), which is how vector DBs deliver near-real-time freshness without rebuilding a monolithic index per write.

Deletes, tombstones, updates.

  • Delete = tombstone in a bitset consulted during traversal; space reclaimed only at merge/compaction ("vacuum").
  • Update = delete + reinsert (vectors are immutable in the graph); metadata-only updates are cheap in engines that store payload separately (Qdrant), expensive where a row rewrite is needed (pgvector: MVCC dead tuple + index bloat → autovacuum tuning matters; heavy churn eventually wants REINDEX CONCURRENTLY).
  • Rebuild vs incremental: incremental upsert is fine at low churn; past roughly 10–20% tombstone ratio (engine-dependent) query latency and recall sag, and a compaction or full rebuild is cheaper than living with the degraded graph. Pinecone's "Great Algorithms Are Not Enough" post is the canonical description of why bolted-on HNSW forces periodic full rebuilds and blue-green swaps.

Key insight

Fresh points are findable because search unions mutable segments with sealed HNSW — not because every write rebuilds the whole graph. That design buys near-real-time ingest; the bill comes due as segment count and tombstone ratio climb without optimizer capacity.

Multi-tenancy patterns:

PatternIsolationCost/scalingCeilingUse when
Namespace per tenant (Pinecone-style)Physical partition; queries scoped, no noisy neighbors; delete namespace = instant offboardingCheap; billed/loaded per namespace~100k+ namespaces (contact-support territory beyond)SaaS with many small-to-medium tenants
Collection/shard per tenant (Weaviate native tenants)Strongest: separate index, per-tenant tuning, per-tenant activity state (hot / inactive / offloaded-to-S3)Per-collection index overhead; naive collection-per-tenant dies at thousands, purpose-built tenant shards scale to ~1M+ with cold offloadingHigh with native support onlyEnterprise tenants, compliance isolation, per-tenant SLAs
Shared collection + tenant metadata filter (Qdrant-recommended)Logical only — one missed filter leaks data; noisy neighbors share the graphCheapest; one index amortized across all tenants; mark tenant field as the tenancy key so storage co-locates each tenant's pointsMillions of tenantsHuge tenant counts with small per-tenant data

The real tradeoff axis: per-tenant index quality and isolation vs memory amortization. Dedicated per-tenant indexes give unfiltered fast searches and hard boundaries but multiply index overhead; shared-index filtering amortizes memory but makes every query a filtered query and every bug a potential cross-tenant leak.

Backup and consistency.

  • Engines offer per-collection snapshots (Qdrant) or object-storage backups (Milvus, whose storage already lives on S3); pgvector inherits Postgres PITR/WAL archiving — a genuinely better story than most dedicated engines.
  • The vector DB is a derived store: source documents + embedding model + pipeline can regenerate it. Mature teams treat re-embedding from source as the authoritative recovery path and snapshots as a restore-time optimization (re-embedding 10M chunks costs real money and hours — snapshot restore is minutes).
  • Dual-write drift is the chronic consistency problem: app writes doc to primary DB, then upserts the vector — the second write fails and search silently misses the doc. Fixes: outbox/CDC-driven indexing with idempotent deterministic IDs, plus a periodic reconciliation job diffing source IDs against index IDs.

Common misconception

"We deleted 30% of the corpus, so memory should drop 30%." It will not until compaction rewrites segments. Tombstones skip results; they do not shrink the graph poster on the wall. Alert on deleted-fraction and optimizer lag, not only on free disk after DELETE API calls succeed.

Memory sizing worked examples (float32, HNSW, 1.5× rule):

CorpusDimsRawProvisioned (~1.5×)With int8 quantization
1M7683.1 GB~4.6 GB~1.2 GB
10M153661.4 GB~92 GB~23 GB
100M1024410 GB~614 GB → shard~154 GB

HNSW link overhead specifically ≈ M × 2 × 4–8 bytes per vector (M=16 default → ~128–256 B/vector) — included in the 1.5× rule. Also: pgvector HNSW builds need the graph in maintenance_work_mem; an index build OOM-ing or spilling is the classic first scaling incident.

The flows

FlowSequenceWhen it appliesWhat breaks it
Realtime upsertWAL → mutable segment → seal + HNSW when full → optimizer mergescontinuous ingest, near-real-time searchmerge lag; too many tiny segments; OOM on seal
Delete / updatetombstone → queries skip → compaction rewrites segmentdocument removal, re-embed of one chunktreating delete as immediate RAM free; no vacuum under churn
Distributed queryroute by partition key or scatter-all → per-shard segment search → merge top-k by scoresharded collectionsstraggler shard dominates p95; unbalanced shard sizes
Blue/green rebuildbuild new collection/index → dual-write → recall parity sample → atomic swap → keep old for rollbackhigh tombstone ratio, param change, embedding-model migrationswap without parity check; no dual-write → lost updates
Disaster recoverysnapshot restore for speed or re-embed from source for authoritynode loss, corruption, region failbackup never restore-tested; no deterministic IDs for rebuild
Tenant lifecyclecreate namespace/tenant shard → isolate queries → offload cold → delete namespace on offboardingmulti-tenant SaaSshared collection without mandatory server-side tenant filter

On the governed enterprise platform, personal upload stores often use short-TTL namespaces or collections with scheduled purge, while org-wide corpora use sharded collections with entitlement filters, CDC from the document system of record, and a tested rebuild path for embedding-model upgrades.

A worked example

Goal. Size and operate a collection for 10M chunks × 1536-dim float32 embeddings, steady ingest, moderate delete churn (~5% of points deleted over a quarter), multi-tenant SaaS with ~50k customers.

Memory and cost levers.

ItemNumber
Raw vectors10M × 1536 × 4 B ≈ 61.4 GB
Provisioned HNSW rule of thumb×1.5 ≈ ~92 GB RAM before headroom for OS and query
int8 quantization first~4× vector storage cut → on the order of ~23 GB provisioned class (still validate recall)
Shard sketch2–3 shards so each graph fits comfortably with merge headroom
Wrong first move10 memory-optimized nodes before trying int8 / disk offload

Multi-tenancy at 50k customers.

OptionFit
Namespace / native tenant shard per customerStrong isolation, instant offboarding, cold offload — preferred at this count
Naive collection-per-tenant without engine supportDies from per-index overhead long before 50k
Shared collection + tenant filterWorks at millions of tiny tenants but every query is filtered; isolation is query correctness

Churn incident. Over two months delete/re-ingest cycles push tombstone ratio toward the 10–20% danger band. p95 search rises ~3× with flat traffic. Order of investigation: RAM headroom (page faults on graph walk) → deleted-ratio / segment stats → optimizer lag → on pgvector, vacuum/bloat history. Remediation: schedule compaction off-peak; if still degraded, blue/green full rebuild; fix upstream dual-write so deletes actually reach the index.

Consistency. Document write to primary succeeds; vector delete fails. Stale chunks still retrieve in RAG. Fix: outbox/CDC with deterministic doc_id+chunk_id, idempotent upsert/delete, reconciliation job diffing source IDs vs index IDs on a schedule. Freshness SLO example: "searchable ≤60s after write" measured end-to-end, with CDC lag and queue depth as leading indicators.

1. Create new collection with target M / ef_construct / quantization
2. Dual-write all upserts/deletes to old and new
3. Backfill from source with deterministic IDs
4. Sample queries: recall@k new vs old vs brute-force subset
5. Atomic alias swap; keep old for rollback window
6. Drop old after soak; stop dual-write

What each omission looks like in production

  • No tombstone/segment metrics → "latency grew 3× with same QPS" becomes a multi-week mystery.
  • Shard before quantize → cloud bill scales with unreduced float32 graphs.
  • Snapshot never restore-tested → "backups green" and a 12-hour RAM reload outage.
  • Dual-write without reconciliation → deleted policies still answer in RAG; new docs silently missing.
  • Tenant filter only in client SDKs → one code path forgets the filter; cross-tenant data leak.
  • Rebuild on the hot collection without dual-write → lost updates during the build window.

Common drill-downs

These are the questions engineers ask one level down when operating vector search at scale.

How much RAM for 10M 1536-dim embeddings? 10M × 1536 × 4 B ≈ 61 GB raw; ×1.5 for graph links, metadata index, and merge headroom ≈ 92 GB. Int8 quantization drops vector storage 4× (~23 GB provisioned); binary up to 32× with a rescoring pass. Always state the formula before the number.

How do vector DBs handle deletes? Tombstones: the point is flagged, traversal skips it, storage and graph links persist until a background compaction/merge rewrites the segment. Hard-deleting from HNSW directly can disconnect the graph. Implication: memory isn't freed on delete, and high churn degrades queries until compaction or rebuild.

Incremental upsert vs full rebuild — when each? Incremental for steady low churn; HNSW inserts are native. Full (blue/green) rebuild when tombstone ratio is high, when recall has measurably decayed, when changing index parameters (M, ef_construct), and always when changing the embedding model. Serve from the old index during the build; swap after verifying recall parity.

Design multi-tenancy for a RAG SaaS with 50k customers. Namespace-per-tenant (or native tenant shards): physical scoping, no noisy neighbors, instant offboarding by namespace delete, cold tenants offloadable. Collection-per-tenant dies at this count from per-index overhead unless the engine has purpose-built tenant management; metadata-filter tenancy is the fallback for millions of tiny tenants but makes isolation a query-correctness property. Enforce tenant scoping server-side either way.

How does a distributed vector search execute? Route by partition key if present, else scatter to all shards; each shard searches its segments (indexed sealed ones plus brute-force over the fresh in-memory one), returns local top-k with scores; coordinator merge-sorts to global top-k. Scores are globally comparable, so the merge is trivial; the cost is fan-out latency = slowest shard.

How do you back up a vector database? Engine snapshots to object storage for fast restore, plus the real answer: the index is derived data — keep source docs + pipeline able to re-embed from scratch, with deterministic IDs so the rebuild is idempotent. Measure restore time; a backup that takes 12 hours to reload into RAM is a 12-hour outage.

Your index and your primary DB disagree — deleted docs still surface in RAG. Root cause? Dual-write without a transaction: primary write succeeded, index delete failed (or arrived out of order). Fix with CDC/outbox-driven indexing, idempotent upserts/deletes keyed by doc ID + version, and a reconciliation job that diffs IDs between source and index on a schedule.

Search latency slowly rose 3× over two months with no traffic change. Suspects? In order: working set outgrew RAM (corpus growth → page faults on graph traversal), tombstone accumulation from churn, segment-count growth outpacing the optimizer, and — on pgvector — index bloat from MVCC dead tuples. Check memory headroom, deleted-ratio, segment stats, and vacuum history before touching query code.

Production concerns

  • Watch tombstone ratio and segment counts, not just QPS: rising deleted-fraction predicts the latency/recall sag before users notice. Alert on optimizer/compaction lag. Wire these into observability alongside retrieval quality metrics.
  • Compaction is a resource event: segment merges burn CPU and can double a collection's disk footprint transiently. Schedule for off-peak, cap concurrency; on Postgres, tune autovacuum for the vector table instead of accepting defaults.
  • Rebuild without downtime: build new index/collection alongside, dual-write during the build, verify recall parity on a sample query set, atomically swap (alias/DNS/collection rename), keep the old one for rollback. Same play for embedding-model migrations — which change every vector and are therefore always a full rebuild.
  • Cold start / restore time: loading hundreds of GB of index from disk into RAM takes minutes; replicas exist partly so restarts don't take the service down. Test restore time, not just backup success. See reliability.
  • Cost levers in order: quantize (int8 is nearly free in recall for most embedding models, binary needs rescoring), offload cold tenants/segments to disk or object storage, then shard. Sharding first is the expensive mistake. Tie spend to cost.
  • Multi-tenant safety: tenant filter injected in one server-side choke point, contract tests that assert cross-tenant queries return zero, and per-tenant quotas so one tenant's ingest storm can't starve compaction for everyone else. Security framing: security.
  • Consistency SLO: define index freshness explicitly (e.g., "document searchable ≤60s after write") and measure it end-to-end — it's the metric users actually feel, and CDC lag, WAL backlog, or indexing-queue depth are its leading indicators. Product-level coupling: production RAG.

Test yourself

After a bulk purge of outdated policies, dashboards show DELETE success but RSS on the vector nodes barely moved. Is the engine broken?

You need capacity for 100M × 1024-d vectors. A proposal is 'buy ~614 GB RAM nodes and call it done.' What is wrong with that plan?

Design multi-tenancy for 50k SaaS customers with compliance isolation and occasional huge tenants. Which pattern and why?

Latency rose 3× over two months; traffic is flat. Give an ordered diagnostic list.

Why is re-embedding part of disaster recovery if snapshots restore faster?

Go deeper

Where this connects

  • Filtering and metadata — shared-collection tenancy turns isolation into filtered search; read this before treating tenant_id as "just another payload field."
  • Vector database landscape — managed vs self-hosted and engine choice determine which of these ops knobs you own.
  • Production RAG — freshness SLOs, ACL, and degradation when the index is part of a live retrieval product.
  • Reliability — outbox/CDC, retries, and failure modes that keep the vector index consistent with the source of truth.
RAG

On this page