Derek Comartin argues that multi-tenancy "best practices" (don't pass the tenant ID around, use ambient context, use global query filters) aren't wrong, but they're usually stated without their trade-offs. Every fix just relocates complexity: an ambient tenant context breaks when you cross from HTTP to a background message processor; automatic query filtering forces you to build a dangerous cross-tenant escape hatch; per-tenant feature flags eventually turn into modules and deployment complexity. The better question isn't "what's the right pattern" but "where does the tenant ID come from, and where should it be explicit vs. implicit?"
- The opening failure mode is a silent one: an HTTP API queues a message, a background job processes it, nothing errors, but the work executes for tenant B instead of tenant A because the ambient tenant context didn't carry across process boundaries.
- An ambient tenant context solves the pain of passing the tenant ID around, but creates a new problem: the context has to be rebuilt every time you cross from one execution context (HTTP) to another (message handler, background task).
- Passing the tenant ID explicitly everywhere is noisy and repetitive, but it's visible in logs, requests, messages, and the database — the noise is the point.
- Automatic isolation in a shared database (global query filters, row-level security) is a safe default with hidden behavior, and real systems still need intentional cross-tenant queries for reporting, admin, and analytics — which forces an escape hatch that is itself dangerous.
- With a database per tenant there's no line to cross, but cross-tenant reporting requires querying each database individually or aggregating into a separate global database.
- Per-tenant features often start correctly as configuration or feature flags, then evolve into modules with their own implementation, versioning, and deployment — trading conditional complexity for structural complexity.
- The recurring pattern across all examples: complexity is never removed, only moved, which is why blanket rules about multi-tenancy ignore the actual question of what you're trading.
- The useful questions to ask are: where does the tenant ID originate, where must it be explicit (visible), and where is it safe for it to be implicit (convenient)?
A concrete, silent failure that shows exactly how an ambient tenant context breaks when work crosses from HTTP into a background queue processor.
Everything succeeded. There were no exceptions, no failed database calls, no errors in your logs. However, something went horribly wrong... that request was supposed to execute for tenant A, but in reality what actually happened is it executed for tenant B.
The clearest worked example of the video's thesis: the safe default forces you to build a dangerous exception you now have to reason about on every query.
We created this global query filter, row-level security, so we didn't have to think about filtering. It was automatic and transparent to us. But it created the problem of we actually did in places need to cross tenant lines.
We didn't remove identity. We just hid it.
You're not removing complexity, you're just moving it.
That's why I despise the idea that there's a right way of doing multi-tenancy... Those statements ignore one thing: what are you trading?
When it's explicit, it's visible. When it's implicit, it's just convenient.