# Design tenant routing before database sharding

> Define a versioned tenant placement record and request-scoped routing before sharding so migrations, cache invalidation, and rollback stay controlled.

Canonical URL: https://www.devobs.io/articles/tenant-routing-before-sharding/
By: Owen Park
Published: 2025-08-22T14:58:14.387Z
Updated: 2026-09-06T08:31:04.426Z
Section: Architecture

Before creating a second shard, define how every request resolves a tenant to one authoritative placement. Treat placement as versioned control-plane state, carry the tenant identity through the request, and make migration a fenced state transition rather than a configuration edit.

## Make placement explicit

A placement record can contain tenant ID, shard ID, placement version, lifecycle state, schema version, residency class, and last change. Use a stable tenant identifier from authenticated context, not a free-form request parameter. The router looks up that identifier and creates a request-scoped database handle that application code cannot silently replace.

Microsoft’s [multitenant storage guidance](https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/approaches/storage-data) describes shared and more isolated data layouts for multitenant systems, and it emphasizes planning for scale, data isolation, implementation complexity, and operational complexity up front. Those tradeoffs are exactly why tenant placement should be explicit before you introduce sharding.

Do not spread shard lookup across repositories. One routing layer should validate tenant identity, placement version, connection target, and schema compatibility. Data access methods should require the routed context, and queries in shared shards should still include tenant predicates or database-enforced isolation.

## Cache without losing control

Placement reads need to be fast, so cache them with a bounded TTL and active invalidation. Include the placement version in cache entries, connection metadata, jobs, and write requests. During migration, a stale client presenting version 18 to a tenant now at version 19 must be redirected, refreshed, or rejected before writing.

Negative caching needs care during onboarding. A long “tenant not found” entry can hide a newly created tenant. Separate TTLs for absent and active records and invalidate both after control-plane changes.

The [AWS Well-Architected SaaS Lens](https://docs.aws.amazon.com/wellarchitected/latest/saas-lens/saas-lens.html) focuses SaaS architecture guidance on the Well-Architected pillars and on design and operational best practices for multitenant workloads. In a sharded multitenant system, tenant routing affects those concerns directly because it shapes isolation boundaries, failure domains, and tenant-scoped observability.

## Migrate with a fence

Use a state machine such as active-source, copying, draining, switched, verifying, and complete. First create the destination at a compatible schema and copy a consistent snapshot. Capture subsequent changes through a log, dual write, or a short write pause.

Before switching, establish a fence that prevents the old shard from accepting writes for the next placement version. Drain in-flight transactions, apply remaining changes, atomically update the placement record, invalidate caches, and enable destination writes. Readers can use the source during copying, but after the switch they must not mix old and new states casually.

Verification compares row counts, checksums for stable partitions, critical invariants, and representative application reads. Keep the source read-only for a defined [rollback window](https://www.devobs.io/articles/rollback-compatible-release-design/). Rollback reverses the placement only if no incompatible destination-only changes have occurred; otherwise use a forward repair.

## Route work beyond HTTP

Queue messages must carry tenant ID and placement version, not a database hostname captured forever. A worker resolves current placement before each durable batch. Scheduled tasks, exports, search indexing, analytics, and webhooks need the same rule.

Transactions cannot span tenant migration and arbitrary background work safely. Pause tenant-specific jobs or make them idempotent and version-aware. Signed download URLs should reference logical objects, not old-shard storage paths that bypass routing.

Log tenant ID, resolved shard, placement version, cache source, request ID, and rejection reason without exposing connection credentials. Alert on mismatched versions, cross-tenant query failures, repeated reroutes, and traffic reaching a fenced source.

## Prove isolation and recovery

Test a stale router, delayed invalidation, worker retry, concurrent migration, destination failure before switch, destination failure after switch, and rollback. Add a sentinel tenant row on each shard and verify a request cannot read a sentinel belonging to another tenant.

Create the placement table and routing interface before physical sharding. Move one synthetic tenant between two development databases, inject stale cache entries, and require every write to land on exactly one authoritative shard.

*Review date: 2026-09-06*

## Source references

- <https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/approaches/storage-data>
- <https://docs.aws.amazon.com/wellarchitected/latest/saas-lens/saas-lens.html>
