266 lines
16 KiB
Markdown
266 lines
16 KiB
Markdown
|
|
# Gap research — erasure mechanism (R4) and noisy-neighbour evidence
|
||
|
|
|
||
|
|
> Research artefact — 2026-08-17. Fifth digest supporting
|
||
|
|
> `canon/architecture/adr-008-multi-tenancy-model.md` (draft-3). Closes the two
|
||
|
|
> gaps the four plane digests left open: how `R4` (verified erasure) can be
|
||
|
|
> reached at all, and what a noisy-neighbour evidence artifact should actually
|
||
|
|
> assert.
|
||
|
|
> **Both findings change a level definition in draft-3.**
|
||
|
|
> Index: `2026-08-17-adr008-multi-tenancy-research-index.md`.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
# Part 1 — Erasure: R4 needs a mechanism it does not currently have
|
||
|
|
|
||
|
|
## 1.1 The problem draft-3 left standing
|
||
|
|
|
||
|
|
ADR-008 §4.5 defines `R4` as "deletion proven complete across live data,
|
||
|
|
backups and derived copies within the published horizon", and `rapp-postgres`
|
||
|
|
ADR-0002 establishes that on shared substrate the horizon is the instance
|
||
|
|
maximum — every co-resident consumer's retention window, whichever is longest.
|
||
|
|
|
||
|
|
Read together, those say: **erasure completes by waiting.** A tenant exercising
|
||
|
|
a deletion right is told the data is gone in up to N days, where N is set by
|
||
|
|
somebody else's retention requirement. That is honest, and it is a weak answer.
|
||
|
|
It also makes `R4` unreachable in any useful sense, because "we waited" is not
|
||
|
|
a proof.
|
||
|
|
|
||
|
|
## 1.2 Crypto-shredding is the established mechanism
|
||
|
|
|
||
|
|
The industry answer to erasure under immutable or long-retention storage is to
|
||
|
|
make the data unreadable rather than absent.
|
||
|
|
|
||
|
|
> "Crypto-shredding for backups encrypts each subject (or tenant) with a unique
|
||
|
|
> key derived from the subject identifier, and on erasure, destroys the key.
|
||
|
|
> The backups remain but are cryptographically inaccessible."
|
||
|
|
> — [oneuptime — setting up crypto-shredding for GDPR right-to-erasure](https://oneuptime.com/blog/post/2026-02-17-how-to-set-up-crypto-shredding-for-gdpr-right-to-erasure-compliance-in-google-cloud/view)
|
||
|
|
|
||
|
|
It is aimed precisely at our situation: the named use cases are "append-only or
|
||
|
|
immutable storage (event sourcing, WORM, immutable backups) where physical
|
||
|
|
deletion is impossible"
|
||
|
|
([Granit — crypto-shredding: GDPR erasure without deleting rows](https://granit-fx.dev/blog/crypto-shredding-gdpr-erasure-without-deleting-rows/)).
|
||
|
|
|
||
|
|
**Regulatory standing, stated carefully because overclaiming here would be
|
||
|
|
worse than in any other section of this framework.** The EDPB has *not* formally
|
||
|
|
endorsed crypto-shredding as Article 17 erasure. Several data protection
|
||
|
|
authorities have accepted it where physical deletion would be manifestly
|
||
|
|
disproportionate effort under Article 17(1), and EDPB Guidelines 5/2019, the UK
|
||
|
|
ICO and the French CNIL are cited as recognising it **provided three conditions
|
||
|
|
hold**: strong encryption (AES-256), irreversible key destruction, and auditable
|
||
|
|
destruction
|
||
|
|
([pg_hardstorage — GDPR Art. 17 crypto-shred](https://github.com/cybertec-postgresql/pg_hardstorage/blob/main/docs/compliance/gdpr-art-17-crypto-shred.md),
|
||
|
|
[Opsio — right to erasure in cloud systems](https://opsiocloud.com/blogs/right-to-erasure-article-17-gdpr-deletion-cloud-systems/)).
|
||
|
|
|
||
|
|
The third condition is the one this estate is well placed for and the one most
|
||
|
|
implementations skip: **destruction must be auditable**. ADR-0002 already
|
||
|
|
requires every sweep to emit an evidence record. Key destruction is the same
|
||
|
|
shape.
|
||
|
|
|
||
|
|
## 1.3 The finding: shredding granularity is bounded by the key boundary
|
||
|
|
|
||
|
|
Crypto-shredding "requires per-entity key isolation: each entity instance gets
|
||
|
|
its own encryption key" (oneuptime, ibid.). That single requirement determines
|
||
|
|
what is available to us, and it is not uniform across the estate.
|
||
|
|
|
||
|
|
| What we want to shred | Key boundary needed | Available today? |
|
||
|
|
|---|---|---|
|
||
|
|
| A consumer's logical exports | Per-consumer key | **Close.** The offsite lane already encrypts with `age`, and OpenBao holds key material. This is a key-management change, not an architecture change. |
|
||
|
|
| A consumer's physical backup | Per-consumer key on the Barman object store | **No.** One WAL stream, one credential, one encryption context for the whole instance. Same constraint that forced the derived maximum in ADR-0002 §2. |
|
||
|
|
| A single tenant's rows inside a consumer | Per-tenant key, applied by the application before the data reaches storage | **No, and not the platform's to build.** Requires application-level per-tenant encryption. |
|
||
|
|
|
||
|
|
The third row is the architecturally interesting one. Per-tenant crypto-shred
|
||
|
|
means the consumer encrypts tenant data under a tenant-specific key before
|
||
|
|
writing it — which is an **enforcement-plane** property, not a retention one. It
|
||
|
|
also happens to be a strong one: data encrypted per tenant is unreadable across
|
||
|
|
the boundary even by a credential that can address the rows, which is E4-class
|
||
|
|
protection achieved without per-tenant substrate.
|
||
|
|
|
||
|
|
**This is a new coupling and it belongs in §3.2.** Draft-3 records two
|
||
|
|
couplings (E4 needs P3; R's horizon is bounded by P). There is a third:
|
||
|
|
**R4 at tenant granularity requires per-tenant keys, which is an E-plane
|
||
|
|
capability.** Reaching the top of the retention ladder is not a retention
|
||
|
|
project.
|
||
|
|
|
||
|
|
A reference implementation exists for the consumer-granularity case:
|
||
|
|
`pg_hardstorage` "implements crypto-shred by destroying the per-tenant KEK that
|
||
|
|
wraps every encrypted backup's DEK" (ibid.). Per the §14 adoption stance we
|
||
|
|
take the **structure** — envelope encryption with a per-consumer key-encryption
|
||
|
|
key wrapping each backup's data-encryption key, and destruction of the KEK as
|
||
|
|
the erasure act — and build it on our own OpenBao and `age` lane rather than
|
||
|
|
adopting the extension.
|
||
|
|
|
||
|
|
## 1.4 The tension nobody should paper over: erasure versus audit
|
||
|
|
|
||
|
|
`audit-core` exists to hold a tamper-evident trail. Crypto-shredding a tenant's
|
||
|
|
audit records destroys evidence, and ADR-0001 §2 deliberately built the role
|
||
|
|
model so that "a compromised runtime credential could not rewrite history at
|
||
|
|
the schema level". Erasure is a sanctioned way to do exactly that.
|
||
|
|
|
||
|
|
The literature treats this as a genuine design tension rather than a solved
|
||
|
|
problem ([Remote Reason — balancing auditability and privacy with crypto-shredding](https://remotereason.com/blog/balancing-auditability-and-privacy-with-crypto-shredding)).
|
||
|
|
The usual resolution is to separate the *fact* of an event (retained, not
|
||
|
|
personal) from its *personal payload* (encrypted per subject, shreddable), so
|
||
|
|
the trail survives erasure with its personal content removed.
|
||
|
|
|
||
|
|
**This is a question for `audit-core`, not an answer this framework should
|
||
|
|
supply.** Flagging it because a naive "R4 everywhere" target would silently
|
||
|
|
instruct the audit service to destroy its own evidence.
|
||
|
|
|
||
|
|
## 1.5 Proposed amendments to draft-3
|
||
|
|
|
||
|
|
1. **`R4` admits two routes, and must name which one a service uses:**
|
||
|
|
- *Horizon-elapsed* — deletion is complete once the published erasure
|
||
|
|
horizon has passed. Available to everyone, proves little, slow.
|
||
|
|
- *Key-destroyed* — data is rendered unreadable immediately by destroying the
|
||
|
|
key that decrypts it. Requires per-entity keys, AES-256-class encryption,
|
||
|
|
and an auditable destruction record.
|
||
|
|
2. **Add the third coupling to §3.2:** R4 at tenant granularity requires an
|
||
|
|
E-plane capability (per-tenant encryption). R4 at consumer granularity
|
||
|
|
requires per-consumer keys on the retained artifacts.
|
||
|
|
3. **State the regulatory position honestly** wherever R4 is described: DPAs
|
||
|
|
have accepted key destruction as erasure under conditions; the EDPB has not
|
||
|
|
formally endorsed it. A plan tier claiming "your data is deleted" on the
|
||
|
|
strength of crypto-shredding is making a defensible but not settled claim,
|
||
|
|
and §11.4's hard line should say so rather than implying R4 is a clean yes.
|
||
|
|
4. **Raise the audit tension** as an open question addressed to `audit-core`.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
# Part 2 — Noisy neighbour: the evidence artifact asserts the wrong thing
|
||
|
|
|
||
|
|
## 2.1 The problem draft-3 left standing
|
||
|
|
|
||
|
|
ADR-008 §13 proposes a P1/P2 evidence artifact: "one consumer saturating its
|
||
|
|
connection or CPU allowance demonstrably **does not breach** another's."
|
||
|
|
|
||
|
|
That is not achievable, and the reference guidance says so in its first line of
|
||
|
|
solution text:
|
||
|
|
|
||
|
|
> "Sharing a single resource inherently carries the risk of noisy neighbor
|
||
|
|
> problems that you can't completely avoid."
|
||
|
|
> — [Azure Architecture Center — Noisy Neighbor antipattern](https://learn.microsoft.com/en-us/azure/architecture/antipatterns/noisy-neighbor/noisy-neighbor)
|
||
|
|
|
||
|
|
An artifact that can only ever fail, or that passes by being run gently enough,
|
||
|
|
is worse than no artifact — it is the overclaim §6 prohibits, dressed as
|
||
|
|
evidence.
|
||
|
|
|
||
|
|
## 2.2 There are two failure shapes, and we only guard against one
|
||
|
|
|
||
|
|
Azure distinguishes them explicitly:
|
||
|
|
|
||
|
|
- **One greedy tenant.** "At peak times, tenant A uses all of the system's
|
||
|
|
resources, which means that any requests that tenant B makes fail." (ibid.)
|
||
|
|
- **Aggregate of many modest tenants.** "The noisy neighbor problem also occurs
|
||
|
|
when each individual tenant consumes only a small portion of the system's
|
||
|
|
capacity. However, the combined resource usage of many tenants can result in
|
||
|
|
a peak in overall usage." (ibid.)
|
||
|
|
|
||
|
|
Our per-consumer governance — `connection_limit`, `statement_timeout`,
|
||
|
|
`idle_in_transaction_session_timeout`, applied at database scope per ADR-0001
|
||
|
|
§3 — addresses the first shape well. It does nothing about the second.
|
||
|
|
|
||
|
|
**And the second shape is exactly what §17 computes.** Seven consumers, each
|
||
|
|
politely within its 14-connection allowance, request 98 of 100 connections.
|
||
|
|
Every one of them is well-behaved; the instance still fails. The connection
|
||
|
|
ceiling in §17 is not merely a capacity number — **it is the aggregate
|
||
|
|
noisy-neighbour bound**, and framing it as a scaling statistic understates what
|
||
|
|
it is.
|
||
|
|
|
||
|
|
## 2.3 The detection signature we can actually build
|
||
|
|
|
||
|
|
Azure's service-side diagnostic is specific and unintuitive:
|
||
|
|
|
||
|
|
> "**Failures when performing an operation for a tenant:** Look for failures
|
||
|
|
> that occur when a tenant isn't consuming a large share of the system's
|
||
|
|
> resources. This pattern might indicate that the tenant is experiencing a
|
||
|
|
> noisy neighbor problem. Track resource consumption by tenant." (ibid.)
|
||
|
|
|
||
|
|
The signal is a failure for a *quiet* consumer. That requires per-consumer
|
||
|
|
resource attribution, and we already have the raw material: ADR-0001 stamps
|
||
|
|
`application_name` per role (`<consumer>/runtime`), PostgreSQL logs carry
|
||
|
|
database, user, application name and client address, and
|
||
|
|
`make postgres-ops-status` already reports sessions by application name.
|
||
|
|
|
||
|
|
What is missing is a **baseline**. "It's important to clearly understand your
|
||
|
|
normal baseline resource usage and to configure monitoring and alerts to detect
|
||
|
|
spikes" (ibid.). No baseline for `platform-pg` has been recorded. Without one,
|
||
|
|
a spike is indistinguishable from a Tuesday.
|
||
|
|
|
||
|
|
## 2.4 A normative requirement we are not meeting
|
||
|
|
|
||
|
|
> "Be transparent with clients about any throttling mechanisms or usage quotas
|
||
|
|
> that you enforce. It's important that they handle failed requests gracefully
|
||
|
|
> and aren't caught off guard by limitations." (ibid.)
|
||
|
|
|
||
|
|
`tenant-engine` was told its connection and timeout limits during provisioning,
|
||
|
|
which is good practice by accident rather than by rule. Nothing requires it. A
|
||
|
|
consumer discovering its `statement_timeout` by hitting it in production is the
|
||
|
|
failure this warns about.
|
||
|
|
|
||
|
|
**Proposed:** fold quota transparency into §10 (blast radius must be published)
|
||
|
|
— the same disclosure obligation, extended from what a leaked credential can
|
||
|
|
reach to what the platform will refuse to do for you.
|
||
|
|
|
||
|
|
## 2.5 Mitigations we have, and the ones we do not
|
||
|
|
|
||
|
|
| Azure-recommended control | Our status |
|
||
|
|
|---|---|
|
||
|
|
| Query time limits / max returnable records | **Have.** `statement_timeout`, per-role since the tenant-engine change. |
|
||
|
|
| Connection quotas per tenant | **Have.** Database-scoped, per ADR-0001 §3. |
|
||
|
|
| Monitor overall *and* per-tenant resource usage | **Partial.** Attribution exists; no baseline, no alerting. |
|
||
|
|
| Throttling / rate limiting | **None.** No request-rate control at the database boundary. |
|
||
|
|
| Quality-of-service prioritisation | **None.** All consumers are equal, including on the synchronous authorization path where `tenant-engine` now sits. |
|
||
|
|
| Run heavy non-time-sensitive work off-peak | **Implicit.** Backups at 02:15; nothing else scheduled. |
|
||
|
|
| Rebalance tenants across stamps | **Not possible.** One instance, no second cell (§19.8). |
|
||
|
|
|
||
|
|
The QoS absence is worth noting against a specific fact: `tenant-engine` chose
|
||
|
|
a 5s statement timeout because `flex-auth` calls it synchronously on the
|
||
|
|
authorization path. That is a latency-critical consumer sharing an instance
|
||
|
|
with `audit-core`, which is not. Nothing prioritises one over the other, and
|
||
|
|
the framework has no vocabulary for saying one consumer's latency matters more.
|
||
|
|
|
||
|
|
## 2.6 Proposed amendment to draft-3
|
||
|
|
|
||
|
|
Replace the §13 P1/P2 artifact with one that asserts something true:
|
||
|
|
|
||
|
|
> **Noisy-neighbour characterisation (P1/P2, adversarial, load-generated).**
|
||
|
|
> A recorded baseline of normal per-consumer resource usage; a load run in
|
||
|
|
> which one consumer saturates its declared allowance; evidence that (a) the
|
||
|
|
> governance controls bind — the greedy consumer is throttled at its declared
|
||
|
|
> limits rather than exceeding them, and (b) the measured degradation
|
||
|
|
> experienced by co-resident consumers is recorded and judged acceptable.
|
||
|
|
> Carries a review date and the aggregate headroom at time of measurement.
|
||
|
|
|
||
|
|
That is achievable, it is falsifiable, and it produces a number — degradation
|
||
|
|
under contention — that a plan tier promising performance would need anyway.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. Summary of changes these two gaps force
|
||
|
|
|
||
|
|
| # | Change | Where |
|
||
|
|
|---|---|---|
|
||
|
|
| A | `R4` splits into *horizon-elapsed* and *key-destroyed* routes; a service must name which | §4.5 |
|
||
|
|
| B | Third coupling: tenant-granularity R4 requires an E-plane capability (per-tenant encryption) | §3.2 |
|
||
|
|
| C | R4's regulatory standing stated honestly — accepted by DPAs under conditions, not formally endorsed by the EDPB | §4.5, §11.4 |
|
||
|
|
| D | Erasure-versus-audit tension raised as an open question to `audit-core` | §19 |
|
||
|
|
| E | Noisy-neighbour artifact reworded from "does not breach" to characterisation with a recorded baseline and measured degradation | §13 |
|
||
|
|
| F | §17's connection ceiling reframed as the aggregate noisy-neighbour bound, not only a capacity figure | §17 |
|
||
|
|
| G | Quota transparency added as a disclosure obligation | §10 |
|
||
|
|
| H | No QoS vocabulary despite a latency-critical consumer sharing with a batch one | §19, new |
|
||
|
|
|
||
|
|
## Sources
|
||
|
|
|
||
|
|
- [oneuptime — How to set up crypto-shredding for GDPR right-to-erasure compliance](https://oneuptime.com/blog/post/2026-02-17-how-to-set-up-crypto-shredding-for-gdpr-right-to-erasure-compliance-in-google-cloud/view)
|
||
|
|
- [cybertec-postgresql/pg_hardstorage — GDPR Art. 17 crypto-shred](https://github.com/cybertec-postgresql/pg_hardstorage/blob/main/docs/compliance/gdpr-art-17-crypto-shred.md)
|
||
|
|
- [Granit — Crypto-shredding: GDPR erasure without deleting a single row](https://granit-fx.dev/blog/crypto-shredding-gdpr-erasure-without-deleting-rows/)
|
||
|
|
- [Granit — Crypto-shredding (dotnet/compliance)](https://granit-fx.dev/dotnet/compliance/crypto-shredding/)
|
||
|
|
- [Opsio — Right to erasure, Article 17 GDPR, deletion in cloud systems](https://opsiocloud.com/blogs/right-to-erasure-article-17-gdpr-deletion-cloud-systems/)
|
||
|
|
- [Remote Reason — Balancing auditability and privacy with crypto-shredding](https://remotereason.com/blog/balancing-auditability-and-privacy-with-crypto-shredding)
|
||
|
|
- [Azure Architecture Center — Noisy Neighbor antipattern](https://learn.microsoft.com/en-us/azure/architecture/antipatterns/noisy-neighbor/noisy-neighbor)
|
||
|
|
- [Neon — The noisy neighbor problem in multitenant architectures](https://neon.com/blog/noisy-neighbor-multitenant)
|
||
|
|
- [PingCAP — Playbook: stop noisy neighbors, multi-tenant MySQL at scale](https://www.pingcap.com/playbook-noisy-neighbor-multi-tenant-mysql/)
|
||
|
|
- [Causal inference for quantifying noisy neighbor effects in multi-tenant cloud environments (arXiv 2604.03145)](https://arxiv.org/pdf/2604.03145)
|
||
|
|
- [Azure Architecture Center — Throttling pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/throttling)
|
||
|
|
- [Azure Architecture Center — Rate Limiting pattern](https://learn.microsoft.com/en-us/azure/architecture/patterns/rate-limiting-pattern)
|