TEN-WP-0009: make database placement portable rather than settled
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
121968d65b
commit
cf7ca1a692
2 changed files with 94 additions and 17 deletions
|
|
@ -17,6 +17,7 @@
|
|||
| workplan | TEN-WP-0006 | finished | — | workplans/TEN-WP-0006-guardrail-quota-policy.md |
|
||||
| workplan | TEN-WP-0007 | finished | — | workplans/TEN-WP-0007-guardrail-production-rollout.md |
|
||||
| workplan | TEN-WP-0008 | ready | — | workplans/TEN-WP-0008-staged-promotion-onboarding.md |
|
||||
| workplan | TEN-WP-0009 | ready | — | workplans/TEN-WP-0009-postgres-production-store.md |
|
||||
| task | ADHOC-2026-07-24-T01 | done | — | workplans/ADHOC-2026-07-24.md |
|
||||
| task | TEN-WP-0001-T01 | done | — | workplans/TEN-WP-0001-statehub-bootstrap.md |
|
||||
| task | TEN-WP-0001-T02 | done | — | workplans/TEN-WP-0001-statehub-bootstrap.md |
|
||||
|
|
@ -53,3 +54,9 @@
|
|||
| task | TEN-WP-0008-T02 | todo | — | workplans/TEN-WP-0008-staged-promotion-onboarding.md |
|
||||
| task | TEN-WP-0008-T03 | todo | — | workplans/TEN-WP-0008-staged-promotion-onboarding.md |
|
||||
| task | TEN-WP-0008-T04 | todo | — | workplans/TEN-WP-0008-staged-promotion-onboarding.md |
|
||||
| task | TEN-WP-0009-T01 | todo | — | workplans/TEN-WP-0009-postgres-production-store.md |
|
||||
| task | TEN-WP-0009-T02 | todo | — | workplans/TEN-WP-0009-postgres-production-store.md |
|
||||
| task | TEN-WP-0009-T03 | todo | — | workplans/TEN-WP-0009-postgres-production-store.md |
|
||||
| task | TEN-WP-0009-T04 | todo | — | workplans/TEN-WP-0009-postgres-production-store.md |
|
||||
| task | TEN-WP-0009-T05 | todo | — | workplans/TEN-WP-0009-postgres-production-store.md |
|
||||
| task | TEN-WP-0009-T06 | todo | — | workplans/TEN-WP-0009-postgres-production-store.md |
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ depends_on:
|
|||
- TEN-WP-0007
|
||||
unblocks:
|
||||
- TEN-WP-0008
|
||||
state_hub_workstream_id: "3bf3b4ab-a116-4914-b2d8-d3c430754956"
|
||||
---
|
||||
|
||||
# TEN-WP-0009 - PostgreSQL production store
|
||||
|
|
@ -57,33 +58,88 @@ So **no secret enters this repo, this workplan, or any evidence dump**, and no
|
|||
or rotate. Runtime secret custody remains OpenBao's per `SCOPE.md`; an
|
||||
operator-minted in-cluster secret is outside that lane.
|
||||
|
||||
## T01 - Decide the topology and write the cluster manifest
|
||||
## Placement: shared by default, movable by design
|
||||
|
||||
Dedicated-vs-shared is **not a decision this workplan should settle once**, and
|
||||
`tenant-engine` is the wrong place to settle it for the platform. The estate
|
||||
has distinct elements — railiance, NetKingdom, HelixForge, Coulomb — plus
|
||||
tenants layered on top, and which repo belongs to which grouping is not yet
|
||||
organised. On top of that, isolation level is heading toward being a
|
||||
*product* property: plans will differ in how much isolation they buy.
|
||||
|
||||
So the requirement here is **portability, not placement**:
|
||||
|
||||
- start on a **shared** cluster, because it is cheaper on a single node and
|
||||
nothing yet justifies dedicated capacity;
|
||||
- make moving to a dedicated cluster — or to a different shared one — an
|
||||
**operational change, not a code change**;
|
||||
- never let the decision leak into the application.
|
||||
|
||||
What that demands of this repo, and all of it is cheap if done now and
|
||||
expensive to retrofit:
|
||||
|
||||
1. **Connect by injected URL only.** No cluster name, namespace, host, or
|
||||
database name anywhere in `src/`. The application must be unable to tell
|
||||
whether it is on a shared or dedicated cluster.
|
||||
2. **Own a whole database, not a set of tables in someone else's.** Sharing a
|
||||
*cluster* is a capacity decision; sharing a *database* would entangle
|
||||
schemas and make relocation a merge rather than a move. A dedicated
|
||||
database inside a shared cluster relocates with a dump and a restore.
|
||||
3. **No cross-database joins or co-location assumptions**, which the repo
|
||||
boundary already forbids — `tenant_id` is the only key shared with
|
||||
`user-engine`. Placement must not quietly become a dependency.
|
||||
4. **Idempotent schema creation**, so a fresh target comes up correct without a
|
||||
hand-built database.
|
||||
|
||||
Then "move `tenant-engine` to dedicated" is: create the target cluster, dump,
|
||||
restore, swap the `secretKeyRef`, restart. No rebuild, no release, no code
|
||||
review.
|
||||
|
||||
**What this repo does not own:** the placement *policy* — which element or
|
||||
tenant gets dedicated capacity and when. That is a platform concern, and
|
||||
`tenant-engine` implementing its own would be the same boundary error as
|
||||
building a fleet drift-detector because we got bitten. Raised with
|
||||
`railiance-platform` separately; see T01.
|
||||
|
||||
## T01 - Make placement portable and provision on shared capacity
|
||||
|
||||
```task
|
||||
id: TEN-WP-0009-T01
|
||||
status: todo
|
||||
priority: high
|
||||
state_hub_task_id: "b4701256-f235-4474-9dcf-7cb09f62b873"
|
||||
```
|
||||
|
||||
Decide between a dedicated `tenant-engine-pg` cluster in our namespace and
|
||||
sharing an existing one (`net-kingdom-pg` or `apps-pg` in `databases`).
|
||||
Provision a dedicated **database** on an existing **shared** cluster, per the
|
||||
principles above. Candidates are `net-kingdom-pg` (already exists, and
|
||||
`tenant-engine` is a NetKingdom service) and `apps-pg`. Prefer the one whose
|
||||
grouping matches the product family, since that is the axis a future split
|
||||
would most likely follow — moving is cheap, but moving less often is cheaper.
|
||||
|
||||
Argue it rather than defaulting. Points to weigh: `user-engine` chose dedicated
|
||||
and is the closest precedent; a dedicated cluster keeps blast radius and backup
|
||||
policy per-service; a shared cluster is cheaper on a single node and
|
||||
`net-kingdom-pg` already exists for this product family. Note that
|
||||
`tenant-engine` is consulted by `flex-auth` on the authorization path, so its
|
||||
availability envelope is not purely its own.
|
||||
Note the deviation from precedent and why: `user-engine` runs a dedicated
|
||||
`user-engine-pg`. We are deliberately *not* copying that. Their choice is fine
|
||||
and ours is reversible in an afternoon; picking dedicated now would spend
|
||||
capacity on an isolation guarantee nothing has asked for yet.
|
||||
|
||||
Write the CNPG `Cluster` manifest into `deploy/`, matching the fleet's existing
|
||||
image and storage conventions rather than inventing new ones.
|
||||
Record what would trigger a move to dedicated, so the reversal is a judgement
|
||||
already made rather than one improvised under pressure. Candidate triggers:
|
||||
a noisy neighbour affecting the authorization path, a compliance or residency
|
||||
requirement, a plan tier that sells isolation, or the shared cluster's backup
|
||||
policy no longer fitting.
|
||||
|
||||
Also decide **backups**. The `scheduledbackups.postgresql.cnpg.io` CRD is
|
||||
available; SQLite-on-a-PVC had no backup story at all, so this is a gain to
|
||||
claim explicitly rather than inherit by accident.
|
||||
Also decide **backups**: whether the shared cluster's existing
|
||||
`scheduledbackups` policy covers us, or we need our own. SQLite-on-a-PVC had no
|
||||
backup story at all, so this is a gain to claim explicitly rather than inherit
|
||||
by accident — and a shared cluster means inheriting *someone else's* retention
|
||||
choice, which is worth checking rather than assuming.
|
||||
|
||||
Done when the topology decision is recorded with its reasoning and the manifest
|
||||
is committed.
|
||||
Write whatever manifest the choice needs into `deploy/` — a CNPG `Database`
|
||||
resource against the shared cluster, not a new `Cluster`, if that is the
|
||||
correct shape.
|
||||
|
||||
Done when the database exists on shared capacity, the connection reaches the
|
||||
app only through an injected URL, and the move-to-dedicated trigger list and
|
||||
runbook are recorded.
|
||||
|
||||
## T02 - Implement `PostgresTenantStore`
|
||||
|
||||
|
|
@ -91,6 +147,7 @@ is committed.
|
|||
id: TEN-WP-0009-T02
|
||||
status: todo
|
||||
priority: high
|
||||
state_hub_task_id: "d7428bc1-2e5f-4a4c-93f5-9368ebb691d5"
|
||||
```
|
||||
|
||||
Implement the full `TenantStore` Protocol — including
|
||||
|
|
@ -128,6 +185,7 @@ Done when all three backends pass the same conformance suites unchanged.
|
|||
id: TEN-WP-0009-T03
|
||||
status: todo
|
||||
priority: high
|
||||
state_hub_task_id: "89c121c6-8bc5-4abe-80f0-d11406aeffc2"
|
||||
```
|
||||
|
||||
`Settings` currently carries only `database_path`. Add a database URL and make
|
||||
|
|
@ -156,6 +214,7 @@ by tests.
|
|||
id: TEN-WP-0009-T04
|
||||
status: todo
|
||||
priority: high
|
||||
state_hub_task_id: "51a83eae-9e9f-4676-bc1d-9c2d884bac0d"
|
||||
```
|
||||
|
||||
Move the live SQLite database to PostgreSQL. Currently small — two tenants
|
||||
|
|
@ -191,6 +250,7 @@ recorded here.
|
|||
id: TEN-WP-0009-T05
|
||||
status: todo
|
||||
priority: high
|
||||
state_hub_task_id: "03aeae3b-5e0f-4c77-a763-787ae08078f5"
|
||||
```
|
||||
|
||||
Update `deploy/tenant-engine.yaml`:
|
||||
|
|
@ -223,6 +283,7 @@ service.
|
|||
id: TEN-WP-0009-T06
|
||||
status: todo
|
||||
priority: medium
|
||||
state_hub_task_id: "e7adbc64-ad1b-4f28-bfd0-0b07034629db"
|
||||
```
|
||||
|
||||
- `flex-auth`: availability characteristics of their data source changed. No
|
||||
|
|
@ -243,4 +304,13 @@ Done when the notes are sent and TEN-WP-0008-T02 is updated.
|
|||
change at the same time makes any regression ambiguous.
|
||||
- **Sharing a database with `user-engine`.** `tenant_id` is the only key the
|
||||
two services share, and it stays that way — separate stores are the boundary,
|
||||
not an implementation detail.
|
||||
not an implementation detail. Sharing a *cluster* is fine; sharing a
|
||||
*database* is not.
|
||||
- **Fleet placement policy.** Which element or tenant gets dedicated capacity,
|
||||
and how isolation maps to plan tiers, is a platform decision. This workplan
|
||||
makes `tenant-engine` movable and stops there.
|
||||
- **Per-tenant isolation.** A future plan tier may sell dedicated
|
||||
infrastructure. `tenant-engine` already records plan assignment by id, and
|
||||
`adaptive-pricing` owns what a plan *means*, so neither the tier definition
|
||||
nor the placement that implements it belongs here. Worth designing for —
|
||||
which the portability principles above do — not worth building for.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue