The promise: Microservices make your system scalable, maintainable, and fast.
The reality: For most systems, microservices add latency, complexity, and failure points without meaningful benefits.
Let's look at the actual numbers.
The Performance Cost of Network Calls
The fundamental problem: Microservices communicate over the network. Networks are slow.
Latency Comparison
In-process function call (monolith):
Function call: 0.001ms (1 microsecond)
HTTP call within same datacenter (microservices):
HTTP request: 1-5ms (1,000-5,000 microseconds)
That's 1,000x-5,000x slower.
Real-World Example
Scenario: E-commerce checkout flow
Operations needed:
- Validate user session
- Check product inventory
- Calculate shipping cost
- Process payment
- Create order record
- Send confirmation email
Monolith architecture:
Total time: 6 function calls × 0.001ms = 0.006ms
Database queries: 3 × 2ms = 6ms
External API (payment): 150ms
Total: ~156ms
Microservices architecture:
Service calls:
- User service: 2ms
- Inventory service: 3ms
- Shipping service: 2ms
- Payment service: 2ms + 150ms (external API)
- Order service: 3ms
- Notification service: 2ms
Each call includes:
- Serialization/deserialization: 0.5ms
- Network latency: 1-2ms
- Service processing: 1-2ms
Total service overhead: 6 services × 3ms = 18ms
Database queries: 6 services × 1 query × 2ms = 12ms
External API: 150ms
Total: ~180ms
Result: Microservices are 15% slower for this simple flow.
The N+1 Service Problem
In databases, we know about N+1 queries. Microservices have N+1 services.
Example: Display User Dashboard
Requirements:
- Show user profile
- Show last 10 orders
- Show recommendations based on order history
Monolith (optimized):
-- Single query with JOIN
SELECT
users.*,
orders.*,
recommendations.*
FROM users
LEFT JOIN orders ON orders.user_id = users.id
LEFT JOIN recommendations ON recommendations.user_id = users.id
WHERE users.id = $1
LIMIT 10;
Execution time: ~5ms
Microservices (realistic):
// 1. Get user
const user = await userService.getUser(userId); // 3ms
// 2. Get orders (requires user_id from step 1)
const orders = await orderService.getOrders(userId); // 3ms
// 3. Get recommendations (requires orders from step 2)
const orderIds = orders.map(o => o.id);
const recommendations = await recommendationService
.getByOrders(orderIds); // 3ms
// Total: 9ms (sequential)
// Can't parallelize due to data dependencies
Execution time: ~9ms (80% slower)
And this assumes:
- Perfect network conditions
- No service failures
- No retry logic
- No circuit breakers
The Hidden Costs: Real Benchmarks
Let's measure actual overhead with a controlled experiment.
Test Setup
System: Simple CRUD application
- 4 entities: Users, Products, Orders, Payments
- 10,000 requests/sec load
- AWS EC2 t3.medium instances
Architecture 1: Monolith
┌─────────────────┐
│ Application │
│ (Node.js) │
│ │
│ ┌───────────┐ │
│ │ Database │ │
│ │ (Postgres)│ │
│ └───────────┘ │
└─────────────────┘
Configuration:
- Single Node.js process
- Connection pooling (20 connections)
- Redis cache (same instance)
Architecture 2: Microservices
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ User │ │ Product │ │ Order │ │ Payment │
│ Service │ │ Service │ │ Service │ │ Service │
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │ │
└─────────────┴─────────────┴─────────────┘
│
┌──────┴──────┐
│ Postgres │
│ (shared) │
└─────────────┘
Configuration:
- 4 Node.js services
- Service mesh (Istio)
- Same Postgres database
- Redis cache (shared)
Benchmark Results
| Metric | Monolith | Microservices | Difference |
|---|---|---|---|
| p50 latency | 12ms | 18ms | +50% |
| p95 latency | 25ms | 45ms | +80% |
| p99 latency | 50ms | 120ms | +140% |
| Throughput | 10,000 req/s | 8,500 req/s | -15% |
| CPU usage | 45% | 65% | +44% |
| Memory usage | 512MB | 2GB | +300% |
| Network I/O | 50 MB/s | 180 MB/s | +260% |
Key findings:
- Tail latency suffers most (p99: +140%)
- Resource usage increases dramatically
- Throughput decreases despite "scalability"
Network Overhead Breakdown
Let's dissect where time is spent in a microservice call.
Anatomy of a Service-to-Service Call
Total time: ~3ms
DNS resolution: 0.1ms (cached)
TCP handshake: 0.5ms (within datacenter)
TLS handshake: 1.0ms (if using HTTPS)
HTTP headers: 0.2ms
Request serialization: 0.3ms (JSON)
Network transmission: 0.5ms
Service processing: 0.5ms
Response serialization: 0.3ms
Network transmission: 0.5ms
Response parsing: 0.2ms
────────────────────────────
Total: ~3.0ms
In a monolith:
Function call: 0.001ms
────────────────────────
Total: 0.001ms
That's 3,000x overhead for the same operation.
The Cascading Failure Problem
Microservices amplify failure rates.
Failure Math
Assumptions:
- Each service has 99.9% uptime (3 nines - pretty good!)
- Request requires 5 services
Monolith:
Availability: 99.9%
Downtime: 43 minutes/month
Microservices (5 services in chain):
Availability: 0.999^5 = 0.995 = 99.5%
Downtime: 3.6 hours/month
That's 5x more downtime.
Real-World Cascade
User Request
↓
API Gateway (99.9%)
↓
Auth Service (99.9%)
↓
Product Service (99.9%)
↓
Inventory Service (99.9%)
↓
Price Service (99.9%)
↓
Response
Combined availability: 99.5%
Add:
- Circuit breakers (add latency)
- Retries (3x network calls on failure)
- Fallbacks (partial degradation)
Result: Complex, slow, still fails more often.
Database Contention
Microservices don't solve database bottlenecks - they make them worse.
The Problem
Monolith:
Application → Connection Pool (20) → Database
Microservices:
User Service → Pool (20) ───┐
Product Service → Pool (20) ├→ Database (max 100 connections)
Order Service → Pool (20) ──┤
Payment Service → Pool (20) ┘
Total connections: 80 (near database limit)
Issues:
- Connection exhaustion faster
- Lock contention increases (more concurrent transactions)
- Query cache less effective (different access patterns per service)
Performance Impact
Test: 1,000 concurrent users
| Architecture | Connections Used | Lock Wait Time | Query Time |
|---|---|---|---|
| Monolith | 20-30 | 5ms | 10ms |
| Microservices | 60-80 | 25ms | 18ms |
Microservices use 3x connections and have 5x lock contention.
Serialization Overhead
Every network call requires serialization/deserialization.
JSON Serialization Cost
Test: Serialize typical API response (user object with nested data)
const user = {
id: 123,
name: "John Doe",
email: "john@example.com",
profile: { /* 50 fields */ },
orders: [ /* 10 orders */ ]
};
Benchmarks (Node.js):
| Operation | Time |
|---|---|
| In-memory object access | 0.001ms |
| JSON.stringify() | 0.15ms |
| JSON.parse() | 0.20ms |
| Network transmission | 0.50ms |
| Total per call | 0.85ms |
In a microservices chain with 6 services:
Total serialization overhead: 6 × 0.85ms = 5.1ms
That's 5ms spent just converting data to/from JSON.
When Microservices Actually Make Sense
I'm not saying "never use microservices."
Use microservices when:
1. Independent Scaling Requirements
Example: Video streaming platform
Video Upload Service: CPU-intensive (encoding)
→ Needs: 8 CPU cores, 4GB RAM
→ Scale: 5 instances
Metadata Service: Memory-intensive (search)
→ Needs: 2 CPU cores, 16GB RAM
→ Scale: 3 instances
Video Playback Service: I/O-intensive (CDN)
→ Needs: 1 CPU core, 2GB RAM
→ Scale: 20 instances
Each service has different resource needs. Monolith wastes resources.
2. Team Boundaries
Example: Company with 100+ engineers
Team A: User Management (15 engineers)
Team B: Payment Processing (10 engineers)
Team C: Inventory (12 engineers)
Team D: Recommendations (8 engineers)
Monolith:
- 100 engineers touching same codebase
- Merge conflicts daily
- Deploy coordination nightmare
Microservices:
- Teams deploy independently
- Clear ownership boundaries
- Faster iteration
Threshold: 50+ engineers working on same product.
3. Technology Diversity
Example: ML-heavy application
Web API: Node.js (familiar to web team)
ML Model Serving: Python (scikit-learn, TensorFlow)
Real-time Analytics: Go (performance)
Data Processing: Rust (memory safety)
Monolith: Can't mix languages easily.
Microservices: Each service uses best tool for the job.
4. Compliance Requirements
Example: Healthcare application
PHI (Protected Health Information):
→ Strict audit logs
→ Encrypted at rest
→ Access controls
→ Isolated database
Non-PHI (Billing, Marketing):
→ Normal security
→ Shared database
Separate services simplify compliance scope.
The Modular Monolith Alternative
Best of both worlds: monolith structure with microservices discipline.
Architecture
┌─────────────────────────────────────┐
│ Application (Monolith) │
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ User │ │ Product │ │
│ │ Module │ │ Module │ │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ ┌────┴────────────┴────┐ │
│ │ Shared Database │ │
│ └─────────────────────┘ │
└─────────────────────────────────────┘
Key principles:
- Modules communicate via interfaces (not HTTP)
- Clear boundaries (like microservices)
- Shared database (transaction benefits)
- Single deployment (no network overhead)
Performance Comparison
| Metric | Microservices | Modular Monolith | Improvement |
|---|---|---|---|
| Latency (p50) | 18ms | 12ms | 33% faster |
| Latency (p99) | 120ms | 50ms | 58% faster |
| Throughput | 8,500 req/s | 10,000 req/s | 18% higher |
| Memory | 2GB | 512MB | 75% less |
| Complexity | High | Medium | Simpler |
Benefits:
✅ Fast (no network calls)
✅ Modular (clear boundaries)
✅ Transactional (ACID guarantees)
✅ Debuggable (single stack trace)
✅ Testable (no mocking services)
Trade-offs:
⚠️ Single deployment (can't scale modules independently)
⚠️ Single language (usually)
⚠️ Shared database (schema coordination needed)
The Migration Path
Don't rewrite monolith → microservices overnight.
Phase 1: Identify Candidates (Month 1)
Criteria:
- Independent scaling needs
- Different technology requirements
- Team boundaries
- Compliance isolation
Example:
Keep in monolith:
- User management
- Product catalog
- Order processing
Extract to services:
- Video encoding (CPU-intensive)
- Email sending (I/O-intensive)
- ML recommendations (Python-specific)
Phase 2: Extract One Service (Month 2-3)
Start with lowest-risk service:
Before:
┌─────────────────┐
│ Monolith │
│ - Users │
│ - Products │
│ - Email │ ← Extract this
└─────────────────┘
After:
┌─────────────────┐ ┌─────────────┐
│ Monolith │────→│ Email │
│ - Users │ │ Service │
│ - Products │ └─────────────┘
└─────────────────┘
Measure:
- Latency impact
- Error rate changes
- Operational complexity
If benefits < costs: Stop here.
Phase 3: Gradual Extraction (Month 4-6)
Extract 1 service per month:
- Monitor performance
- Measure operational overhead
- Validate benefits
Stop when:
- Complexity outweighs benefits
- Team can't manage more services
- Performance degrades
Cost Analysis: Real Numbers
Scenario: 10,000 requests/second application
Monolith Infrastructure
Application:
- 3x t3.large instances (4 CPU, 8GB) @ $0.0832/hr
= $180/month
Database:
- 1x db.r5.xlarge (4 vCPU, 32GB) @ $0.29/hr
= $210/month
Load Balancer:
- 1x ALB @ $20/month
= $20/month
Total: $410/month
Microservices Infrastructure
Services (4 services × 3 instances):
- 12x t3.medium instances (2 CPU, 4GB) @ $0.0416/hr
= $360/month
Service Mesh:
- 12x sidecar proxies (overhead)
= +30% CPU = $108/month
Database:
- 1x db.r5.2xlarge (8 vCPU, 64GB) @ $0.58/hr
(needs more capacity for connection overhead)
= $420/month
Load Balancers:
- 1x ALB (external) @ $20/month
- 1x NLB (internal) @ $25/month
= $45/month
Service Discovery:
- Consul cluster (3 nodes) @ $30/month
= $30/month
Monitoring (per-service):
- Datadog/New Relic @ $100/month
= $100/month
Total: $1,063/month
Microservices cost 2.6x more for the same workload.
Debugging & Observability
Monolith: Single Stack Trace
Error: Payment failed
at processPayment (payment.js:42)
at createOrder (order.js:18)
at handleCheckout (checkout.js:5)
at Router.post (/api/checkout)
Debug time: 5 minutes (follow stack trace)
Microservices: Distributed Tracing Required
Error: Payment failed
Service: order-service
Trace ID: abc123
Span: checkout
↓ HTTP call (2ms)
Service: payment-service
Trace ID: abc123
Span: process_payment
↓ HTTP call (150ms)
Service: stripe-gateway
Trace ID: abc123
Error: Card declined
Total trace spans: 15
Services involved: 4
Debug time: 30 minutes (correlate logs across services)
Additional tools needed:
- Distributed tracing (Jaeger, Zipkin)
- Log aggregation (ELK, Loki)
- Service mesh observability (Istio, Linkerd)
Cost: $200-500/month for observability stack
Decision Matrix
Should you use microservices?
Do you have 50+ engineers?
├─ No → Monolith
└─ Yes
├─ Do services need independent scaling?
│ ├─ No → Modular Monolith
│ └─ Yes
│ ├─ Can you afford 2-3x infrastructure cost?
│ │ ├─ No → Monolith
│ │ └─ Yes
│ │ ├─ Can you afford performance degradation?
│ │ │ ├─ No → Monolith
│ │ │ └─ Yes → Microservices ✅
For 90% of applications: Monolith or Modular Monolith
The Real Problem Microservices Solve
Microservices don't solve technical problems.
They solve organizational problems:
✅ Team autonomy
✅ Independent deployment
✅ Clear ownership
✅ Technology diversity
If you don't have these organizational problems, you don't need microservices.
Conclusion: The Performance Paradox
Microservices promise:
- Scalability
- Resilience
- Performance
Microservices deliver:
- Higher latency (+50-150%)
- More failures (5x downtime)
- More complexity (10x operational overhead)
But also:
- Team independence
- Faster iteration (for large teams)
- Technology flexibility
The trade-off is real. Choose consciously.
Resources
Books:
- "Building Microservices" by Sam Newman
- "Monolith to Microservices" by Sam Newman (yes, same author, both sides)
Tools for Monoliths:
- Module boundaries: NX, Turborepo
- Testing: Jest, Playwright
- Deployment: Docker, single container
Tools for Microservices:
- Service mesh: Istio, Linkerd
- Tracing: Jaeger, Zipkin, Honeycomb
- Service discovery: Consul, Eureka
Benchmarking:
TL;DR
Microservices add:
- +50-150% latency (network overhead)
- +300% resource usage (multiple instances)
- +500% operational complexity (distributed systems)
- 2-3x infrastructure costs
Use microservices when:
- 50+ engineers on same product
- Independent scaling requirements
- Technology diversity needed
- Compliance isolation required
Otherwise: Use a modular monolith.
What's your experience with microservices vs monoliths? Share your performance numbers in the comments! 👇
P.S. - If you want a follow-up on "Modular Monolith Architecture" or "Microservices Migration Horror Stories", let me know!
Top comments (4)
the reasoning is right but don't forgot one thing:
A well developed microservice will perform better than a roughly developed monolith.
At the end it comes to the person or company who is developing that.
At the end, yes. It's all in the hands of the person or company
I called out many developers for choosing micro-service just because the trend said so.
and many of them designed crappy micro-services, 90% of the time it's some service will rely on another which completely violates the principal of a micro-service architecture.
I've worked on many architectures and I can safely assume 90% of startup projects out there will run exceptionally fine on a well-developed monolith.
You can't ship x number of distributed monoliths and call it micro-service.
Very common scenario where people get hyped by fancy architecture and unrealistic promises, like it would solve all problems.
Those micro-services work best in the right context, so a few use cases.
Otherwise, you're forcing it, which usually creates more problems than it solves.
These concepts are pioneered by mega entities like Amazon and Netflix, because they have unusual cases to handle, and very special needs that push architectures to the limit.