This article provides an honest assessment of both approaches, effort estimates for the re-architecture, and a recommendation.
flowchart TD
subgraph risk["Risk Comparison"]
direction TB
R1["Per-app model<br/>Bug affects 1 group"]
R2["Single instance<br/>Bug affects ALL 500 groups"]
R1 -.-|"vs"| R2
end
subgraph isolation["Data Isolation"]
direction TB
I1["Per-app model<br/>Physical isolation<br/>Separate databases"]
I2["Single instance<br/>Logical isolation<br/>useDb switching"]
I1 -.-|"vs"| I2
end
subgraph cost["Cost at 500 Groups"]
direction TB
C1["Per-app model<br/>No Fly.io charges<br/>600/month MongoDB"]
C2["Single instance<br/>50-80/month Fly.io<br/>600/month MongoDB"]
C1 -.-|"vs"| C2
end
style risk fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143
style isolation fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143
style cost fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143
groupCode filter or a stale useDb() call. At 500 groups this risk is amplified.The current architecture can scale to 500 groups with investment in deployment automation (which is already largely in place). The per-group deployment model provides stronger isolation, lower blast radius, and simpler application code.
If Ramblers Head Office insists on a single instance, Option B (database-per-group with useDb() switching) is the lowest-risk path because it preserves the current data isolation model and minimises code changes.
The key distinction to communicate to Ramblers Head Office:
| What they want | Current status |
|---|---|
| Single codebase | Already true — one repo, one team |
| Single build artifact | Already true — one Docker image |
| Automated group provisioning | Already true — CLI tool creates new groups |
| Single running instance | Not currently, but technically feasible |
The question is whether "single running instance" is a hard requirement or whether "single codebase with automated provisioning" meets their actual need.
Estimates are given for two delivery modes: AI-assisted (developer prompting Claude Code or similar) and manual (developer working unassisted).
| Component | AI-assisted | Manual | Risk |
|---|---|---|---|
| Tenant resolution middleware | 2–4 hours | 2–3 days | Medium |
Database switching (useDb()) |
4–8 hours | 3–5 days | High — needs load testing |
| Auth consolidation + group-scoped tokens | 1–2 hours | 1–2 days | Medium |
| S3 consolidation to single bucket | 3–6 hours | 2–3 days | Medium |
| Background job scheduler (group-aware) | 4–8 hours | 3–5 days | Medium |
API route audit (req.db everywhere) |
2–4 hours | 3–5 days | High — AI excels here |
| Super-admin and provisioning UI | 1–2 days | 5–8 days | Medium |
| Migration tooling (13 sites → single) | 3–6 hours | 2–3 days | Medium |
| Load testing at 500-group concurrency | 1–2 days | 3–5 days | High |
| Total | ~5–8 days | ~5–7 weeks |
The speedup is most dramatic for:
req.db — tedious for humans, trivial for AIThe speedup is smallest for:
useDb() database switchingmongoose.useDb() under concurrent multi-group traffic (simulate 500 databases)