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 fundamental problem: Microservices communicate over the network. Networks are slow.
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.
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.
In databases, we know about N+1 queries. Microservices have N+1 services.
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
Let's measure actual overhead with a controlled experiment.
System: Simple CRUD application
4 entities: Users, Products, Orders, Payments
10,000 requests/sec load
AWS EC2 t3.medium instances
┌─────────────────┐
│ Application │
│ (Node.js) │
│ │
│ ┌───────────┐ │
│ │ Database │ │
│ │ (Postgres)│ │
│ └───────────┘ │
└─────────────────┘
Configuration:
Single Node.js process
Connection pooling (20 connections)
Redis cache (same instance)
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ User │ │ Product │ │ Order │ │ Payment │
│ Service │ │ Service │ │ Service │ │ Service │
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │ │
└─────────────┴─────────────┴─────────────┘
│
┌──────┴──────┐
│ Postgres │
│ (shared) │
└─────────────┘
Configuration:
4 Node.js services
Service mesh (Istio)
Same Postgres database
Redis cache (shared)
MetricMonolithMicroservicesDifferencep50 latency12ms18ms+50%p95 latency25ms45ms+80%p99 latency50ms120ms+140%Throughput10,000 req/s8,500 req/s-15%CPU usage45%65%+44%Memory usage512MB2GB+300%Network I/O50 MB/s180 MB/s+260%
Key findings:
Tail latency suffers most (p99: +140%)
Resource usage increases dramatically
Throughput decreases despite "scalability"
Let's dissect where time is spent in a microservice 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.
Microservices amplify failure rates.
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.
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.
Microservices don't solve database bottlenecks - they make them worse.
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)
Test: 1,000 concurrent users
ArchitectureConnections UsedLock Wait TimeQuery TimeMonolith20-305ms10msMicroservices60-8025ms18ms
Microservices use 3x connections and have 5x lock contention.
Every network call requires serialization/deserialization.
Test: Serialize typical API response (user object with nested data)
const user = {
id: 123,
name: "John Doe",
email: "[email protected]",
profile: { /* 50 fields */ },
orders: [ /* 10 orders */ ]
};
Benchmarks (Node.js):
OperationTimeIn-memory object access0.001msJSON.stringify()0.15msJSON.parse()0.20msNetwork transmission0.50msTotal per call0.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.
I'm not saying "never use microservices."
Use microservices when:
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.
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.
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.
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.
Best of both worlds: monolith structure with microservices discipline.
┌─────────────────────────────────────┐
│ 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)
MetricMicroservicesModular MonolithImprovementLatency (p50)18ms12ms33% fasterLatency (p99)120ms50ms58% fasterThroughput8,500 req/s10,000 req/s18% higherMemory2GB512MB75% lessComplexityHighMediumSimpler
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)
Don't rewrite monolith → microservices overnight.
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)
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.
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
Scenario: 10,000 requests/second application
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
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.
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)
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
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
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.
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.
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:
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!
1
7
0