# Feather DB Namespace Isolation: Multi-Tenant Memory Architecture > Feather DB namespaces give you per-tenant memory isolation inside a single .feather file. v0.15.3's adaptive capacity shrinks a 19-namespace deployment from 709 MB to 92 MB — a 7.7× reduction. Here's how to design multi-tenant systems that actually scale. - **Category**: Architecture - **Read time**: 6 min read - **Date**: June 16, 2026 - **Author**: Feather DB (Engineering) - **URL**: https://getfeather.store/theory/feather-db-namespace-isolation-guide --- ## The multi-tenant memory problem Multi-tenant AI systems have a specific isolation requirement that generic vector stores don't enforce well: User A's memories must be completely invisible to User B's searches — not just filtered out, but never traversed at all. At 10,000 users, a filter-after-scan approach is a performance disaster. You need true graph partitioning, not a WHERE clause bolted onto a global index. Feather DB namespaces are that partitioning. Each namespace is a named scope with its own HNSW subgraph inside a single `.feather` file. Search in namespace `user_123` traverses only that subgraph. It doesn't know the other namespaces exist. ## What a namespace actually is A namespace is a per-modality named scope within a single `.feather` file. Every modality (text, visual, audio) can have its own namespace set. The namespace is passed as a string at add time and at search time — that's the entire API surface. ```python import feather_db as fdb db = fdb.DB.open("saas.feather", dim=768) # Two users, one file, complete isolation db.add(embed("Alice prefers dark mode."), text="Alice prefers dark mode.", namespace="user_alice") db.add(embed("Bob is building a payments API."), text="Bob is building a payments API.", namespace="user_bob") # Search for Alice — Bob's data is never traversed results = db.search(embed("What UI does this user prefer?"), k=5, namespace="user_alice") ``` Under the hood, Feather maintains a per-namespace HNSW subgraph. Search latency for namespace `user_alice` scales with the number of memories Alice has — not with the total count across all namespaces. Adding 10,000 users does not slow down any individual user's search. ## The v0.15.3 adaptive capacity win Before v0.15.3, every namespace was pre-allocated with capacity for 1,000,000 elements. That made the math brutal for multi-tenant systems: 19 namespaces × 1M capacity = memory footprint that bore no relationship to actual usage. v0.15.3 shipped adaptive capacity. Each namespace now starts at 4,096 elements and grows as needed. The impact on a 19-namespace deployment: VersionNamespacesMemoryReduction Pre-v0.15.319709 MB— v0.15.3+1992 MB7.7× A namespace holding 200 memories consumes the memory of 200 memories — not 1,000,000. This is what makes per-user namespaces practical at the hundreds-of-tenants scale you'll actually operate at during early product stages. ```bash pip install "feather-db>=0.15.3" ``` ## Core API: add, search, forget All three core operations accept a `namespace` parameter. The pattern is consistent across the entire API surface. ```python import feather_db as fdb db = fdb.DB.open("tenants.feather", dim=768) # ADD — scope memory to a namespace def remember(user_id: str, text: str): db.add(embed(text), text=text, namespace=user_id) # SEARCH — results scoped to namespace only def recall(user_id: str, query: str, k: int = 5): return db.search(embed(query), k=k, namespace=user_id) # FORGET — delete all memories in a namespace def forget_all(user_id: str): db.forget_namespace(user_id) ``` `forget_namespace` removes the entire subgraph for that namespace — vectors, HNSW graph, and metadata — and frees the adaptive capacity allocation. Use it when a user deletes their account or when a session expires and you want full cleanup. ## Design patterns ### Per-user isolation (the default SaaS pattern) Use the user's account ID as the namespace key. This gives you a hard tenant boundary enforced at the graph level. ```python namespace = f"user_{user.account_id}" db.add(embed(text), text=text, namespace=namespace) results = db.search(query_vec, k=5, namespace=namespace) ``` ### Per-session scoping For agents that need fresh context per session without bleeding across sessions, scope to a session ID. At session end, call `forget_namespace` or let the namespace idle for compaction. ```python namespace = f"session_{session_id}" # Build session memory during conversation for turn in conversation: db.add(embed(turn.content), text=turn.content, namespace=namespace) # Query during the session results = db.search(query_vec, k=5, namespace=namespace) # Clean up at end of session db.forget_namespace(namespace) ``` ### Per-agent-role partitioning Multi-agent systems often have specialized agents — a planner, a critic, a researcher — that should not share memory. Namespace by role to keep their context clean. ```python agents = ["planner", "critic", "researcher", "executor"] def agent_memory(role: str, text: str): db.add(embed(text), text=text, namespace=f"agent_{role}") def agent_recall(role: str, query: str): return db.search(embed(query), k=5, namespace=f"agent_{role}") ``` ### Per-topic knowledge scoping For a product where users have distinct knowledge domains (e.g., different projects, codebases, or knowledge bases), namespace by topic within the user's scope. Combine with entity tags for secondary grouping. ```python def add_to_topic(user_id: str, topic: str, text: str): namespace = f"{user_id}_{topic}" db.add(embed(text), text=text, namespace=namespace) # user_42_backend, user_42_frontend, user_42_infra add_to_topic("user_42", "backend", "FastAPI service handles auth via JWT.") add_to_topic("user_42", "infra", "Deployed on Render, Postgres on Supabase.") ``` ## Cross-namespace search Sometimes you need to search across multiple namespaces — for admin tooling, analytics, or a super-user view. Feather doesn't support a single cross-namespace ANN call (by design — the subgraphs are separate), so the pattern is a fan-out with merge: ```python def search_across_namespaces(namespaces: list[str], query: str, k: int = 5): query_vec = embed(query) all_results = [] for ns in namespaces: results = db.search(query_vec, k=k, namespace=ns) for r in results: r.meta.set_attribute("_namespace", ns) all_results.append(r) # Merge by score, keep top k globally all_results.sort(key=lambda r: r.score, reverse=True) return all_results[:k] # Admin: search across all active users active_users = ["user_alice", "user_bob", "user_carol"] results = search_across_namespaces(active_users, "deployment issues", k=10) ``` Cross-namespace fan-out is appropriate for admin dashboards and offline analytics. It should not be in the hot path of per-user requests — those should always use single-namespace search. ## Namespace-scoped compaction Feather's compaction removes low-scoring nodes based on recall history and temporal decay. You can trigger compaction for a specific namespace without touching the rest of the file: ```python # Compact a single user's namespace # Removes nodes with low stickiness and high decay db.compact_namespace("user_alice", min_recall_count=1, max_age_days=90) # Or compact all namespaces in one pass db.compact(min_recall_count=1, max_age_days=90) ``` Namespace-scoped compaction is useful for long-running SaaS deployments where inactive users accumulate stale memories that waste adaptive capacity without ever surfacing in searches. ## Multi-tenant SaaS: one file or many? Two valid architectures — each with a clear use case: PatternStructureBest forTradeoff One file, many namespaces`saas.feather` with namespace per userStartups, <10K users, single-server deploySimple ops; one file to back up One file per account`user_alice.feather`, `user_bob.feather`Enterprise, GDPR right-to-erasure, data residencyFile-per-user management; stronger isolation For most early-stage SaaS products, one file with namespace-per-user is simpler and sufficient. You get the 7.7× memory savings from adaptive capacity, one backup target, and zero per-user infrastructure. When you need GDPR right-to-erasure (delete everything for a user), one file per account makes it a file deletion — no namespace surgery required. ## Security: what namespace isolation is and isn't Namespace isolation is **logical, not cryptographic**. The HNSW subgraph for namespace `user_alice` is physically stored in the same file as `user_bob`. A process with file-system read access to `saas.feather` can read all namespaces. What this means in practice: - Namespace isolation **prevents cross-tenant data from appearing in search results** — this is the common threat in multi-tenant AI products. - Namespace isolation **does not prevent** a compromised process with file access from reading any namespace directly. - For strong cryptographic isolation (healthcare, finance, legal), use separate `.feather` files per tenant, optionally encrypted at rest via OS-level or cloud storage encryption. ```python # Logical isolation (most SaaS use cases) # One file, one process, namespace enforcement in application code db = fdb.DB.open("saas.feather", dim=768) # Strong isolation (regulated industries) # Separate file per tenant, each file independently encrypted/permissioned def get_tenant_db(tenant_id: str) -> fdb.DB: path = f"/secure/tenants/{tenant_id}.feather" return fdb.DB.open(path, dim=768) ``` ## Putting it together: a complete SaaS pattern ```python import feather_db as fdb from datetime import datetime db = fdb.DB.open("saas_memory.feather", dim=768) class TenantMemory: def __init__(self, user_id: str): self.user_id = user_id self.namespace = f"user_{user_id}" def add(self, text: str, category: str = "general", importance: float = 1.0): mem = db.add(embed(text), text=text, namespace=self.namespace, entity=category) mem.meta.set_attribute("created_at", datetime.utcnow().isoformat()) mem.meta.set_attribute("importance", str(importance)) return mem def search(self, query: str, category: str = None, k: int = 5): return db.search(embed(query), k=k, namespace=self.namespace, entity=category) # None = all categories def forget_category(self, category: str): db.delete_by_entity(namespace=self.namespace, entity=category) def forget_all(self): db.forget_namespace(self.namespace) def compact(self, max_age_days: int = 90): db.compact_namespace(self.namespace, min_recall_count=1, max_age_days=max_age_days) # Usage alice = TenantMemory("alice_7f3a") alice.add("Alice works at a fintech startup.", category="work", importance=1.5) alice.add("Alice mentioned anxiety about the Series A.", category="emotional") results = alice.search("What is the user's work situation?") ``` The `TenantMemory` wrapper keeps namespace strings out of application code and makes the tenant boundary explicit in the type system. Each instantiation represents one tenant's memory scope — one HNSW subgraph, one adaptive capacity allocation, zero cross-tenant data exposure in search. **Install:** `pip install "feather-db>=0.15.3"` · **GitHub:** [github.com/feather-store/feather](https://github.com/feather-store/feather) --- *This is the machine-readable mirror of the theory post at [getfeather.store/theory/feather-db-namespace-isolation-guide](https://getfeather.store/theory/feather-db-namespace-isolation-guide). For the full Feather DB documentation, see [getfeather.store/llms-full.txt](https://getfeather.store/llms-full.txt).*