Structural Guarantees vs Policy: Why Per-User Databases Beat Row-Level Security
Here's a question most architects skate past: when User A's data must never be visible to User B, what's enforcing that rule?
In most systems, the answer is "the query author remembered to add WHERE user_id = ?." The isolation is policy. It lives in the discipline of every engineer who writes a query. When someone forgets — and eventually someone will — the guarantee fails silently.
Emily does it differently. User A and User B have physically separate PostgreSQL databases. Cross-user access isn't "forbidden by policy." It's impossible by architecture. There is no query you can write that reads across them without deliberately opening two connections.
The difference between policy and structure
Policy says: "don't do X." You trust people to remember.
Structure says: "X isn't possible." You don't need to trust anyone.
Policy fails when humans err. Structure fails only when the architecture itself is wrong — and that's a much rarer, much more auditable event.
Row-level security (RLS) is policy, even when the database enforces it. The RLS rule has to be defined correctly, applied to every table, and maintained against schema changes. The enforcement is policy-shaped even when the mechanism is technical.
Per-user databases are structural. The isolation is the same mechanism that isolates two Postgres instances on different hosts. It's not an opt-in guarantee. It's the default state of the universe.
What this actually buys
Four concrete benefits:
1. Correctness under team growth. New engineers can't accidentally leak cross-user data because there's no shared table to leak from. The worst they can do is fail to connect — not fail to filter.
2. Right-to-be-forgotten is a single command. DROP DATABASE emily_user_{id} removes everything. No "scan all tables for rows with this user_id" cleanup. No "don't forget the cache" anxiety. The data is gone because the container is gone.
3. Portability is pg_dump. Handing a user their own data is one command. Handing them their Emily is one command. This is what data sovereignty looks like when it's real.
4. Horizontal sharding is trivial. Want to move 20% of users to a second host? Copy the databases. You don't re-shard rows. You don't recompute partition keys. You move the containers.
The scaling-unit shift
Here's the subtle move Emily makes: the scaling unit is the user, not the row.
Most systems pick the row as the scaling unit because rows are small and additive. You scale by adding more rows to bigger tables. Isolation becomes a discipline problem.
Emily picks the user as the scaling unit because users are what actually need to be isolated. You scale by adding more user-databases. Isolation becomes an architectural invariant.
This trades some things (cross-user aggregation is harder) for others (isolation is free, portability is trivial, sharding is containerized).
What you give up
Honesty: per-user DBs aren't costless.
- Cross-user analytics requires separate pipelines that read from all databases. You can't just
SELECT COUNT(*) FROM memoriesand get a platform-wide count. - Connection pools get more complex. Emily's pool manager has to route connections per user and handle many small databases rather than one big one.
- Backup and migration tooling needs to understand the per-user topology. Schema migrations run N times, once per user database.
- Vector indexing is per-user, which keeps indices fast but means no "all users" similarity search without cross-DB work.
These are real costs. For a product where cross-user queries are the main workload (ad analytics, say) they'd dominate. For Emily, where the workload is almost entirely within-a-user, they're a small tax paid for a large structural guarantee.
The audit perspective
Here's what an auditor sees:
- Policy-based isolation: "show me the code that enforces isolation." You show queries. They find one missing filter. They fail you.
- Structural isolation: "show me the code that enforces isolation." You show the connection boundary. There's nothing else to review. Pass.
The audit cost is different in kind, not degree. The structural version is cheaper to defend because there's less surface to audit.
When policy is the right answer
Structural isolation isn't universally correct. For workloads that are dominated by cross-user aggregation, or for very high-cardinality tenants where provisioning N databases is impractical, RLS is a sensible trade.
Emily isn't that workload. Users don't need to query each other. Users aren't thousands of tiny entities — they're long-lived, memory-intensive, stateful beings. Per-user databases are the right shape for this workload.
The principle generalizes beyond databases: when the guarantee matters, make it structural. When it doesn't matter as much, policy is fine. The expensive mistake is treating a structural requirement as a policy problem.
Part of the Emily OS architecture philosophy series.