AnswersFiltering & Multi-Tenancy

Collections or Filters for Multi-Tenancy?

For a multi-tenant SaaS application with thousands of small customers, when do separate collections beat metadata filtering, and what does the answer depend on?

2 min readJuly 2026

The short answer

For thousands of small tenants, default to a shared collection with a mandatory tenant filter, and split out only the tenants that are huge, noisy, or contractually required to be isolated.

The deciding variable is your engine's cost of an idle collection: in memory-resident engines every collection carries fixed RAM and index overhead, so thousands of collections mean paying for thousands of mostly-idle indexes; in storage-backed engines idle data costs storage prices, which weakens the penalty on either layout.

What never changes: the tenant filter must be enforced by the engine at query time, and it must stay fast at high selectivity.

What does the shared-collection pattern look like?

Everything lives in one index: every document carries tenant_id, and the application attaches a non-negotiable tenant_id = X filter to every query. That is the same mechanism as RBAC filtering, since tenancy is just the coarsest ACL. Onboarding a tenant is writing rows, not provisioning infrastructure, which is what makes thousands of customers operable by a small team.

In TopK the tenant filter is one predicate on the query:

from topk_sdk.query import select, field, fn
docs = client.collection("docs").query(
select("title", score=fn.semantic_similarity("content", user_query))
.filter(field("tenant_id") == "acme") # mandatory, attached by the app
.sort(field("score"), asc=False)
.limit(10)
)

The query documentation covers the predicate syntax.

When do separate collections win?

Separate collections win in three cases. A scale outlier, a tenant so large it degrades everyone's latency, deserves its own home. Compliance isolation wins when contracts mandate physical separation that a filter can't provide. Divergent configuration wins when a tenant needs a different embedding model or schema and can't share an index built on other assumptions. All three describe your largest few customers. Building per-tenant collections for everyone designs the whole system around the exceptions.

Start shared; split out the exceptions.

Shared collection + filter

Thousands of tenants, one index

Mandatory tenant filter

Default for small tenants

Collection per tenant

One index per tenant

Fixed overhead times tenant count

Reserve for the outliers

What's the failure mode of each layout?

The shared layout fails through its filter: a tenant occupying 0.1% of a shared index is a 0.1%-selectivity filter on every query, which is exactly where naive engines time out or under-fill. The separate layout fails through fixed-cost multiplication and operational sprawl, and in stateful engines per-tenant growth reintroduces shard-rebalancing pain one collection at a time.

TopK's architecture keeps both layouts viable. Each collection is its own prefix on object storage: isolation by construction, with idle data at storage prices rather than pinned to compute. Natively-evaluated filters are benchmarked across selectivities for the shared layout, and for tenants who need physical isolation contractually, dedicated single-tenant regions cover the compliance case. The choice can follow your tenants rather than your database.