Let's talk
operations

The software was multi-tenant on day one and onboarding a second tenant was still a project

Every business table in the quoting system carries a tenant identifier. Every query filters on it. Login requires a company code. The permission model is per tenant, the branding is per tenant, the settings are per tenant. By the usual definition it is a multi-tenant product.

Getting a second customer onto it would have taken weeks of developer time, and none of those weeks would have been spent on anything a customer would pay for.

Tenant isolation is the half of multi-tenancy that gets attention because it is the half with security consequences. The other half — how a new tenant’s data gets in — decides how many customers the business can actually have, and it is usually nobody’s design problem until the second customer signs.

What onboarding one tenant actually involved

It is worth listing, because the individual steps all sound reasonable and the total does not.

The pricing rules arrived as a spreadsheet. That spreadsheet had to be parsed into intermediate artefacts, because it could not be loaded directly — the expressions used one notation for material references and the engine used another, so every expression needed rewriting at seed time.

Then it had to be audited. Every calculation chain was checked for steps referring to values no earlier step produced; that turned up 121 problems. A repair layer generated proposed corrections for around 120 rules, each of which a human had to confirm rather than accept.

Then the references had to be resolved. Twelve rule codes were named in the mappings and defined nowhere, and establishing that — as opposed to assuming a load error — took a full pass over three sheets.

Then it was seeded, and then reconciled: expected count against actual count for materials, services, suppliers, rules, compositions and mappings, with every difference explained.

Then the master data had to be made legible. Fifty-four material groups had arrived with their code duplicated into the name field, so the interface showed a two-letter code where a description belonged. Thirty-five of them were matched to descriptive names by parsing the customer’s own stock price list across seven sheets and four hundred-odd codes. The remaining nineteen appeared nowhere in that list, and were named by inspecting what was actually in each group and deducing what the group was.

Then the rules themselves: fifty-one had placeholder names — a grade string, an auto-generated label, or the code repeated — and seven more were orphans referenced by nothing. All of them were renamed to something a fabricator would recognise, derived from what they were mapped to.

Every one of those steps was necessary. Every one required someone who could write and run code against the data. That is the problem.

The ceiling this creates

The arithmetic is unforgiving and it does not care about the quality of the product.

If onboarding takes a developer some number of weeks, then the number of customers you can add in a year is your developer weeks divided by that number, minus whatever those developers were supposed to be building instead. A better product does not raise that ceiling. Better sales does not raise it. Nothing raises it except making onboarding not require a developer.

There is a worse second-order effect. Because onboarding is expensive, the temptation is to only take customers large enough to justify it, which means the product drifts toward customers who expect bespoke handling, which makes onboarding more expensive again. That loop closes quietly and by the time it is visible you have a consultancy with a software asset rather than a software business.

The pipeline is the specification for the product feature

The useful thing about having done it manually once is that the manual process is a complete specification of the feature that should replace it.

Every script written for the first tenant maps to something a self-service importer needs.

A defined input shape — the columns, the sheets, what each one means. This existed as tribal knowledge about one workbook and needs to be a published template.

A pre-import audit that reads the file and reports, before anything is written: every reference that does not resolve, every broken calculation chain, every identifier that classifies as nothing the system recognises. This existed as three separate scripts producing three separate files.

A dry run with counts. What would be created, of each kind, how many. Compared against what the file contains. Any difference named with a reason before the user commits.

A reconciliation report after loading, in both directions, so the user can see the load matched the source and nothing arrived from anywhere else.

And a naming pass, or better, a refusal to import master data where the name field merely repeats the code — because that was not a data quality accident, it was the source file’s shape, and it will be the next customer’s shape too.

None of those is research. All of them exist as working code. They exist as scripts run by their author against one dataset, with paths and assumptions baked in, which is precisely the distance between a thing that works and a product feature.

What I would do differently

I would have written the pipeline as the product from the beginning, and onboarded the first customer through it.

The counter-argument at the time was reasonable and I still half believe it: you do not know what the importer needs until you have imported something, and building a general importer before seeing one real dataset produces a general importer for a problem you have imagined.

But there is a middle position I did not take. Run the first onboarding manually, and after each step, write down the input contract and the output check rather than only the result. I have that now, reconstructed from scripts and notes, which is a worse version of what I could have had by writing it as I went.

The honest state

It is not built. A second tenant today would cost close to what the first one cost, minus the learning, which is real but is not most of it.

That is the constraint on the business, stated plainly, and it is a constraint in the operations of the thing rather than in its architecture. The tenancy model is fine. The data path into it is the bottleneck, and no amount of work on the former improves the latter.

The rule

When you build multi-tenant software, cost the second tenant before you finish the first. Not the isolation — the loading. Write down every step that gets a customer’s real data into the system and mark which ones need a developer.

If that list is not close to empty, the number of customers you can serve is already decided, and it was decided by an implementation detail nobody discussed.

Working on something like this?

We build this kind of software, and we staff the teams that do.

Get in touch