The microservices pitch is seductive: independent deployments, team autonomy, technology heterogeneity, targeted scaling. Each service can use the best tool for the job. Teams can deploy without coordinating with anyone else.
These benefits are real. So are the costs that don’t make it into the architecture review slides.
The Tax You Pay for Distribution
When you move from a monolith to microservices, you don’t eliminate complexity - you redistribute it. The logic that was a function call becomes a network call. The transaction that was atomic becomes an eventual consistency problem. The stack trace that showed you exactly what happened becomes a distributed trace spanning six services.
The tax is proportional to the number of services and to how tightly coupled those services are. Organizations that extract microservices well - clear domain boundaries, minimal cross-service coordination - pay a manageable tax. Organizations that extract microservices badly - services that share databases, teams that need to coordinate every deployment - pay a brutal one.
Specific Hidden Costs
Service Discovery and Health Checking
In a monolith, calling a function is O(1). In a microservices architecture, service A needs to know where service B is running, whether it’s healthy, and how to route to it. You need a service registry (Consul, Kubernetes services, AWS Service Discovery), health check endpoints in every service, and logic to handle the case where the registry is wrong.
This is solvable - Kubernetes makes it mostly automatic - but “mostly automatic” still means engineers need to understand it when things go wrong.
Distributed Tracing
When a user request spans 7 services, debugging a latency issue requires tracing the request through all 7. Without distributed tracing infrastructure, you’re correlating logs across services by hand, which is miserable.
Setting up distributed tracing properly requires:
- A tracing backend (Jaeger, Zipkin, Honeycomb, Datadog APM)
- Instrumentation in every service (OpenTelemetry)
- Trace context propagation through every HTTP client and message queue
- Engineers trained to read distributed traces
The infrastructure costs $100-500/month for a moderate-size deployment. The engineering time to instrument every service properly is 1-2 weeks per service. And traces only help if engineers know how to use them - which requires investment in tooling documentation and training.
Network Failure Handling
In a monolith, function calls don’t fail with connection refused, timeout, or 503 Service Unavailable. In a microservices architecture, every service call can fail for reasons completely unrelated to your code - network blips, the other service is deploying, the other service is at capacity.
Every service client needs:
- Retry logic with exponential backoff
- Circuit breakers to stop sending traffic to unhealthy services
- Timeout handling (and agreed-upon timeouts between teams)
- Fallback behavior when the service is unavailable
This is 2-4 days of engineering work per service pair. At 20 services with an average of 3 upstream dependencies each, that’s 60 service pairs and several months of engineering time. Libraries help (Resilience4j for Java, go-resilience, Circuit Breaker in Polly for .NET) but the work is real.
Data Consistency Without Transactions
In a monolith with a shared database, a transaction that touches multiple tables is atomic. Service A and service B each have their own database. A business operation that requires updating both - say, creating an order and decrementing inventory - cannot be wrapped in a single transaction.
The solutions (Saga pattern, outbox pattern, eventual consistency with compensating transactions) work. They are also significantly harder to implement correctly and reason about than database transactions.
When you have an inconsistency bug in a microservices architecture, finding it requires understanding the distributed state across multiple services’ databases. When you have an inconsistency bug in a monolith, you read the transaction log.
| Issue | Monolith | Microservices |
|---|---|---|
| ACID transactions | Native | Saga pattern |
| Debugging latency | Stack trace | Distributed trace |
| Data consistency bugs | Query one DB | Cross-service correlation |
| Deployment coordination | None | Service mesh / versioned APIs |
| Local development | One process | docker-compose with 12 services |
Local Development Complexity
Running a feature that spans 3 services locally requires running 3 services, their dependencies (databases, queues), and having them all configured to talk to each other. This is why docker-compose files with 12 services are common in microservices shops.
The cognitive load of onboarding a new engineer is higher. The time to “first feature contributed” is longer. Engineers need to understand not just their service but the contracts with upstream and downstream dependencies.
Operational Overhead Per Service
Each service needs:
- A CI/CD pipeline
- Monitoring and alerting
- Logging configured and routed
- A deployment configuration (Kubernetes manifests, Helm chart)
- A runbook for common failure modes
- An oncall rotation that understands it
In practice, teams try to templatize this. “We have a service template that includes all of this.” Templates help but don’t eliminate the overhead. At 50 services, maintaining 50 deployment configurations is a real engineering cost.
When Microservices Are Right
The benefits are real for:
- Organizations with multiple teams that need to deploy independently
- Services with genuinely different scaling characteristics (video processing vs. API serving)
- Systems where technology diversity is necessary (ML inference in Python, business logic in Java)
- Very large codebases where a monolith’s deployment has become a coordination problem
The honest threshold: microservices make sense when the pain of the monolith (long deployments, team coordination overhead, inability to scale specific bottlenecks) exceeds the operational tax of distribution. For most early-stage startups, that threshold is not reached until you have 10+ engineers or specific technical requirements.
The Modular Monolith Alternative
Many teams get the benefits without the full distributed systems tax by building a well-structured monolith: separate modules with clear interfaces, separate CI pipelines for different domains, separate databases per module even in a single process.
A modular monolith can be extracted into services when the need is clear. A distributed system cannot be un-distributed easily.
Bottom Line
Microservices have real benefits and real costs. The benefits - team autonomy, independent scaling, technology flexibility - are genuine and worth pursuing when you have the problems they solve. The costs - distributed tracing, network failure handling, eventual consistency, local development complexity, per-service operational overhead - are also genuine and rarely fully accounted for in architecture decisions. The question to ask is not “are microservices better?” but “are the benefits greater than the operational tax, given our specific team size and technical requirements?” That answer is “no” more often than the pitch deck implies.
Comments