But when do you start putting services back together?
That’s the question no one wants to answer. The microservices evangelists have moved on to agentic AI and platform engineering, anything that keeps the distributed systems dream alive. Meanwhile, engineering teams from startups to $8B enterprises are quietly doing the architectural equivalent of a U-turn, merging their fragmented services back into something coherent.
The truth is uncomfortable: 80% of microservice projects should have been modular monoliths from the start. That’s not my opinion, it’s the sentiment echoed across engineering forums, and increasingly, in production post-mortems.
This piece isn’t about bashing microservices. It’s about the underdocumented reality of merging them back together, the data nightmares that await, and the pattern that actually works.
The Over-Fragmentation Problem Nobody Talks About
Picture this: 15 microservices maintained by 6 developers. Half of them can’t be deployed independently anyway. A small feature means touching four repos, updating three go.mod files, and coordinating releases across teams that don’t exist.
This is the reality of over-fragmentation in small teams. The 2026 architectural inflection point has arrived, and the trend of reverting from microservices to consolidated systems is no longer whispered in private channels, it’s happening in production.
The root cause is a category error that’s been repeated thousands of times. Microservices were designed to resolve an organizational coordination bottleneck, not a technical execution bottleneck. When you have 500+ engineers, independent services prevent teams from stepping on each other’s toes. But apply that same logic to a team of 12, and you’ve just created distributed overhead without any of the organizational benefits.
The Data Problem: The Part Everyone Skips
Merging code is easy. Shipping it is easy. The nightmare begins when you look at the databases.
A recurring theme across teams that have actually done this consolidation: the data side is where projects go to die. Separate schemas, event flows built on eventual consistency assumptions, jobs that poll databases like they’re waiting for a bus that might never come.
One team that went through this in production described their approach as surprisingly pragmatic:
In the first cut, we migrated the data schema-unchanged into the larger services and colocated the code. Callers would still make multiple calls into the service as if it were 2 separate services. Then we combined the orchestration calls into a single gRPC call and added transactions spanning both schemas.
The result? They eliminated consistency and latency issues they’d been fighting for months. And they never re-architected the schemas, it was good enough, and the model was easy to work with.
This “good enough” approach deserves more attention. The instinct to do the perfect domain-driven data remodel during a merge is wrong. The merge itself delivers most of the value, the schema reorganization is optimization you can defer indefinitely.
When the Monolith Becomes the “Distributed Monolith”
The worst case scenario isn’t having too many services. It’s having services that can’t function independently but still carry all the overhead of distributed systems.
Think about what happens when services share database tables, require lockstep deployment, and chain synchronous HTTP calls. You end up with an unmaintainable system that’s fragile over the network yet impossible to deploy independently. In the examination of the hidden operational costs of microservices, the distributed monolith pattern emerges as the most common architectural pathology in the industry.
The failure compounds when data management was never properly solved. One engineer working at an $8B company described a particularly brutal scenario: 70+ product teams created 200-300 microservices without solving the data problem, and the original monolith was never retired. The result? A monolith processing over 1B queries/day, plus 70+ Postgres clusters where each team duplicated data because it was easier than figuring out which service to query.
Here’s the kicker: when they audited the system, they found the data broke down into just 12 distinct domains. Most of those 300 services were Byzantine complexity wrapped around duplicated data.
The Hidden Costs of Staying Fragmented
If you’re still running 30 services with a team of 10, you’re bleeding money in ways that don’t show up on your cloud bill directly:
Latency tail amplification. When a user request fans out to 12 downstream microservices, each with a 99th percentile (p99) latency SLA of 1%, the probability that your user experiences p99 latency becomes 11.36%. In a call graph of 30 services, that jumps to 26.03%. Over a quarter of your user requests are hitting worst-case latency, and you’re chasing distributed tracing breadcrumbs to figure out which service is the bottleneck this week.
The observability tax. Debugging a monolith is a single stack trace with local log context. Debugging a distributed system requires OpenTelemetry instrumentation, W3C trace context propagation across every HTTP header and message queue, and then you hit the sampling dilemma: 100% sampling blows your APM budget, while 1-5% sampling means your rare p99.9 latency spikes vanish without a trace. If you’re running Kafka, context propagation often breaks entirely when a producer forgets to serialize tracing headers.
Kubernetes maintenance. Every microservice setup eventually means managing clusters, and the operational and financial cost of maintaining microservices is staggering. With a service mesh like Istio, every application pod runs an Envoy sidecar consuming 0.1-0.5 vCPU and 50MB-128MB+ of memory. In a system with 30 microservices running 3 replicas across staging and production, you have 180+ sidecars idle-consuming resources just to route localhost traffic.
The Reverse Strangler Fig: A Pattern That Works
The good news? You don’t need a ground-up rewrite. The Reverse Strangler Fig Pattern lets you consolidate incrementally, keeping the system operational at every phase:
Phase 1: In-process client facades. Wrap existing HTTP/gRPC clients in clean internal interfaces. This is pure refactoring, no behavior change.
Phase 2: Codebase ingestion. Import service source trees into a monorepo as bounded modules. The code moves, but it still communicates over network boundaries initially.
Phase 3: Swap network transport for direct invocation. Replace HTTP calls with in-process method calls. This is where the latency wins materialize.
Phase 4: Logical database consolidation. Migrate databases into schemas within a primary database. Use pg_dump/pg_restore or foreign data wrappers. This is the hard part.
Phase 5: Infrastructure decommissioning. Tear down clusters, sidecars, and auxiliary message queues.
This pattern is proven at massive scale. Amazon Prime Video’s landmark engineering post-mortem documented a 90% cost reduction by consolidating a distributed monitoring service into a monolith on EC2/ECS. Segment’s “Goodbye Microservices” retrospective chronicled consolidating 140+ destination services into a single worker application called Centrifuge, restoring stability and dramatically increasing velocity.
The 6-Container Developer Nightmare
Let’s zoom in on a smaller but deeply relatable case. A developer running a personal deals aggregation app had decomposed it into 4 Go microservices (backend, scraper, mailer, notifier), each with its own go.mod, Dockerfile, and main loop. Running locally required orchestrating 6 simultaneous containers.
The evidence of over-fragmentation was brutal:
- Database connection retry loops copy-pasted across 4 services
threadandsubscriberstruct definitions duplicated in 4 separate places- Hardcoded
host="postgres"fallbacks that broke containerized environments
The fix? Consolidate into a single Go binary with a unified CLI:
serve # Run the HTTP REST API server
ingest # Fetch and persist deals from the Atom feed
mail # Run the daily newsletter job
notify # Run the real-time Slack/webhook check
worker # Run the in-process background scheduler
Local development went from orchestrating 6 containers to go run ., one command, one process, one database connection. The developer labeled this as a P1 priority with “High” impact. For a personal project.
If this pattern holds for a single developer’s side project, imagine what it means for your team.
The Discipline Question
The pushback to consolidation always sounds the same: “Won’t the monolith decay into a big ball of mud?”
It’s a fair concern. The modular monolith isn’t a return to the chaotic “Big Ball of Mud” architectures of the past. It’s an intentional, highly structured design pattern that enforces domain separation at compile time while maintaining a single runtime binary.
The enforcement mechanisms are mature:
- Go’s internal packages prevent external imports of private implementations
- ArchUnit for JVM projects fails CI builds when boundaries are violated
- Shopify’s Packwerk scans Ruby and TypeScript code against explicit boundary rules
- PostgreSQL schema isolation with no cross-schema foreign keys preserves data boundaries
Shopify, which processed $235.9 billion in GMV in 2023, runs a massive modular monolith on Ruby on Rails and MySQL. They built Packwerk specifically to enforce boundary encapsulation without decomposing into microservices. If Shopify can run on a monolith exploring real-world cases where companies rejected microservices in favor of simpler architectures, your 15-service startup probably can too.
When Microservices Actually Make Sense
Before you rage-delete your Docker Compose file, let’s be clear: microservices remain legitimate under specific conditions.
Radically asymmetric hardware requirements. If 98% of your application handles standard HTTP traffic but 2% runs GPU-intensive ML inference, extract that worker service. Running both in the same container wastes compute.
Scoped compliance perimeters. PCI-DSS and HIPAA often require network-isolated workloads for cardholder data or ePHI. Isolation minimizes the compliance audit surface.
Conway’s Law at enterprise scale. Past 150-200 engineers organized into autonomous business units with truly disparate roadmaps, coordination overhead in a single repo can degrade velocity. At that scale, service boundaries map to organizational boundaries.
For everyone else, and that’s most of you reading this, the case for startups defaulting to monoliths is overwhelming. The architectural minimalism movement isn’t about being lazy, it’s about recognizing that over-engineering rarely pays off the way the diagrams promise.
Merging microservices isn’t admitting failure. It’s recognizing that architecture should serve your team, not the other way around. The teams that have done this, from a single developer consolidating a personal app to engineers cleaning up $8B company sprawl, report the same results: fewer operational incidents, faster feature delivery, and fewer “works on my machine” moments.
The data problem is real, but it’s tractable. You can migrate schemas as-is, keep multiple service calls temporarily, and let direct in-process invocations deliver the consistency and latency wins before you tackle schema reorganization. The “good enough” approach is often the right one.
The industry spent a decade learning how to decompose. The next decade is about learning when to recompose. The teams that figure this out first are going to ship faster, spend less, and sleep better.
The question isn’t whether you can run 30 microservices with a 6-person team. It’s whether you can afford to keep pretending you should.




