Skip to content

feat(tenant): add opt-in multi-tenancy with per-tenant key isolation - #2325

Open
ErfanMomeniii wants to merge 2 commits into
dgraph-io:mainfrom
ErfanMomeniii:add-tenant
Open

feat(tenant): add opt-in multi-tenancy with per-tenant key isolation#2325
ErfanMomeniii wants to merge 2 commits into
dgraph-io:mainfrom
ErfanMomeniii:add-tenant

Conversation

@ErfanMomeniii

Copy link
Copy Markdown
Contributor

Description

This PR adds optional multi-tenancy to Badger so a single DB can serve many tenants (customers, workspaces, environments) while keeping their data isolated. Today the usual workaround is to run a separate Badger instance per tenant, which gets expensive on file descriptors and memory once you have more than a handful. This lets you keep one instance and give each tenant its own isolated keyspace instead.

The feature is off by default, so nothing changes for existing users unless they opt in with WithMultiTenancy(true).

Here's the gist of how it's used:

db, _ := badger.Open(badger.DefaultOptions(dir).WithMultiTenancy(true))

acme, _ := db.Tenants().Create("acme")   // register a tenant
scope, _ := db.TenantScope(acme.ID)       // get a namespaced view

scope.Set([]byte("user:1"), []byte("alice"))
val, _ := scope.Get([]byte("user:1"))

Under the hood each tenant gets a unique 8-byte namespace that Badger puts in front of its keys, so you keep writing plain keys like user:1 and never have to think about the prefix. Two tenants can use the exact same key without colliding.

There are two entry points once it's enabled:

  • db.Tenants() manages the tenants themselves — create, look up by id or name, list, ban and unban, delete, and purge. Tenant records live in the same DB and come back after a restart. Delete just deregisters the tenant (its data stays around for offline cleanup), while Purge also drops all of its keys to free up disk.
  • db.TenantScope(id) is what you use for the tenant's data. Besides the one-shot Set/Get/Delete shown above, it has Update/View when you need a real transaction (atomic multi-key writes, read-modify-write), an iterator that only sees that tenant's keys, and NewWriteBatch for bulk loading.

Isolation is enforced on writes: with multi-tenancy on, any write to a namespace that isn't a registered tenant is rejected with ErrUnknownTenant. Reads are isolated by going through TenantScope, which confines everything to that tenant's range.

A couple of things worth calling out:

  • Turning on multi-tenancy pins NamespaceOffset to 0, since the scoped iterator relies on the tenant id being a plain key prefix. If you'd already set a non-zero offset explicitly, Open returns an error rather than silently overriding it.
  • Managed mode (OpenManaged) can use the tenant registry, but not TenantScope — scoped operations assign their own timestamps, which clashes with the user-supplied timestamps managed mode expects. Calling TenantScope there returns ErrTenantScopeManaged.

Backward compatibility was the main constraint. The option defaults to false, the enforcement checks short-circuit when it's off, and legacy plain-key databases stay fully readable after enabling it (only new writes are gated). The existing BanNamespace API and the SuperFlag round-trip are untouched.

There's an end-user guide in docs/multi-tenancy.md and a short section in the README.

Testing

Added 30 tests in tenant_test.go covering the tenant lifecycle, cross-tenant isolation, restart persistence, concurrent creates racing on the same and on distinct names, some key layout edge cases (short/3-char names, empty logical keys), managed-mode behavior, and the backward-compatibility paths. The full package suite passes and the tenant tests are clean under -race.

Checklist

  • Code compiles correctly and linting passes locally
  • Tests added for new functionality, or regression tests for bug fixes added as applicable

@ErfanMomeniii
ErfanMomeniii requested a review from a team as a code owner July 23, 2026 17:30
@ErfanMomeniii

Copy link
Copy Markdown
Contributor Author

Hi @matthewmcneely, could you please review?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant