245 lines
14 KiB
Markdown
245 lines
14 KiB
Markdown
|
|
# Plane P — Placement: prior knowledge and best practices
|
|||
|
|
|
|||
|
|
> Research artefact — 2026-08-17. Supports `canon/architecture/adr-008-multi-tenancy-model.md`
|
|||
|
|
> (draft-2), plane P of four. Surveys external practice for which substrate
|
|||
|
|
> holds a tenant's data, and holds our `P0–P4` ladder against it.
|
|||
|
|
> **Contains strong external validation of draft-2's reframe, and one
|
|||
|
|
> expressiveness failure in the P ladder itself.**
|
|||
|
|
> Companions: plane I (identity), plane A (authorization), plane E (enforcement).
|
|||
|
|
|
|||
|
|
## 1. What the plane covers
|
|||
|
|
|
|||
|
|
Which physical substrate holds a tenant's data, who decides, and what moving
|
|||
|
|
costs. Our ladder:
|
|||
|
|
|
|||
|
|
| Level | Shape |
|
|||
|
|
|---|---|
|
|||
|
|
| P0 | Shares a database with another consumer |
|
|||
|
|
| P1 | Database per consumer, shared cluster |
|
|||
|
|
| P2 | Dedicated cluster per consumer |
|
|||
|
|
| P3 | Dedicated cluster per tenant |
|
|||
|
|
| P4 | P3 plus separate region or jurisdiction |
|
|||
|
|
|
|||
|
|
## 2. Validation: "isolation is a spectrum, not a discrete property"
|
|||
|
|
|
|||
|
|
The operator's instruction to reframe draft-1 from a fixed model into a
|
|||
|
|
graduated framework is not merely defensible — it is Microsoft's stated
|
|||
|
|
position, in almost the same words:
|
|||
|
|
|
|||
|
|
> "Instead of viewing isolation as a discrete property, consider it a spectrum.
|
|||
|
|
> You can deploy components of your architecture that are more isolated or less
|
|||
|
|
> isolated than other components in the same architecture, depending on your
|
|||
|
|
> requirements."
|
|||
|
|
> — [Azure Architecture Center — Tenancy models for a multitenant solution](https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/considerations/tenancy-models)
|
|||
|
|
|
|||
|
|
The same page decomposes isolation across tiers exactly as our planes do —
|
|||
|
|
"you can use different levels of isolation for each tier", with a shared UI
|
|||
|
|
tier, a shared middle tier, and isolated databases given as the worked example.
|
|||
|
|
|
|||
|
|
**Against our model:** draft-2's core move is externally corroborated by the
|
|||
|
|
most rigorous public taxonomy available. Worth citing in ADR §15, because it
|
|||
|
|
converts "we decided to do it this way" into "this matches the reference
|
|||
|
|
guidance", which matters for reviewers who will ask whether we invented a
|
|||
|
|
private vocabulary.
|
|||
|
|
|
|||
|
|
## 3. Validation: our E↔P coupling claim is independently confirmed
|
|||
|
|
|
|||
|
|
ADR §3.2 asserts that the planes couple at the top — `E4` is only reachable at
|
|||
|
|
`P3` or above — and §4.4 asserts that `P0 → P1 → P2` does not raise the E
|
|||
|
|
level. Microsoft states the mechanism plainly:
|
|||
|
|
|
|||
|
|
> "A key difference between tenants and deployments is how isolation is
|
|||
|
|
> enforced. When multiple tenants share a single deployment (a set of
|
|||
|
|
> infrastructure), you typically rely on your application code and a tenant
|
|||
|
|
> identifier that's in a database to keep each tenant's data separate. When
|
|||
|
|
> tenants have their own dedicated deployments, they have their own
|
|||
|
|
> infrastructure, so it might be less important for your code to account for a
|
|||
|
|
> multitenant environment." (ibid.)
|
|||
|
|
|
|||
|
|
That is our E/P relationship derived independently: shared deployment ⇒
|
|||
|
|
enforcement is application code (our E1–E3); dedicated deployment ⇒ enforcement
|
|||
|
|
is structural (our E4).
|
|||
|
|
|
|||
|
|
## 4. Finding: our P ladder cannot express per-tenant placement, and our own commercial section requires it
|
|||
|
|
|
|||
|
|
This is the significant gap.
|
|||
|
|
|
|||
|
|
Azure names two partitioning axes that our single ladder collapses:
|
|||
|
|
|
|||
|
|
- **Vertically partitioned deployments** — "a combination of single-tenant and
|
|||
|
|
multitenant deployments. For example, you might have most of your customers'
|
|||
|
|
data and application tiers on multitenant infrastructures, but you deploy
|
|||
|
|
single-tenant infrastructures for customers who require higher performance or
|
|||
|
|
data isolation." Explicitly monetisable: "You can even charge customers a
|
|||
|
|
higher rate to use a single-tenant deployment." (ibid.)
|
|||
|
|
- **Horizontally partitioned deployments** — shared application tier, dedicated
|
|||
|
|
databases per tenant, targeted at the component that carries the load: "if
|
|||
|
|
you identify that specific components cause most of the load on your system,
|
|||
|
|
then you can deploy separate components for each tenant." (ibid.)
|
|||
|
|
|
|||
|
|
Our P ladder assigns **one level per service**. `tenant-engine` is `P1`.
|
|||
|
|
`audit-core` is `P1`. There is no way to say *"P1 for most tenants, P3 for the
|
|||
|
|
three enterprise tenants who bought isolation"* — which is precisely the
|
|||
|
|
vertically partitioned model, and precisely what ADR §11's minimum-level plan
|
|||
|
|
tiers imply.
|
|||
|
|
|
|||
|
|
The contradiction is internal to draft-2: §11.1 says "a tier may require
|
|||
|
|
`E3 P2`", but if only some tenants hold that tier, the delivering service
|
|||
|
|
occupies two P levels at once. The posture vector as specified cannot represent
|
|||
|
|
that state, so a service in it would have to either overclaim (declare P2 for
|
|||
|
|
everyone) or underclaim (declare P1 and misrepresent what the tier delivers).
|
|||
|
|
Both are §6 violations.
|
|||
|
|
|
|||
|
|
**Proposed amendment.** Either:
|
|||
|
|
|
|||
|
|
1. **Make P a per-tenant-class level rather than a per-service one** — the
|
|||
|
|
vector carries a default plus exceptions:
|
|||
|
|
```yaml
|
|||
|
|
tenancy:
|
|||
|
|
current: { I: 2, A: 3, E: 2, P: 1 }
|
|||
|
|
placement_exceptions:
|
|||
|
|
- tenants: ["tenant:enterprise:*"]
|
|||
|
|
P: 3
|
|||
|
|
reason: "isolation tier"
|
|||
|
|
```
|
|||
|
|
2. Or **declare per-tenant placement out of scope** for the framework and say
|
|||
|
|
so, accepting that plan tiers selling isolation are then unmodellable.
|
|||
|
|
|
|||
|
|
Option 1 is more work and is the honest one, because the estate has already
|
|||
|
|
anticipated tiered isolation. Azure also flags the cost: "your codebase needs
|
|||
|
|
to be designed to support both multitenant and single-tenant deployments", and
|
|||
|
|
migration between them must be planned for (ibid.) — which is exactly what our
|
|||
|
|
§7 portability rules already provide, so we are better positioned for this than
|
|||
|
|
most.
|
|||
|
|
|
|||
|
|
## 5. Cell-based architecture: the pattern our ladder skips
|
|||
|
|
|
|||
|
|
ADR §18.8 asks whether to evaluate cells. The literature gives enough to
|
|||
|
|
sharpen the question.
|
|||
|
|
|
|||
|
|
- A cell is "a complete, independent instance of the service" with **a fixed
|
|||
|
|
maximum size** — compute, storage, supporting services and configuration for
|
|||
|
|
a defined blast radius
|
|||
|
|
([Rackspace — cell-based architecture on AWS](https://www.rackspace.com/blog/cell-based-architecture-aws),
|
|||
|
|
[AWS guidance for cell-based architecture](https://github.com/aws-solutions-library-samples/guidance-for-cell-based-architecture-on-aws)).
|
|||
|
|
- Cells are logical partitions on a **partition key** — customer ID ranges or
|
|||
|
|
tenant ID — and "the data required for or generated by each cell is also
|
|||
|
|
partitioned, so that there is no replication of data between cells" (ibid.).
|
|||
|
|
- The sizing argument is the point: "by capping the size of each cell… you can
|
|||
|
|
potentially predict and address failures more easily. This leads to a higher
|
|||
|
|
MTBF" (ibid.). And "it is important to identify the proper partition key to
|
|||
|
|
minimize cross-cell interactions" (ibid.).
|
|||
|
|
- Cells are the bulkhead pattern applied to tenancy — AWS files it under
|
|||
|
|
fault isolation, REL10-BP04
|
|||
|
|
([AWS Well-Architected — use bulkhead architectures to limit scope of impact](https://docs.aws.amazon.com/en_us/wellarchitected/2022-03-31/framework/rel_fault_isolation_use_bulkhead.html)).
|
|||
|
|
- Azure's equivalent is the **Deployment Stamps pattern**: "each stamp serves a
|
|||
|
|
predefined number of tenants, allowing you to scale almost linearly and serve
|
|||
|
|
increasing numbers of tenants"
|
|||
|
|
([Azure — Deployment Stamps pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/deployment-stamp)).
|
|||
|
|
Azure also calls deployments "supertenants or stamps" and describes the
|
|||
|
|
tenant→deployment mapping table as a first-class component.
|
|||
|
|
|
|||
|
|
**Against our model:** a cell is not a rung on our P ladder — it is a different
|
|||
|
|
axis. P measures *how much is shared*; a cell measures *how large a shared unit
|
|||
|
|
is allowed to get before you make another one*. Our `platform-pg` is, in cell
|
|||
|
|
terms, an uncapped cell: §16 of the ADR computes a ceiling of roughly six
|
|||
|
|
consumers but nothing enforces it, and there is no second cell to overflow
|
|||
|
|
into.
|
|||
|
|
|
|||
|
|
That reframes §18.8 usefully. The question is not "should we adopt cells" but
|
|||
|
|
"**what is `platform-pg`'s declared maximum size, and what happens at the
|
|||
|
|
boundary?**" — a question we can answer now, cheaply, and which the connection
|
|||
|
|
ceiling already forces.
|
|||
|
|
|
|||
|
|
Azure also names **bin packing** as the assignment strategy and stresses
|
|||
|
|
maintaining "a record of your customers and the infrastructure where their data
|
|||
|
|
and applications reside so that you can route their traffic to the correct
|
|||
|
|
location" — a tenant→deployment mapping table. We have no such record; the
|
|||
|
|
placement facts in ADR §1 were assembled by inspection.
|
|||
|
|
|
|||
|
|
## 6. Placement is a commercial decision, and the guidance says so first
|
|||
|
|
|
|||
|
|
Our §11 treats commercial expression as a constraint to be managed. Azure puts
|
|||
|
|
it the other way round:
|
|||
|
|
|
|||
|
|
> "Selecting a tenancy model isn't only a technical decision. It's also a
|
|||
|
|
> commercial decision." — considering business objectives, compliance, scale,
|
|||
|
|
> automation capacity, and SLAs (ibid.).
|
|||
|
|
|
|||
|
|
And on the economics, matching our §14 rejection of R3/P3-everywhere: "if a
|
|||
|
|
single tenant requires a specific infrastructure cost, 100 tenants probably
|
|||
|
|
require 100 times that cost", with ongoing maintenance time-consuming at scale
|
|||
|
|
(ibid.). Conversely single-tenant is appropriate "if you expect that your
|
|||
|
|
business will have only a few customers… even though it's more costly".
|
|||
|
|
|
|||
|
|
**Against our model:** ADR §8.2 proposes placement ownership to
|
|||
|
|
`railiance-platform`, an operations-shaped repo. This guidance suggests
|
|||
|
|
placement policy is *jointly* commercial and technical — which strengthens the
|
|||
|
|
case that `adaptive-pricing` must be a co-signer on §18.2, not only on §18.5.
|
|||
|
|
|
|||
|
|
## 7. Our tenant taxonomy is a recognised hard case
|
|||
|
|
|
|||
|
|
Azure's B2C guidance describes our exact situation:
|
|||
|
|
|
|||
|
|
> "If your customers are consumers, it's often more complicated to relate
|
|||
|
|
> customers, tenants, and users… consider whether your solution might be used
|
|||
|
|
> by families, groups of friends, clubs, associations, or other groups that
|
|||
|
|
> might need to access and manage their data together. For example, a music
|
|||
|
|
> streaming service might support both individual users and families, and it
|
|||
|
|
> might treat each of these account types differently when it separates them
|
|||
|
|
> into tenants." (ibid.)
|
|||
|
|
|
|||
|
|
Our ADR-0013 taxonomy contains `consumer`, `family`, `community`, and
|
|||
|
|
`association` verbatim. It also notes that if tenants are individuals, "you
|
|||
|
|
might need to consider how you handle personal data and about the data
|
|||
|
|
sovereignty laws in each jurisdiction" — which is our `P4` and, combined with
|
|||
|
|
plane A §6, the tenant-deletion gap.
|
|||
|
|
|
|||
|
|
## 8. Testing placement is a named obligation
|
|||
|
|
|
|||
|
|
> "Whichever isolation model you choose, be sure to test your solution to verify
|
|||
|
|
> that one tenant's data isn't accidentally leaked to another **and that any
|
|||
|
|
> noisy neighbor outcomes are acceptable**." (ibid.)
|
|||
|
|
|
|||
|
|
Two evidence artifacts, not one. ADR §13 covers the leak half (P1–P4:
|
|||
|
|
"provisioning declaration plus the platform's own isolation probes") and says
|
|||
|
|
nothing about noisy-neighbour behaviour. Azure suggests deliberate fault
|
|||
|
|
injection (Chaos Studio) to verify resilience.
|
|||
|
|
|
|||
|
|
**Proposed amendment to §13:** add a noisy-neighbour evidence artifact for P1
|
|||
|
|
and P2 — a demonstration that one consumer saturating its connection or CPU
|
|||
|
|
allowance does not breach another's. `rapp-postgres` already sets
|
|||
|
|
database-scoped `connection_limit`, `statement_timeout` and
|
|||
|
|
`idle_in_transaction_session_timeout` precisely for this, and ADR-0001 §3 argues
|
|||
|
|
resource exhaustion is an isolation failure — but nothing proves the limits
|
|||
|
|
hold under load.
|
|||
|
|
|
|||
|
|
## 9. Summary against our ladder
|
|||
|
|
|
|||
|
|
| Finding | Verdict |
|
|||
|
|
|---|---|
|
|||
|
|
| "Isolation is a spectrum, not a discrete property"; different tiers at different levels | **Validates draft-2.** Cite in §15. |
|
|||
|
|
| Shared deployment ⇒ app-code enforcement; dedicated ⇒ structural | **Confirms E↔P coupling** independently. |
|
|||
|
|
| Vertical partitioning: some tenants pooled, some dedicated, explicitly monetised | **Gap.** P is per-service; cannot express per-tenant placement that §11 requires. |
|
|||
|
|
| Horizontal partitioning: shared app tier, per-tenant DB for the hot component | **Unmodelled.** Our P assumes whole-service granularity. |
|
|||
|
|
| A cell has a **fixed maximum size**; stamps scale near-linearly | **Reframes §18.8.** Ask "what is `platform-pg`'s declared max size?", not "should we adopt cells". |
|
|||
|
|
| Tenant→deployment mapping table is a first-class component | **Missing.** Our placement facts were assembled by inspection. |
|
|||
|
|
| Tenancy model is a commercial decision as much as technical | **Amend §8.2.** `adaptive-pricing` should co-sign placement ownership. |
|
|||
|
|
| B2C/family/association tenancy is a recognised hard case | Our taxonomy hits it verbatim; jurisdiction concerns feed P4. |
|
|||
|
|
| Test for leakage **and** for acceptable noisy-neighbour outcomes | **Amend §13.** No noisy-neighbour evidence artifact exists. |
|
|||
|
|
|
|||
|
|
## Sources
|
|||
|
|
|
|||
|
|
- [Azure Architecture Center — Tenancy models for a multitenant solution](https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/considerations/tenancy-models)
|
|||
|
|
- [Azure Architecture Center — Architectural approaches for a multitenant solution](https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/approaches/overview)
|
|||
|
|
- [Azure Architecture Center — Deployment Stamps pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/deployment-stamp)
|
|||
|
|
- [Azure Architecture Center — Considerations for updating a multitenant solution](https://learn.microsoft.com/en-us/azure/architecture/guide/multitenant/considerations/updates)
|
|||
|
|
- [AWS Well-Architected — REL10-BP04 Use bulkhead architectures to limit scope of impact](https://docs.aws.amazon.com/en_us/wellarchitected/2022-03-31/framework/rel_fault_isolation_use_bulkhead.html)
|
|||
|
|
- [AWS Well-Architected — How do you use fault isolation to protect your workload?](https://wa.aws.amazon.com/wellarchitected/2020-07-02T19-33-23/wat.question.REL_10.en.html)
|
|||
|
|
- [AWS Solutions Library — Guidance for cell-based architecture on AWS](https://github.com/aws-solutions-library-samples/guidance-for-cell-based-architecture-on-aws)
|
|||
|
|
- [Rackspace — Cell-based architecture on AWS](https://www.rackspace.com/blog/cell-based-architecture-aws)
|
|||
|
|
- [Ryan Harrison — Cell-based architecture engineering guidelines](https://guidelines.ryanharrison.co.uk/architecture/cell-architecture/)
|
|||
|
|
- [Anudeep Balla — The guide to cell-based architecture](https://medium.com/@anudeepballa7/the-guide-to-cell-based-architecture-58cf607f9754)
|
|||
|
|
- [AWS Well-Architected SaaS Lens (PDF)](https://docs.aws.amazon.com/pdfs/wellarchitected/latest/saas-lens/wellarchitected-saas-lens.pdf)
|
|||
|
|
- [hidekazu-konishi — AWS SaaS multi-tenant architecture guide: tenant isolation, pool and silo models](https://hidekazu-konishi.com/entry/aws_saas_multi_tenant_architecture_guide.html)
|