ibee

Multi-Tenant Storage Architecture for SaaS — Isolation, Billing, and Scale

MohitEngineering team
April 22, 20264 min read

For engineering leads and architects at Indian B2B SaaS companies whose product stores data on behalf of customer organisations.

The Two Multi-Tenant Storage Models

Every B2B SaaS product that stores files must choose between two object storage architectures. Each has a distinct tradeoff profile.

Shared bucket with prefix isolation — all tenants' data lives in one bucket, separated by a key prefix: tenant-abc123/documents/file.pdf. The application enforces isolation through access control: every query that reads or writes objects includes the tenant ID in the key, and the application layer validates that the requesting user's tenant ID matches the key prefix they are accessing.

Bucket per tenant — each tenant has a dedicated bucket: mycompany-tenant-abc123. The bucket boundary enforces isolation at the storage layer. A misconfiguration in the application access control layer cannot expose one tenant's data to another, because the bucket policy prevents it.

Neither model is universally correct. The choice depends on your tenant count, data volume per tenant, compliance requirements, and enterprise client expectations.

Shared Bucket: When It Is Appropriate

A shared bucket with prefix isolation is appropriate when:

The tenant count is in the hundreds and growing (not tens of thousands), average data volume per tenant is modest (under 10 GB), your enterprise clients do not ask for dedicated infrastructure isolation, and your team has the operational capacity to enforce access control discipline at the application layer.

For a typical Indian SaaS product at the early-growth stage — 200 customers, primarily SMB, no explicit data isolation contracts — shared prefix isolation is the simpler choice. One bucket to provision, one set of credentials, straightforward Terraform configuration.

The access control discipline required: every code path that generates presigned URLs, reads objects, or writes objects must include the tenant ID in the key. Code review should flag any S3 operation that does not scope to a tenant prefix. This is enforced through code patterns (a storage service class that always prepends the tenant ID) rather than through bucket policies.

The risk: a bug in the tenant scoping logic could expose one tenant's data to another. In a shared bucket, the storage layer provides no backstop. For customers in regulated sectors (BFSI, healthcare, legal), this risk is not acceptable.

Bucket Per Tenant: When It Is Required

Bucket per tenant is the appropriate choice when:

Any enterprise or regulated client asks about data isolation guarantees, your product handles data in BFSI, healthcare, legal, or government sectors where co-tenancy is a concern, tenant data volumes are large enough to warrant independent lifecycle policies, or you need clean data export and deletion for DPDP Act compliance.

With bucket per tenant, the bucket policy enforces isolation regardless of application code bugs. Even if a logic error causes the application to request an object from another tenant's bucket, the bucket policy denies the request. The storage layer is the backstop.

Enterprise procurement teams at banks, hospitals, and large corporates will ask: "Is our data stored separately from other customers?" The answer with bucket per tenant is "yes, in a dedicated bucket" — unambiguous and documentable. The answer with shared prefix isolation is "yes, but separated by a key prefix in a shared bucket" — which some procurement teams will not accept.

Implementing Bucket Per Tenant on IBEE

Bucket provisioning on tenant signup uses the AWS SDK. Add this to your tenant onboarding workflow:

Store the tenant-to-bucket mapping in your database. Every storage operation retrieves the bucket name from the mapping rather than constructing it from the tenant ID — this allows bucket names to be changed without breaking application code.

Per-Tenant Storage Billing

With bucket per tenant, per-tenant storage billing is straightforward. Use CloudWatch metrics (or IBEE's equivalent usage API) to query the storage usage for each tenant's bucket:

Run this query monthly for each active tenant. The storage usage in GB, multiplied by your per-GB billing rate, is the tenant's storage charge for the month. This billing is directly attributable and auditable — no estimation or allocation required.

Tenant Offboarding and Data Export

Offboarding a tenant with bucket per tenant is a clean, auditable operation:

Export all data from the tenant's bucket to a compressed archive (using rclone) and make it available to the tenant for download, satisfying data export obligations. Retain the bucket for the contractual data retention period (typically 30–90 days after contract end). At retention period end, delete the bucket and all objects. Record the deletion timestamp in the audit log.

With shared prefix isolation, the offboarding process requires carefully deleting all objects under the tenant's prefix — a more complex operation with higher risk of accidentally deleting objects from neighbouring prefixes.

Related articles