skip to content

A company has split its system into 40 separately deployed services, each with its own repository and CI pipeline. Releases, however, still require a scheduled 'release train' where most services deploy together in a fixed order, and a shared database is queried directly by a dozen of the services. What architectural anti-pattern does this describe, and what does it cost the organization compared to either a true microservices style or a well-modularized monolith?

level: seniorimportance: should knowfreq 60%

answer

  1. release train = synchronized deploys
  2. shared DB across services = hidden coupling
  3. worst of both worlds
  4. pays distribution cost, no independence benefit
  5. fix: real data ownership + versioned contracts, or consolidate back

basics

~20 s

This is called a 'distributed monolith' -- many separate services that still have to be released together and share data directly. It gives you all the extra network calls and complexity of microservices, but none of the benefit of teams shipping independently.

solid answer

~50 s

This is the distributed monolith anti-pattern: services are physically separated into their own deployables, but they're still coupled at runtime (via a shared database queried by many services) and at release time (via a synchronized release train), so they behave, organizationally, like a single deployable unit. Compared to true microservices, the organization gets none of the independent-deployability benefit -- teams still can't ship on their own schedule -- while paying the full technical cost of distribution: network calls and their added latency and failure modes, the operational burden of running 40 separate deployables, and much harder end-to-end debugging since a bug now requires tracing across many processes instead of one call stack. Compared to a well-modularized monolith, it's strictly worse: the monolith would give the same synchronized-release constraint without any of the network/operational overhead. The fix is either to genuinely decouple (private per-service data ownership, versioned contracts that let releases happen independently) or to accept that the system is organizationally a monolith and simplify back toward fewer deployables.

go deeper

for a junior

Should recognize that having many separate services doesn't automatically mean they can be released independently, at a basic descriptive level.

for a middle

Should identify both the shared-database and the synchronized-release symptoms as evidence of the distributed monolith anti-pattern.

for a senior

Should be able to explain concretely why this configuration is worse than either honest alternative and propose a realistic incremental path to real decoupling.

for a principal

Should be able to diagnose this pattern across an entire organization's service estate, prioritize which couplings to remove first based on risk/impact, and make the strategic call between decoupling versus consolidating back to fewer deployables.

## What the scenario describes The scenario described -- many separately deployed services that nonetheless require a synchronized 'release train' and that share a database queried directly by multiple services -- is the textbook definition of a **distributed monolith**: a system that has paid the technical cost of distributing into many processes without earning the organizational benefit that distribution was supposed to buy. It looks like microservices from the repo list, but behaves like a monolith from the perspective of the thing that actually matters: can any one team ship a change on their own schedule without coordinating with others? In this scenario the answer is no, for two independent reasons that reinforce each other. ## Reason one -- the shared database The first reason is the shared database. When multiple services read and write the same tables directly, that schema becomes a **de facto shared interface**, exactly like a monolith's internal data layer, except now enforced across process and network boundaries instead of within one codebase. Any team that wants to add a column, change a type, or restructure a relation has to check whether any of the other services querying that table depend on the old shape, and coordinate the change with all of them -- the same coordination cost a monolith's shared database imposes, just spread across more org chart boxes and harder to trace because the dependency isn't visible in a single codebase's imports, it's hidden in each service's queries against tables it doesn't own. ## Reason two -- the release train The second reason is the release train itself: a fixed, scheduled, ordered deployment of most services together. This typically emerges because services have accumulated undocumented runtime assumptions about each other's current version -- one service assumes a field another just added, or two services share a library version that has to move in lockstep -- and the organization has responded by formalizing the coordination into a recurring calendar event rather than removing the coupling that made coordination necessary. Once a release train exists, it tends to become **self-reinforcing**: 1. because releases only happen on the train's schedule, teams batch up more changes per release; 2. which makes each release riskier and the coordination meeting even more important; 3. which further discourages any team from trying to break out and deploy independently. ## What it costs against the honest alternatives What this costs, compared to the two honest alternatives, is **the worst of both worlds**. - **Against a true microservices architecture** -- where each service's data is genuinely private and every inter-service call goes through a versioned, backward-compatible contract -- this system gets zero of the benefit: no team can actually ship independently, so the entire premise for taking on distributed-systems complexity never materializes. - **Against a well-modularized monolith** -- one binary, one deploy, with clean internal module boundaries enforced by the compiler rather than by network calls -- this system is strictly worse on every axis that matters here. The monolith would give the same 'we release together on a schedule' constraint this system already has, but without paying for 40 sets of infrastructure to run and monitor, without the added latency and partial-failure modes of network calls, and without the debugging tax of chasing a single logical operation's failure across a dozen services' logs instead of one call stack. The distributed monolith pays microservices' costs and collects a monolith's benefits -- none of which is a good trade. ## The fix -- pick one path honestly The fix has two forms and an organization has to pick one honestly rather than staying stuck in between. 1. **One path is to genuinely decouple.** Give each service private ownership of its own data store so no other service queries it directly, replace direct table access with calls through a versioned API kept backward-compatible so services can each deploy on their own schedule, and remove the release-train dependency incrementally as each pairwise coupling gets untangled -- this is real, often multi-quarter migration work, not a config change. 2. **The other path is to accept**, honestly, that the system's actual granularity is one deployable unit organizationally, and consolidate back toward fewer services, which at least stops paying for distribution's overhead while keeping the coordination cost the org has already accepted. ## Why it is treated as a cautionary case This pattern is widely reported in postmortems and conference talks from companies that adopted microservices as an org-wide mandate without first doing the data-ownership and contract work -- teams end up describing their systems, ruefully, as 'microservices in name, monolith in practice,' a phrase common enough in the industry's retrospective writing on early, overzealous microservices adoptions that it's treated as a standard cautionary case study.

  • Why is a shared database across services worse than it might first appear, compared to a shared library?
    A shared library dependency is at least visible in each service's build manifest and versioned explicitly, so a change is a deliberate, trackable event. A shared database queried directly by multiple services hides the dependency inside queries scattered across each service's codebase, so a schema change can silently break a consumer nobody remembered was reading that table, with no compile-time or build-time signal.
  • If an organization can't do the full decoupling migration right away, is there an incremental first step?
    Yes -- a common first step is putting each table behind exactly one owning service and forcing every other service that needs that data to go through that owner's API instead of querying the table directly, even before tackling the release-train coordination. That alone removes the hidden-dependency problem and is usually less risky than fixing deployment coordination and data ownership simultaneously.
  • Why does a release train tend to get worse over time rather than naturally getting fixed?
    Because it becomes self-reinforcing: infrequent release opportunities push teams to batch more changes into each release, which raises the risk and coordination overhead per release, which further discourages any team from investing in breaking out of the train -- so left alone, the incentive gradient points toward more coupling and larger, riskier releases, not less.

Like renting 40 separate apartments but requiring every tenant to move in and out on the same day because the building's water and electricity are wired as one shared circuit -- you pay for 40 leases but get none of the freedom of living independently.

saying these in an interview costs you the question

  • thinks having many repos/pipelines is sufficient evidence of a healthy microservices architecture
  • doesn't recognize a shared database queried by multiple services as a coupling problem
  • proposes 'coordinate better' as the fix instead of removing the underlying coupling
  • can't explain why this is worse than just staying a monolith
  • assumes the release train is an acceptable permanent operating model for microservices

context