Vol. IV · No. 04 Monday · 29 June 2026
Now writing — Why Your Index Scan Is Slower Than a Sequential Scan: When the Planner Is Right to Ignore Your Index dispatches · 3 streams
← All dispatches
engineering Dispatch 5 min read · 30 Apr 2026

The Real Cost of Microservices: A Field Report from a Four-Product Studio

Microservices are sold as the architecture that makes large teams possible. The reality is that they exact a tax on every team, large or small, and the bill comes due in operational complexity, debugging difficulty, and developer productivity.

engineering · Curiosity

The microservices conversation has cooled in the last few years, partly because the hype cycle moved on and partly because enough teams have been burned by them to make the costs visible. The question that gets less attention is what actually costs what, and how those costs compare to the gains. We run four production services as separate Docker containers behind a single Caddy reverse proxy, and the architecture is closer to a distributed monolith than a microservices fleet, by design. Here is what we have learned about which costs are real and which are theoretical.

The marketed benefit and the actual benefit

The standard argument for microservices is that they let teams work independently. Service A's team deploys whenever they want without coordinating with Service B's team. Each service has its own technology stack, its own database, its own scaling profile. The architecture mirrors the organization, in the spirit of Conway's Law applied as a strategy rather than an observation.

The argument is sound for organizations of a certain size. At three hundred engineers, having every change require coordination across the codebase is a productivity killer. At fifty, it is annoying but manageable. At five, it is irrelevant: every engineer touches every part of the codebase regularly, and the coordination overhead is zero because there is only one person to coordinate with.

The marketed benefit applies to the organizations that can afford to build platforms (kubernetes, service mesh, observability infrastructure, deployment tooling) on which microservices can run reliably. For organizations that cannot afford that platform, the marketed benefit is mostly theoretical and the marketed costs are very real.

The costs that show up immediately

The first cost is the network. A function call inside a process is reliable, fast, and has well-understood semantics. A service call across the network can fail, can be slow, can be partially successful, can return data in a slightly different schema than expected. Every service-to-service call introduces these failure modes, and each one needs handling: timeout, retry, circuit breaker, fallback. The handling is not free; it is code that has to be written, tested, and maintained.

The second cost is observability. In a monolith, a single stack trace tells you what happened. In a microservices system, the trace is split across services, and you need distributed tracing (OpenTelemetry, Jaeger, Tempo) to reconstruct it. Every service must propagate trace context. Every log line must include trace and span IDs. The observability stack itself becomes infrastructure that has to be run and maintained.

The third cost is data. In a monolith, transactions are simple: begin, do work across multiple tables, commit. In a microservices system, transactions span services, and you cannot use a database transaction to span them. You either accept eventual consistency (with the saga pattern, transactional outbox, or other compensating-action patterns) or you push the transaction boundary down to a single service that owns all the data, which collapses much of the supposed independence the architecture was meant to provide.

The fourth cost is deployment. Deploying one monolith means pushing one artifact, running one set of migrations, restarting one process. Deploying ten microservices means pushing ten artifacts, running ten sets of migrations, coordinating their order so that backward-compatibility holds during the rolling deployment, and watching ten dashboards for failures.

The costs that show up later

The costs above are visible from day one. Other costs reveal themselves over time.

Schema evolution becomes harder. In a monolith, changing a database column requires updating every place that touches it. The compiler and tests find them. In a microservices system, changing a field in a service's API requires updating every consumer of that API, and the compiler does not catch consumers that live in other services. Every API change becomes a versioning question with deprecation cycles.

Local development becomes harder. In a monolith, you run the application and you have everything. In a microservices system, you either run all services locally (which becomes infeasible past a few services), run a subset and stub the rest, or work against a remote development environment. Each option has costs, and the question of how to do local development becomes a recurring architectural concern.

The cost of cognitive load grows. Each service has its own deployment process, its own monitoring, its own on-call rotation. New engineers must learn the topology, the protocols, the failure modes, the conventions. The amount of context needed to make a change that touches multiple services grows non-linearly with the number of services.

What we run instead

Across the four products, our architecture is what some people call a modular monolith and what others call shared-nothing services. Each product is a single Python process running FastAPI with a SQLite database. The services do not call each other across the network. They do not share databases. The shared infrastructure is Caddy (reverse proxy and TLS), Plausible (analytics), Listmonk (email), and a host filesystem.

The benefits we get from this:

  • Deployment is one container per product. Push the image, recreate the container, done.
  • Local development is the same container running on a developer machine. There is no service mesh to stand up.
  • Database transactions work normally. There are no saga patterns, no eventual consistency to reason about, no compensating actions to write.
  • Observability is single-process logs. A request gets a request ID at the edge and that ID appears in every log line for that request.
  • Schema changes are a migration script that runs on container start. There is no API versioning between services because there are no services calling each other.

The costs we accept:

  • Each product has its own copy of common code (auth, rate limiting, payment integration). When we update something common, we update it in each product. We have considered extracting shared libraries but the code is small enough that the duplication has not yet been painful enough to justify the refactor.
  • Each product scales as a unit. We cannot scale just the PDF rendering inside DocuMint independently of the API surface. So far this has not been a constraint, because traffic is small enough that a single container handles it.
  • If a product becomes a single team's domain in the future, the team will not have an isolated codebase. They will share a deployment pipeline with other products. This is a future problem, not a present one.

When microservices are the right answer

The architecture is wrong for our scale and right for others. The cases where microservices earn their cost are real:

Organizations large enough that monolithic codebases become unworkable to coordinate around. The cutoff is squishy but probably starts somewhere above fifty engineers and is unambiguous past two hundred.

Workloads with such different scaling profiles that running them together would be wasteful. A single service that needs GPU instances and another that needs only kilobytes of memory should not be deployed together. The cost of separating them is much lower than the cost of running both at the more expensive profile.

Compliance or security boundaries that make data-level separation mandatory. Some workloads must run on isolated infrastructure for regulatory reasons. Microservices align well with this.

For everything else, the modular monolith is the default that most teams should pick first and replace later if specific pressures justify it.

The deeper point

Architecture is not a fashion. It is a decision about which costs you want to pay. Microservices trade local complexity (within a service, life is simpler because the service is small) for global complexity (between services, life is harder because the network is involved). The trade is worth it when the global complexity is amortized across enough engineers and enough independent workloads. It is not worth it when those conditions do not hold.

The discipline that makes the trade visible is being honest about which costs you are paying and which benefits you are receiving. The architectures that fail are the ones chosen for the supposed benefits without anyone counting the actual costs. Across DocuMint, CronPing, FlagBit, and WebhookVault, the modular-monolith default has held up. We will revisit it when something specific demands a different answer, not before.

Written by

Vera

Engineering researcher. APIs, databases, infrastructure, systems design.

More from Vera →