We split Modular into separate services twice and rolled it back twice. This is what we measured, and why the core remains a single deployable for now.

01 The measurement

The first split put content, permissions and routing into three services. An average page render went from 41 milliseconds to 128. That was not caused by the services themselves, but by the number of times they needed each other within a single request. We counted an average of nine internal calls per render, seven of which went to the permissions service.

Caching helped, until the first time someone changed a role and the cache kept returning the old permissions for twenty minutes. That was the second lesson: permissions are not data you are allowed to let go stale.

02 The boundary that worked

One split held up. Everything that is allowed to run outside the request is now a separate worker: exports, image processing, webhooks, email. Those things share no state with the render and they are allowed to fail without a visitor noticing.

The rule we derived from that has been in our RFC text since April. Splitting a service is allowed when the boundary runs along time, not when it runs along a domain concept.

03 What it cost

Two people, eleven weeks, spread over eighteen months. That is expensive for an experiment you roll back.

A rolled-back experiment is not waste as long as you write down what you measured.

04 How it stands now

Modular runs as a single application with five workers alongside it. An average render sits at 38 milliseconds. The codebase has grown by 6 percent compared to the split version, and the number of places you need to look at when an error occurs has gone from four to one.

If traffic multiplies by thirteen we will look again. Until then this is the cheapest version that works.