Shared Database
Multi-Tenant Shared Database stores all tenants' data in one physical database cluster, discriminating rows by a tenant_id column (or equivalent).
Introduction
Multi-Tenant Shared Database stores all tenants' data in one physical database cluster, discriminating rows by a tenant_id column (or equivalent). Google Workspace, early Salesforce, and many SaaS platforms start here for operational simplicity — one backup, one migration, one capacity plan — but isolation becomes entirely application-enforced.
Real production story
A Google Workspace team ran all customer documents in a single Cloud Spanner database with org_id on every row. Operational wins were real: one schema migration, unified monitoring, and 40% lower infra cost vs per-org databases at 50K orgs scale. Then an internal admin tool shipped without WHERE org_id = ? on a support query — two enterprise customers saw each other's folder names in a debug panel for 11 minutes before rollback.
The post-incident program didn't abandon shared database — it hardened it: mandatory row-level security policies, ORM middleware that injects tenant predicate on every query, integration tests that fail if any SQL lacks tenant filter, and audit logs on cross-tenant access attempts. Shared database remained the model through 5M orgs with zero repeat incidents in 3 years.
Business problem
Business pressure: Google Workspace must onboard thousands of new organizations daily without provisioning a new database per signup. Shared database minimizes ops overhead but demands rigorous tenant isolation in application and database layers.
- Revenue at risk: Cross-tenant data leak terminates enterprise contracts and triggers regulatory investigation — one bug is existential.
- Engineering velocity: Single schema migration updates all tenants simultaneously — fast feature rollout but high blast radius on migration bugs.
- Compliance / trust: SOC2, HIPAA BAA, and FedRAMP require demonstrable isolation controls — "we filter in app code" needs RLS and audit evidence.
Architecture overview
Shared Database multi-tenancy in production: one database instance, shared schema, every table carries tenant_id, every query filtered by tenant context from authenticated session — never from client-supplied parameter alone.
- Definition: All tenants share one physical database; logical isolation via tenant discriminator column and access controls.
- When to adopt: High tenant count, similar schema needs, strong platform team for isolation guardrails, cost-sensitive scale.
- When to defer: Enterprise deals requiring physical isolation, regulatory mandates for separate storage, or noisy-neighbor SLAs per tenant.
- Operability: Per-tenant query metrics, RLS policy audit, synthetic cross-tenant penetration tests in CI.
Architecture motivation
Why architects care: Shared database optimizes cost and operational simplicity at the expense of isolation risk. The naive "we'll always remember tenant_id" fails at scale — Google treats isolation as defense in depth: app middleware + RLS + audit.
- Force: Tenant count exceeds per-DB provisioning economics (10K+ tenants).
- Constraint: Cannot afford N databases × backup × migration × on-call for N tenants.
- Outcome: Single fleet with tenant_id on all tables, RLS as safety net, automated tenant filter lint in CI.
Internal architecture
Google Workspace shared database topology:
Tenant A ──┐Tenant B ──┼──▶ API Gateway (JWT → org_id claim)Tenant C ──┘ ↓TenantContext (request-scoped)↓Repository layer (auto WHERE org_id = ?)↓Cloud Spanner (shared instance)┌─────────────────────────────┐│ documents(org_id, doc_id,…) ││ RLS: org_id = session_org() │└─────────────────────────────┘
Data flow
Read path: JWT validated → org_id extracted → set in TenantContext → repository generates SELECT ... WHERE org_id = @ctx → Spanner RLS double-checks → response scoped to tenant.
- Write path: org_id set from context on INSERT — never from request body field.
- Read path: connection pool shared; no per-tenant connections needed.
- Async path: background jobs carry tenant_id in message envelope; worker sets context before DB access.
class TenantContext {private static als = new AsyncLocalStorage<{ orgId: string }>();static run<T>(orgId: string, fn: () => T): T {return this.als.run({ orgId }, fn);}static get orgId(): string {const ctx = this.als.getStore();if (!ctx?.orgId) throw new Error("Missing tenant context — query blocked");return ctx.orgId;}}class DocumentRepository {async list(): Promise<Document[]> {const orgId = TenantContext.orgId;return db.query("SELECT * FROM documents WHERE org_id = @orgId",{ orgId },);}}
System design diagram
Two diagrams show the Shared Database topology and the primary request/event path used in production at scale.
Production code example
Tenant-safe repository base class with CI hook for SQL audit:
abstract class TenantScopedRepository<T> {protected abstract table: string;protected baseQuery(): QueryBuilder {return db.from(this.table).where("org_id", TenantContext.orgId);}async findById(id: string): Promise<T | null> {return this.baseQuery().andWhere("id", id).first();}}// CI: sql-lint fails on raw queries missing org_id on tenant tables// scripts/lint-tenant-queries.ts scans ORM + raw SQL in PR diff
Enterprise case study
Google Workspace — shared Spanner at 5M orgs: TenantContext middleware, RLS policies, CI tenant filter lint, and quarterly red-team cross-tenant tests.
- Before: 11-minute cross-tenant leak in admin tool; enterprise escalations.
- Decision: Defense in depth — app + RLS + CI lint + audit; keep shared DB for economics.
- After: Zero cross-tenant incidents in 3 years; 40% lower infra cost vs per-org DB at current scale.
Trade-offs
- Cost vs isolation risk: Lowest $/tenant — highest blast radius on missing filter bug.
- Migration simplicity vs noisy neighbor: One bad query can starve all tenants — need query quotas and per-tenant rate limits.
- Uniform schema vs customization: All tenants share schema — hard to offer tenant-specific columns without EAV or extension tables.
Security considerations
Security is architectural: Shared database means one SQL injection or ORM bug can cross tenant boundaries — treat tenant context as security boundary.
- Identity: org_id from signed JWT only — never trust query param or header from client without signature.
- Data: RLS as defense in depth; encrypt sensitive columns; per-tenant KMS keys optional for enterprise tier.
- Supply chain: ORM and query builder versions pinned; SQL linter in CI for all services touching shared DB.
Scalability analysis
Scale dimensions: Google plans for 5M orgs, petabyte storage, and hot orgs 1000× larger than median — shared DB needs sharding strategy before single instance limits.
- Horizontal scale: Spanner sharding by org_id range; rebalancing hot orgs to dedicated split.
- Hot spots: Mega-enterprise org dominates read QPS — read replica routing or cache partition per hot org_id.
- Cost: Shared fleet amortizes storage; monitor $/org/month and alert when hot tenant exceeds fair share.
Failure scenarios
What breaks: Missing tenant filter in new endpoint; RLS disabled during migration; background job without tenant context writes cross-tenant.
- Missing filter: CI SQL linter fails build if query touches tenant table without org_id predicate.
- RLS bypass: Admin migrations use break-glass role with full audit — never disable RLS in prod without ticket.
- Job without context: Message schema requires tenant_id; worker throws if absent.
Staff engineer insights
- If your isolation strategy is "developers remember tenant_id," you will leak — automate enforcement.
- RLS is the seatbelt, not the steering wheel — app layer must set context correctly; RLS catches mistakes.
- Plan sharding exit ramp before single Spanner instance hits limits — org_id range split is easier early.
Interview questions
Interview Prep
Practice concise answers, then expand each card for the explanation.
1AdvancedQuestionWhy would Google choose shared database over separate database per org at 5M orgs?+
Answer
Follow-up
2AdvancedQuestionDesign defense in depth for tenant isolation in a shared Spanner database.+
Answer
Follow-up
3AdvancedQuestionA hot enterprise tenant is causing noisy neighbor latency for smaller tenants. Options?+
Answer
Follow-up
Architecture review questions
- Are quality attributes (latency, availability, consistency) explicit with SLOs for Multi-Tenant Shared Database?
- Is the failure/degraded mode documented — including what happens when dependencies are down?
- Are boundaries and ownership clear on an architecture diagram a new engineer understands in 10 minutes?
- Is there an ADR capturing alternatives considered and why they were rejected?
- Can this design scale 10× on traffic and 3× on engineering headcount without a rewrite?
- Security: authn/authz, encryption, and blast radius reviewed at every external interface?
Summary
Multi-Tenant Shared Database at Google scale means one fleet, tenant_id everywhere, and isolation enforced by multiple layers — not developer discipline alone. Master the economics, wire automated guardrails, and plan the sharding exit ramp.