CSE Roadmap

Common tier · about 3 hours · 4 parts

Part 2 of 4 · 11 min read

Definitions

Covers: Definitions; The boundaries; Results; Design rules; Interview questions, with the follow-ups; Common mistakes; What the example does not do.

Definitions

Tenant, multi-tenancy

  • Tenant: one customer organisation using the shared system.
  • Multi-tenancy: serving many tenants from the same running system.

Isolation, noisy neighbour

  • Isolation: tenants cannot see or affect each other's data or performance.
  • Noisy neighbour: one tenant's heavy use degrading others.

Row-level security

  • Row-level security (RLS): database policies that restrict which rows a session can read or write.

Confused deputy

  • Confused deputy: a component with authority is tricked into using it for someone without that authority (here, the agent using a tool on a tenant's behalf for another).

Namespace, pre-filter, post-filter

  • Namespace: a separate partition of an index or store per tenant.
  • Pre-filter: restricting candidates before similarity search.
  • Post-filter: dropping results after search.

Offboarding, erasure registry

  • Offboarding: removing a tenant and its data.
  • Erasure registry: the list of every store and cache that can hold tenant data.

The boundaries

AAAWgmp1bWIAAAAeanVtZGMycGEAEQAQgAAAqgA4m3EDYzJwYQAAABZcanVtYgAAAEdqdW1kYzJtYQARABCAAACqADibcQN1cm46YzJwYTo3NGEyYTlmNC05YzA3LTRhMzEtYTJhNi0zM2JjMTE4OGZjOTEAAAADl2p1bWIAAAApanVtZGMyYXMAEQAQgAAAqgA4m3EDYzJwYS5hc3NlcnRpb25zAAAAALxqdW1iAAAARGp1bWRjYm9yABEAEIAAAKoAOJtxE2MycGEuaW5ncmVkaWVudC52MwAAAAAYYzJzaGSN9JBrLbXbvJJDHVNw8P4AAABwY2JvcqNpZGM6Zm9ybWF0bWltYWdlL3N2Zyt4bWxqaW5zdGFuY2VJRHgseG1wOmlpZDo1ZDVjOWJkYS0yMjliLTRhODUtYWJmYS0yZTk5YzA2MDA2NmNscmVsYXRpb25zaGlwaHBhcmVudE9mAAAB4mp1bWIAAABBanVtZGNib3IAEQAQgAAAqgA4m3ETYzJwYS5hY3Rpb25zLnYyAAAAABhjMnNoKgIjlc5CqgTaUg0KJ7CFNQAAAZljYm9yomdhY3Rpb25zgqJmYWN0aW9ua2MycGEub3BlbmVkanBhcmFtZXRlcnOha2luZ3JlZGllbnRzgaJjdXJseC1zZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmluZ3JlZGllbnQudjNkaGFzaFgg5N2AbZyQVr0aUn0rr/P6BJ5m/DMw9x5zE2ud1fGkTUWkZmFjdGlvbngdY29tLmFudGhyb3BpYy5jbGF1ZGUucHJvdmlkZWRqcGFyYW1ldGVyc6F4H2NvbS5hbnRocm9waWMub3JpZ2luLWNvbmZpZGVuY2VndW5rbm93bmtkZXNjcmlwdGlvbnhmQ2xhdWRlIHByb3ZpZGVkIHRoaXMgZmlsZSBhdCB0aGUgcmVxdWVzdCBvZiBhIHVzZXIgYW5kIG1heSBoYXZlIGNyZWF0ZWQgb3IgbW9kaWZpZWQgdGhlIGZpbGUgY29udGVudHMubXNvZnR3YXJlQWdlbnShZG5hbWVmQ2xhdWRlcmFsbEFjdGlvbnNJbmNsdWRlZPUAAADIanVtYgAAAEBqdW1kY2JvcgARABCAAACqADibcRNjMnBhLmhhc2guZGF0YQAAAAAYYzJzaPjjsmb3UbpG2GEW8bn7uI4AAACAY2JvcqVjYWxnZnNoYTI1NmNwYWRNAAAAAAAAAAAAAAAAAGRoYXNoWCDj1eG5CdgvTUK97uhL3R93Qc/Wpv2C8+6ckry7WLot/2RuYW1lbmp1bWJmIG1hbmlmZXN0amV4Y2x1c2lvbnOBomVzdGFydBigZmxlbmd0aBkeBAAAAj5qdW1iAAAAJ2p1bWRjMmNsABEAEIAAAKoAOJtxA2MycGEuY2xhaW0udjIAAAACD2Nib3KlY2FsZ2ZzaGEyNTZpc2lnbmF0dXJleE1zZWxmI2p1bWJmPS9jMnBhL3VybjpjMnBhOjc0YTJhOWY0LTljMDctNGEzMS1hMmE2LTMzYmMxMTg4ZmM5MS9jMnBhLnNpZ25hdHVyZWppbnN0YW5jZUlEeCx4bXA6aWlkOmY3YjkzNmFmLTU4NTItNDc2OC04ODlkLTVkYTY3MWY3ZDk3ZXJjcmVhdGVkX2Fzc2VydGlvbnODomN1cmx4LXNlbGYjanVtYmY9YzJwYS5hc3NlcnRpb25zL2MycGEuaW5ncmVkaWVudC52M2RoYXNoWCDk3YBtnJBWvRpSfSuv8/oEnmb8MzD3HnMTa53V8aRNRaJjdXJseCpzZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmFjdGlvbnMudjJkaGFzaFgg2oF3cf5um3Zmc80bnFj0eDZ6QPrb+NrWVZFx31IM0fiiY3VybHgpc2VsZiNqdW1iZj1jMnBhLmFzc2VydGlvbnMvYzJwYS5oYXNoLmRhdGFkaGFzaFggOorIMFL3mIHWzRdunSpJ3IBrra10tWacQxjOjBbIRbR0Y2xhaW1fZ2VuZXJhdG9yX2luZm+jZG5hbWVvQW50aHJvcGljIEZpbGVzZ3ZlcnNpb25lMS4wLjBrc3BlY1ZlcnNpb25lMi40LjAAABA4anVtYgAAAChqdW1kYzJjcwARABCAAACqADibcQNjMnBhLnNpZ25hdHVyZQAAABAIY2JvctKEWQISogEmGCFZAgowggIGMIIBjaADAgECAhRA5aAK7sI50L64g/oGQgU9Z1UTADAKBggqhkjOPQQDAzBJMRcwFQYDVQQKEw5BbnRocm9waWMsIFBCQzEuMCwGA1UEAxMlQW50aHJvcGljIENvbnRlbnQgQ3JlZGVudGlhbHMgUm9vdCBDQTAeFw0yNjA4MDcxODQzNTZaFw0yODA4MDYxOTQzNTZaMEQxFzAVBgNVBAoTDkFudGhyb3BpYywgUEJDMSkwJwYDVQQDEyBBbnRocm9waWMgQ2xhdWRlIENvbnRlbnQgU2lnbmluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJh6CmvLUBgFFNU0vUKlOVtE6djd17L5SuwX0LemFisBM3dkd/3cyjxFA3Qo5S46fX0/ihY0VZ7mfb9KF703t5OjWDBWMA4GA1UdDwEB/wQEAwIHgDAVBgNVHSUEDjAMBgorBgEEAYPoXgIBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUzlHiBIFOZFsj+OPEz5o+nMHXXMIwCgYIKoZIzj0EAwMDZwAwZAIwMXMdFJ4BetLLVY7ORuE9noqbbAZOZn/aArXyTwFAZfKrPzxF2vPoJNf1+UCdg1XGAjBwX1zd9WGqYkqmL5SFqw1QySjr1zJfpJM9+1rdDwSPLMOPOjKuiXjoU/pUUeG9RwmhY3BhZFkNngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPZYQD8j8llqbrbFiiuBMpArxQ7N/zC9HH2LQbv7+9VZ2MVBsGqNushXKr5HJ//FAWZPmkfmiTuSH4KymJ5TGKTDHwc=Where tenant data can cross, and the control at each placeAPI / sessiontenant id comes from authentication, never from the model or the request bodyDatabasescoped repository injects the tenant on every query; row-level security as a second layerVector index / memorynamespace or collection per tenant; filter before search, not afterCachestenant (and user) in every cache key; no semantic cache across tenantsTools and credentialsper-tenant credentials fetched from the session at call time; no shared service accountCompute and quotasper-tenant concurrency and token quotas, fair scheduling (module 2)Logs, traces, backupstenant tag, access control on viewers, retention, erasure registry for offboardingDefence in depth: assume one layer will have a bug, and make sure the next one still holds.
Where tenant data can cross, and the control at each place.

Results

Failure (simulated) Without the control With the control
Filter forgotten (100 endpoints) 60 other-tenant rows 0 (scoped repository)
Tenant id from the model (1,000 injected attempts) 306 leaks 0 (tenant from session)
Shared cache key (4,000 requests) 2,675 wrong-tenant answers 0 (tenant in key)
Small tenant retrieval, post-filter top-5 0.00 to 1.03 of 5 own results 5 of 5 (namespace)
Noisy neighbour: tenant B served 24% 100% (per-tenant quota)
Offboarding with an incomplete registry data left in 2 of 6 stores none (full registry)
Isolation model Strength Cost
Shared tables, scoped queries, RLS relies on code and policy correctness lowest
Schema or namespace per tenant stronger boundaries, per-tenant erasure medium
Database or deployment per tenant strongest, per-tenant keys and regions highest
AAAWgmp1bWIAAAAeanVtZGMycGEAEQAQgAAAqgA4m3EDYzJwYQAAABZcanVtYgAAAEdqdW1kYzJtYQARABCAAACqADibcQN1cm46YzJwYTpjYTk0NzU5OC0xMDU1LTQwZDYtYjdhOS1iYTkyN2QyOGUwZTgAAAADl2p1bWIAAAApanVtZGMyYXMAEQAQgAAAqgA4m3EDYzJwYS5hc3NlcnRpb25zAAAAALxqdW1iAAAARGp1bWRjYm9yABEAEIAAAKoAOJtxE2MycGEuaW5ncmVkaWVudC52MwAAAAAYYzJzaLcw7H4X8ZqbXLPtEBG4GKQAAABwY2JvcqNpZGM6Zm9ybWF0bWltYWdlL3N2Zyt4bWxqaW5zdGFuY2VJRHgseG1wOmlpZDowOTEzMWY3ZS1jNjA1LTRkNjUtOTYwYi01Mjg2YzA1Njg5YTdscmVsYXRpb25zaGlwaHBhcmVudE9mAAAB4mp1bWIAAABBanVtZGNib3IAEQAQgAAAqgA4m3ETYzJwYS5hY3Rpb25zLnYyAAAAABhjMnNoDKWvNsqnxFQwIyxnuFeD8wAAAZljYm9yomdhY3Rpb25zgqJmYWN0aW9ua2MycGEub3BlbmVkanBhcmFtZXRlcnOha2luZ3JlZGllbnRzgaJjdXJseC1zZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmluZ3JlZGllbnQudjNkaGFzaFggcyjRMCLJ5/64HJWYhBr7FFRfRaCJCCfR9PAzrmH83w6kZmFjdGlvbngdY29tLmFudGhyb3BpYy5jbGF1ZGUucHJvdmlkZWRqcGFyYW1ldGVyc6F4H2NvbS5hbnRocm9waWMub3JpZ2luLWNvbmZpZGVuY2VndW5rbm93bmtkZXNjcmlwdGlvbnhmQ2xhdWRlIHByb3ZpZGVkIHRoaXMgZmlsZSBhdCB0aGUgcmVxdWVzdCBvZiBhIHVzZXIgYW5kIG1heSBoYXZlIGNyZWF0ZWQgb3IgbW9kaWZpZWQgdGhlIGZpbGUgY29udGVudHMubXNvZnR3YXJlQWdlbnShZG5hbWVmQ2xhdWRlcmFsbEFjdGlvbnNJbmNsdWRlZPUAAADIanVtYgAAAEBqdW1kY2JvcgARABCAAACqADibcRNjMnBhLmhhc2guZGF0YQAAAAAYYzJzaF1OqaIpQlRTc9ct5N3l3/gAAACAY2JvcqVjYWxnZnNoYTI1NmNwYWRNAAAAAAAAAAAAAAAAAGRoYXNoWCDwB2xtEsKF2exr15NlSxzHoTqM27g87knzfXEtb9eTV2RuYW1lbmp1bWJmIG1hbmlmZXN0amV4Y2x1c2lvbnOBomVzdGFydBidZmxlbmd0aBkeBAAAAj5qdW1iAAAAJ2p1bWRjMmNsABEAEIAAAKoAOJtxA2MycGEuY2xhaW0udjIAAAACD2Nib3KlY2FsZ2ZzaGEyNTZpc2lnbmF0dXJleE1zZWxmI2p1bWJmPS9jMnBhL3VybjpjMnBhOmNhOTQ3NTk4LTEwNTUtNDBkNi1iN2E5LWJhOTI3ZDI4ZTBlOC9jMnBhLnNpZ25hdHVyZWppbnN0YW5jZUlEeCx4bXA6aWlkOjQwMmEzYzkxLTdmZGMtNDVlYy04ZTg2LTFiMjBhMTcwMThiMHJjcmVhdGVkX2Fzc2VydGlvbnODomN1cmx4LXNlbGYjanVtYmY9YzJwYS5hc3NlcnRpb25zL2MycGEuaW5ncmVkaWVudC52M2RoYXNoWCBzKNEwIsnn/rgclZiEGvsUVF9FoIkIJ9H08DOuYfzfDqJjdXJseCpzZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmFjdGlvbnMudjJkaGFzaFgglg/pTYs+owVIMkfUtp+Loqbuhl5eBVGuWE9Ra7mfnYGiY3VybHgpc2VsZiNqdW1iZj1jMnBhLmFzc2VydGlvbnMvYzJwYS5oYXNoLmRhdGFkaGFzaFggmJWGVlpdLzduh2WumM6OBlyfnjxngRAD1Rs9DFYlmvh0Y2xhaW1fZ2VuZXJhdG9yX2luZm+jZG5hbWVvQW50aHJvcGljIEZpbGVzZ3ZlcnNpb25lMS4wLjBrc3BlY1ZlcnNpb25lMi40LjAAABA4anVtYgAAAChqdW1kYzJjcwARABCAAACqADibcQNjMnBhLnNpZ25hdHVyZQAAABAIY2JvctKEWQISogEmGCFZAgowggIGMIIBjaADAgECAhRA5aAK7sI50L64g/oGQgU9Z1UTADAKBggqhkjOPQQDAzBJMRcwFQYDVQQKEw5BbnRocm9waWMsIFBCQzEuMCwGA1UEAxMlQW50aHJvcGljIENvbnRlbnQgQ3JlZGVudGlhbHMgUm9vdCBDQTAeFw0yNjA4MDcxODQzNTZaFw0yODA4MDYxOTQzNTZaMEQxFzAVBgNVBAoTDkFudGhyb3BpYywgUEJDMSkwJwYDVQQDEyBBbnRocm9waWMgQ2xhdWRlIENvbnRlbnQgU2lnbmluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJh6CmvLUBgFFNU0vUKlOVtE6djd17L5SuwX0LemFisBM3dkd/3cyjxFA3Qo5S46fX0/ihY0VZ7mfb9KF703t5OjWDBWMA4GA1UdDwEB/wQEAwIHgDAVBgNVHSUEDjAMBgorBgEEAYPoXgIBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUzlHiBIFOZFsj+OPEz5o+nMHXXMIwCgYIKoZIzj0EAwMDZwAwZAIwMXMdFJ4BetLLVY7ORuE9noqbbAZOZn/aArXyTwFAZfKrPzxF2vPoJNf1+UCdg1XGAjBwX1zd9WGqYkqmL5SFqw1QySjr1zJfpJM9+1rdDwSPLMOPOjKuiXjoU/pUUeG9RwmhY3BhZFkNngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPZYQCR1V0QvDzVgmSbgfXWrkuGeKkYpzobr4pBn2afSZ41n+/Or5CDtHQbl/j1FUlRNAh1iZ1KJlMzWRJSRUCjypuA=Isolation failures in simulation (assumed rates; the point is the mechanism)Filter by convention: 5 teams x 20 endpoints, 5% chance an endpoint forgets the tenant filterby convention: 6 forgetful endpoints of 100, 60 other-tenant rows returned. enforced scope: 0 rowsRetrieval, 20,000 docs over 100 tenants, top-5: own results returned, by tenant sizetenant with 3,901 docs: filter after search 1.03 of 5 (and 4.0 foreign docs fetched) namespace: 5 of 5tenant with 163 docs: filter after search 0.07 of 5 (and 4.9 foreign docs fetched) namespace: 5 of 5tenant with 27 docs: filter after search 0.00 of 5 (and 5.0 foreign docs fetched) namespace: 5 of 5Cache, 4,000 requests from 3 tenants with common promptskey = prompt: 2,675 answers served to the wrong tenant. key = (tenant, prompt): 01,000 injected attempts to read tenant B's data (model obeys 30% of the time, assumed)tenant id as a tool argument: 306 leaks. tenant from the session: 0Noisy neighbour: tenant A floods a shared token budget; share of tenant B's demand servedshared budget: 24%. per-tenant quota: 100%Offboarding: erasing from a registry of 4 stores leaves data in object_files, backups.
Cross-tenant failures with and without controls.
  1. Make the safe path the only path: scoped repositories, namespaces, tenant-keyed caches.
  2. Never accept identity from the model; derive it from the authenticated session.
  3. Isolation is also performance: quotas and fair scheduling.
  4. You cannot delete what you did not register.

Design rules

  • Authenticate first and build an immutable request context (tenant, user, roles) that the platform, not the model, passes to tools.
  • Scope data access through one layer that injects the tenant; forbid unscoped queries; add row-level security as backup.
  • Use namespaces or collections per tenant for vector stores and memory; test recall by tenant size.
  • Key caches by tenant (and user for personalised content); no cross-tenant semantic cache.
  • Fetch per-tenant credentials at call time from a secrets manager; avoid shared broad service accounts.
  • Give each tenant quotas for tokens, concurrency and queue share; schedule fairly (module 2).
  • Isolate execution: separate sandboxes, filesystems and network egress rules per tenant.
  • Tag all logs and traces with the tenant, restrict viewer access, set retention.
  • Keep an erasure registry of all stores; test offboarding end to end.
  • Write cross-tenant tests: two tenants, attempt every read path, with injected instructions; run them in CI.
  • Offer stronger isolation tiers for sensitive customers.

Interview questions, with the follow-ups

Design a customer-support agent platform for many companies. How do you isolate tenants?

"The tenant is derived from authentication and carried in an immutable context; tools never accept it from the model. Data goes through a scoped repository with row-level security behind it, retrieval uses a namespace per tenant, caches are keyed by tenant, credentials are per tenant, and each tenant has token and concurrency quotas with fair scheduling. Traces are tagged by tenant, and an erasure registry covers every store." Follow-up: "What is the biggest agent-specific risk?" A model-controlled argument selecting the tenant: in my simulation it leaked 306 of 1,000 injected attempts; binding the tenant to the session leaked none.

How do you stop a retrieval leak across tenants?

"Filter before search or partition by tenant, and test it. Filtering after a global top-k both exposes other tenants' documents to the process and wrecks recall for small tenants: in my toy index small tenants got almost none of their own results." Follow-up: "Is a metadata filter enough?" Only if it is applied by the platform on every query path and covered by cross-tenant tests; namespaces make the mistake harder.

What is a noisy neighbour and how do you handle it?

"A tenant that consumes shared capacity: tokens, workers or rate limits. I apply per-tenant quotas, concurrency caps, fair scheduling and bulkheads. In my simulation a flooding tenant left another with 24 percent of its demand under a shared budget and 100 percent with per-tenant quotas." Follow-up: "How do you price for it?" Plans with quotas and overage, so heavy use is paid for.

A tenant asks you to delete all their data. How?

"Use the erasure registry: primary store, vector index, caches, traces, files and backups, with a defined backup expiry. Verify with a post-deletion scan and record it in the audit log." Follow-up: "What do you do about backups?" Document the retention window or use per-tenant keys so deleting the key renders the data unreadable.

Common mistakes

  • Tenant id as a tool or API argument from the model or client.
  • Per-endpoint tenant filters instead of an enforced scoped layer.
  • Post-filtering vector results, or a shared index with no tenant tests.
  • Shared caches keyed only by prompt.
  • One shared service account that can read every tenant.
  • No quotas, so one tenant's loop or flood affects all.
  • Traces readable by every engineer, with raw tenant data inside.
  • Offboarding that misses caches, traces and backups.
  • No cross-tenant tests, so isolation is untested folklore.

What the example does not do

  • No real database, RLS, vector store, secrets manager or authentication.
  • Assumed rates: forgetting probability, model obedience, tenant sizes, traffic and budgets.
  • Toy retrieval: random vectors, one query distribution, top-5 only; real stores support pre-filtering and larger candidate pools, which change the numbers.
  • Cache and quota models are simple (no TTLs, no real scheduling).
  • Injection is a coin flip, not a real model being attacked (Level 10 modules 1 and 8 cover that).
  • No per-tenant encryption keys, residency or regional deployment.
  • Much of this is general engineering knowledge, not measured fact: the isolation rules (per-session actor, environment and state isolation, and the rest) are design guidance, and only the toy experiments here were run.
  • Optional real experiment (I did not run this): create two tenants in a staging copy of your platform, seed distinct secrets, and run an automated suite that tries every read path, with injected instructions, to access tenant B from tenant A; record every success as a bug.

Check yourself

A tool takes tenant_id as an argument from the model. What can go wrong? (A prompt injection can make the model ask for another tenant, and the tool trusts it. Take the tenant from the authenticated session instead.)

For the curious Filtering after the search loses results as well as leaking them

Filtering after a global top-5 search left a tenant with 3,901 documents only 1.03 of its own results in 5. A cache keyed by prompt alone served 2,675 of 4,000 answers to the wrong tenant; keying by (tenant, prompt) gave 0. When one tenant flooded a shared budget, another received 24 percent of its demand; a per-tenant quota gave it 100 percent. Toy in-memory models, assumed rates, random vectors.

What you will use daily at work

Practice What it looks like
Get identity from the session Never from a model-supplied argument.
Enforce the tenant scope in the data layer Not by convention in each endpoint.
Use namespaces or pre-filters in retrieval Test recall for small tenants.
Key caches by tenant And register every store for offboarding.

Draft: confirm what your database and vector store provide for row-level security, namespaces and per-tenant keys.

What you must be able to say (one page)

  • Multi-tenancy means many customers share one deployment; isolation means one tenant can never read, change, slow down or learn about another. For agents the risk is higher than in ordinary SaaS because a model decides which tool calls to make, retrieved text can carry injected instructions (Level 10 module 1), and many shared layers (cache, vector index, memory, traces) sit between the user and the data. The design question interviewers ask is "design a customer-support agent platform for many companies" (this level's interview prompt).
  • Where tenant data can cross: the API and session, the database, the vector index and memory, caches, tool credentials, compute and quotas, and logs, traces and backups. Put a control at each place and assume one will have a bug (defence in depth).
  • Identity comes from the session, never from the model. A tool that takes tenant_id as an argument trusts whatever the model passes; a prompt injection can make the model ask for another tenant. In my simulation of 1,000 injected attempts (the model obeys 30 percent of the time, assumed), the argument-scoped tool leaked 306 times; a tool that ignores the argument and uses the tenant from the authenticated session leaked 0, even at 100 percent obedience (tested). This is the confused-deputy problem; it extends Level 10 modules 1, 2 and 5.
  • Enforce the tenant filter, do not rely on convention. With a convention ("every query must pass the tenant"), 5 teams of 20 endpoints at a 5 percent forgetting rate had 6 forgetful endpoints out of 100 and returned 60 other-tenant rows; a scoped repository that injects the tenant from the request context on every query and has no unscoped read path returned 0. Add database row-level security (a policy the database itself enforces) as a second layer.
  • Retrieval: filter before search, or use per-tenant namespaces. Filtering after a global top-k throws away other tenants' hits and leaves little: in my toy index (20,000 documents across 100 tenants with Zipf-like sizes, top-5, random vectors) a tenant with 3,901 documents got 1.03 of its own results in 5, one with 163 documents 0.07, one with 27 documents 0.00, and the other tenants' documents were fetched into memory (4 to 5 of 5), which is also a leak risk if the filter is ever skipped. A namespace per tenant returned 5 of 5 every time. Real vector stores offer pre-filtering or over-fetching; the lesson is to test recall per tenant size.
  • Cache keys must include the tenant (and the user if answers are personal). With 3 tenants asking common prompts, a cache keyed by prompt only served 2,675 of 4,000 answers to the wrong tenant; keyed by (tenant, prompt) 0. Never share semantic caches across tenants (module 5).
  • Compute isolation (noisy neighbour): when tenant A floods a shared token budget, tenant B received 24 percent of its demand; a per-tenant quota (each tenant capped at half the budget) gave B 100 percent. Combine per-tenant quotas, concurrency limits, fair queue scheduling (module 2) and bulkheads (module 6).
  • Credentials and tools: per-tenant secrets fetched at call time from the session's tenant, no shared service account with broad access, and per-tenant tool allow-lists (Level 10 module 2). Sandboxes for code execution must not share filesystems or networks across tenants (Level 10 module 3).
  • Logs, traces and backups hold tenant data too: tag every span with the tenant, restrict who can view which traces, set retention, and keep a registry of every store so that offboarding and erasure requests reach all of them (Level 10 module 6). In my toy, a registry that knew 4 of 6 stores left data in object_files and backups.
  • Isolation levels are a spectrum: shared everything with row-level scoping (cheapest, most reliance on code correctness), schema or namespace per tenant, database or deployment per tenant (strongest, most expensive). Offer stronger isolation for regulated customers; decide by data sensitivity, cost and operations.
  • Honest limits: toy in-memory models, assumed obedience and forgetting rates, random vectors, no real database, index, auth or network.