{"id":"69bb38f63f1c7d8bc1820a19","title":"Assessment","path":"how-to/technical-articles/2026-03-18-single-instance-multi-tenant-exploration/assessment","contentMarkdown":"# Assessment, Effort Estimates, and Recommendation\n\n_____\n\nThis article provides an honest assessment of both approaches, effort estimates for the re-architecture, and a recommendation.\n\n## Architecture Comparison at a Glance\n\n```mermaid\nflowchart TD\n    subgraph risk[\"Risk Comparison\"]\n        direction TB\n        R1[\"Per-app model<br/>Bug affects 1 group\"]\n        R2[\"Single instance<br/>Bug affects ALL 500 groups\"]\n        R1 -.-|\"vs\"| R2\n    end\n\n    subgraph isolation[\"Data Isolation\"]\n        direction TB\n        I1[\"Per-app model<br/>Physical isolation<br/>Separate databases\"]\n        I2[\"Single instance<br/>Logical isolation<br/>useDb switching\"]\n        I1 -.-|\"vs\"| I2\n    end\n\n    subgraph cost[\"Cost at 500 Groups\"]\n        direction TB\n        C1[\"Per-app model<br/>No Fly.io charges<br/>600/month MongoDB\"]\n        C2[\"Single instance<br/>50-80/month Fly.io<br/>600/month MongoDB\"]\n        C1 -.-|\"vs\"| C2\n    end\n\n    style risk fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143\n    style isolation fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143\n    style cost fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143\n```\n\n## Arguments for Single-Instance Multi-Tenant\n\n- **Ramblers Head Office preference**: reduces their perceived complexity for national rollout\n- **Operational simplicity at scale**: managing 500 Fly.io apps is genuinely harder than managing 1–4\n- **Transparent billing**: a single app with clear, predictable costs avoids any ambiguity around free-tier usage at scale. A large organisation like Ramblers would likely prefer a clean billing relationship with infrastructure providers\n- **Easier onboarding**: adding a new group = database seed + DNS record, not full Fly.io app provisioning\n- **Central visibility**: single log stream, single metrics dashboard, single deployment\n\n## Arguments for Keeping the Current Model\n\n- **Data isolation is inherent**: no risk of cross-group data leakage — it's physically impossible today. Multi-tenancy introduces the risk of a missing `groupCode` filter or a stale `useDb()` call. **At 500 groups this risk is amplified.**\n- **Blast radius**: a bug or outage takes down ALL 500 groups simultaneously. **A single point of failure at national scale is a serious operational risk.**\n- **Resource isolation**: one group's heavy traffic doesn't starve others\n- **Zero Fly.io compute cost today**: each app falls under the $5/month free allowance. However, **this advantage may not scale to 500 apps** — Fly.io could reasonably view 500 free-tier apps operated by a single organisation as an unintended use of their pricing model.\n- **The current model already works**: single Docker image, automated deployment, proven at scale\n- **Security posture**: separate databases and auth secrets per group is stronger. A multi-tenant data leak across 500 groups could be catastrophic\n- **Custom domains already work**: users are completely unaware of the shared codebase\n\n## Recommendation\n\n**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.\n\nIf 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.\n\nThe key distinction to communicate to Ramblers Head Office:\n\n| What they want | Current status |\n|----------------|---------------|\n| Single codebase | Already true — one repo, one team |\n| Single build artifact | Already true — one Docker image |\n| Automated group provisioning | Already true — CLI tool creates new groups |\n| Single running instance | Not currently, but technically feasible |\n\nThe question is whether \"single running instance\" is a hard requirement or whether \"single codebase with automated provisioning\" meets their actual need.\n\n## Estimated Effort\n\nEstimates are given for two delivery modes: **AI-assisted** (developer prompting Claude Code or similar) and **manual** (developer working unassisted).\n\n| Component | AI-assisted | Manual | Risk |\n|-----------|------------|--------|------|\n| Tenant resolution middleware | 2–4 hours | 2–3 days | Medium |\n| Database switching (`useDb()`) | 4–8 hours | 3–5 days | High — needs load testing |\n| Auth consolidation + group-scoped tokens | 1–2 hours | 1–2 days | Medium |\n| S3 consolidation to single bucket | 3–6 hours | 2–3 days | Medium |\n| Background job scheduler (group-aware) | 4–8 hours | 3–5 days | Medium |\n| API route audit (`req.db` everywhere) | 2–4 hours | 3–5 days | High — AI excels here |\n| Super-admin and provisioning UI | 1–2 days | 5–8 days | Medium |\n| Migration tooling (13 sites → single) | 3–6 hours | 2–3 days | Medium |\n| Load testing at 500-group concurrency | 1–2 days | 3–5 days | High |\n| **Total** | **~5–8 days** | **~5–7 weeks** | |\n\n### Why the AI-Assisted Speedup?\n\nThe speedup is most dramatic for:\n- **Route auditing**: AI can systematically scan every Express route handler and flag those not using `req.db` — tedious for humans, trivial for AI\n- **Middleware and boilerplate**: tenant resolution, auth changes, and S3 path rewriting are well-understood patterns\n- **Migration scripts**: database-to-database data movement is mechanical and well-suited to AI generation\n\nThe speedup is smallest for:\n- **Load testing**: test execution and monitoring takes real time regardless\n- **Super-admin UI**: involves UX decisions and iterative refinement\n- **Integration testing**: verifying behaviour across 500 groups requires real infrastructure\n\n## Next Steps\n\n- Present current architecture to Ramblers Head Office, emphasising single codebase + single artifact + automated provisioning\n- Clarify whether \"single instance\" is a hard requirement or whether the actual need is simpler onboarding/management\n- If required, prototype tenant resolution middleware + `useDb()` database switching\n- Load test `mongoose.useDb()` under concurrent multi-group traffic (simulate 500 databases)\n- Assess MongoDB Atlas pricing at M30+ tier for 500 databases\n- Assess Fly.io multi-domain TLS certificate management at 500+ domains\n","contentHtml":"<h1>Assessment, Effort Estimates, and Recommendation</h1>\n<hr>\n<p>This article provides an honest assessment of both approaches, effort estimates for the re-architecture, and a recommendation.</p>\n<h2>Architecture Comparison at a Glance</h2>\n<pre><code class=\"language-mermaid\">flowchart TD\n    subgraph risk[&quot;Risk Comparison&quot;]\n        direction TB\n        R1[&quot;Per-app model&lt;br/&gt;Bug affects 1 group&quot;]\n        R2[&quot;Single instance&lt;br/&gt;Bug affects ALL 500 groups&quot;]\n        R1 -.-|&quot;vs&quot;| R2\n    end\n\n    subgraph isolation[&quot;Data Isolation&quot;]\n        direction TB\n        I1[&quot;Per-app model&lt;br/&gt;Physical isolation&lt;br/&gt;Separate databases&quot;]\n        I2[&quot;Single instance&lt;br/&gt;Logical isolation&lt;br/&gt;useDb switching&quot;]\n        I1 -.-|&quot;vs&quot;| I2\n    end\n\n    subgraph cost[&quot;Cost at 500 Groups&quot;]\n        direction TB\n        C1[&quot;Per-app model&lt;br/&gt;No Fly.io charges&lt;br/&gt;600/month MongoDB&quot;]\n        C2[&quot;Single instance&lt;br/&gt;50-80/month Fly.io&lt;br/&gt;600/month MongoDB&quot;]\n        C1 -.-|&quot;vs&quot;| C2\n    end\n\n    style risk fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143\n    style isolation fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143\n    style cost fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143\n</code></pre>\n<h2>Arguments for Single-Instance Multi-Tenant</h2>\n<ul>\n<li><strong>Ramblers Head Office preference</strong>: reduces their perceived complexity for national rollout</li>\n<li><strong>Operational simplicity at scale</strong>: managing 500 Fly.io apps is genuinely harder than managing 1–4</li>\n<li><strong>Transparent billing</strong>: a single app with clear, predictable costs avoids any ambiguity around free-tier usage at scale. A large organisation like Ramblers would likely prefer a clean billing relationship with infrastructure providers</li>\n<li><strong>Easier onboarding</strong>: adding a new group = database seed + DNS record, not full Fly.io app provisioning</li>\n<li><strong>Central visibility</strong>: single log stream, single metrics dashboard, single deployment</li>\n</ul>\n<h2>Arguments for Keeping the Current Model</h2>\n<ul>\n<li><strong>Data isolation is inherent</strong>: no risk of cross-group data leakage — it&#39;s physically impossible today. Multi-tenancy introduces the risk of a missing <code>groupCode</code> filter or a stale <code>useDb()</code> call. <strong>At 500 groups this risk is amplified.</strong></li>\n<li><strong>Blast radius</strong>: a bug or outage takes down ALL 500 groups simultaneously. <strong>A single point of failure at national scale is a serious operational risk.</strong></li>\n<li><strong>Resource isolation</strong>: one group&#39;s heavy traffic doesn&#39;t starve others</li>\n<li><strong>Zero Fly.io compute cost today</strong>: each app falls under the $5/month free allowance. However, <strong>this advantage may not scale to 500 apps</strong> — Fly.io could reasonably view 500 free-tier apps operated by a single organisation as an unintended use of their pricing model.</li>\n<li><strong>The current model already works</strong>: single Docker image, automated deployment, proven at scale</li>\n<li><strong>Security posture</strong>: separate databases and auth secrets per group is stronger. A multi-tenant data leak across 500 groups could be catastrophic</li>\n<li><strong>Custom domains already work</strong>: users are completely unaware of the shared codebase</li>\n</ul>\n<h2>Recommendation</h2>\n<p><strong>The current architecture can scale to 500 groups</strong> 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.</p>\n<p>If Ramblers Head Office insists on a single instance, <strong>Option B</strong> (database-per-group with <code>useDb()</code> switching) is the lowest-risk path because it preserves the current data isolation model and minimises code changes.</p>\n<p>The key distinction to communicate to Ramblers Head Office:</p>\n<table>\n<thead>\n<tr>\n<th>What they want</th>\n<th>Current status</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>Single codebase</td>\n<td>Already true — one repo, one team</td>\n</tr>\n<tr>\n<td>Single build artifact</td>\n<td>Already true — one Docker image</td>\n</tr>\n<tr>\n<td>Automated group provisioning</td>\n<td>Already true — CLI tool creates new groups</td>\n</tr>\n<tr>\n<td>Single running instance</td>\n<td>Not currently, but technically feasible</td>\n</tr>\n</tbody></table>\n<p>The question is whether &quot;single running instance&quot; is a hard requirement or whether &quot;single codebase with automated provisioning&quot; meets their actual need.</p>\n<h2>Estimated Effort</h2>\n<p>Estimates are given for two delivery modes: <strong>AI-assisted</strong> (developer prompting Claude Code or similar) and <strong>manual</strong> (developer working unassisted).</p>\n<table>\n<thead>\n<tr>\n<th>Component</th>\n<th>AI-assisted</th>\n<th>Manual</th>\n<th>Risk</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>Tenant resolution middleware</td>\n<td>2–4 hours</td>\n<td>2–3 days</td>\n<td>Medium</td>\n</tr>\n<tr>\n<td>Database switching (<code>useDb()</code>)</td>\n<td>4–8 hours</td>\n<td>3–5 days</td>\n<td>High — needs load testing</td>\n</tr>\n<tr>\n<td>Auth consolidation + group-scoped tokens</td>\n<td>1–2 hours</td>\n<td>1–2 days</td>\n<td>Medium</td>\n</tr>\n<tr>\n<td>S3 consolidation to single bucket</td>\n<td>3–6 hours</td>\n<td>2–3 days</td>\n<td>Medium</td>\n</tr>\n<tr>\n<td>Background job scheduler (group-aware)</td>\n<td>4–8 hours</td>\n<td>3–5 days</td>\n<td>Medium</td>\n</tr>\n<tr>\n<td>API route audit (<code>req.db</code> everywhere)</td>\n<td>2–4 hours</td>\n<td>3–5 days</td>\n<td>High — AI excels here</td>\n</tr>\n<tr>\n<td>Super-admin and provisioning UI</td>\n<td>1–2 days</td>\n<td>5–8 days</td>\n<td>Medium</td>\n</tr>\n<tr>\n<td>Migration tooling (13 sites → single)</td>\n<td>3–6 hours</td>\n<td>2–3 days</td>\n<td>Medium</td>\n</tr>\n<tr>\n<td>Load testing at 500-group concurrency</td>\n<td>1–2 days</td>\n<td>3–5 days</td>\n<td>High</td>\n</tr>\n<tr>\n<td><strong>Total</strong></td>\n<td><strong>~5–8 days</strong></td>\n<td><strong>~5–7 weeks</strong></td>\n<td></td>\n</tr>\n</tbody></table>\n<h3>Why the AI-Assisted Speedup?</h3>\n<p>The speedup is most dramatic for:</p>\n<ul>\n<li><strong>Route auditing</strong>: AI can systematically scan every Express route handler and flag those not using <code>req.db</code> — tedious for humans, trivial for AI</li>\n<li><strong>Middleware and boilerplate</strong>: tenant resolution, auth changes, and S3 path rewriting are well-understood patterns</li>\n<li><strong>Migration scripts</strong>: database-to-database data movement is mechanical and well-suited to AI generation</li>\n</ul>\n<p>The speedup is smallest for:</p>\n<ul>\n<li><strong>Load testing</strong>: test execution and monitoring takes real time regardless</li>\n<li><strong>Super-admin UI</strong>: involves UX decisions and iterative refinement</li>\n<li><strong>Integration testing</strong>: verifying behaviour across 500 groups requires real infrastructure</li>\n</ul>\n<h2>Next Steps</h2>\n<ul>\n<li>Present current architecture to Ramblers Head Office, emphasising single codebase + single artifact + automated provisioning</li>\n<li>Clarify whether &quot;single instance&quot; is a hard requirement or whether the actual need is simpler onboarding/management</li>\n<li>If required, prototype tenant resolution middleware + <code>useDb()</code> database switching</li>\n<li>Load test <code>mongoose.useDb()</code> under concurrent multi-group traffic (simulate 500 databases)</li>\n<li>Assess MongoDB Atlas pricing at M30+ tier for 500 databases</li>\n<li>Assess Fly.io multi-domain TLS certificate management at 500+ domains</li>\n</ul>\n"}