DEV Community

Cover image for Microservices Are Killing Your Performance (And Here's the Math)
Polliog
Polliog

Posted on

Microservices Are Killing Your Performance (And Here's the Math)

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)
Enter fullscreen mode Exit fullscreen mode

HTTP call within same datacenter (microservices):

HTTP request: 1-5ms (1,000-5,000 microseconds)
Enter fullscreen mode Exit fullscreen mode

That's 1,000x-5,000x slower.

Real-World Example

Scenario: E-commerce checkout flow

Operations needed:

  1. Validate user session
  2. Check product inventory
  3. Calculate shipping cost
  4. Process payment
  5. Create order record
  6. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)│  │
│  └───────────┘  │
└─────────────────┘
Enter fullscreen mode Exit fullscreen mode

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)   │
            └─────────────┘
Enter fullscreen mode Exit fullscreen mode

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:

  1. Tail latency suffers most (p99: +140%)
  2. Resource usage increases dramatically
  3. 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
Enter fullscreen mode Exit fullscreen mode

In a monolith:

Function call: 0.001ms
────────────────────────
Total: 0.001ms
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Microservices (5 services in chain):

Availability: 0.999^5 = 0.995 = 99.5%
Downtime: 3.6 hours/month
Enter fullscreen mode Exit fullscreen mode

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%
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Issues:

  1. Connection exhaustion faster
  2. Lock contention increases (more concurrent transactions)
  3. 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 */ ]
};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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    │           │
│  └─────────────────────┘            │
└─────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Key principles:

  1. Modules communicate via interfaces (not HTTP)
  2. Clear boundaries (like microservices)
  3. Shared database (transaction benefits)
  4. 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)
Enter fullscreen mode Exit fullscreen mode

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     │     └─────────────┘
└─────────────────┘
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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 ✅
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
dshaw0004 profile image
Dipankar Shaw

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.

Collapse
 
polliog profile image
Polliog

At the end, yes. It's all in the hands of the person or company

Collapse
 
adamthedeveloper profile image
Adam - The Developer

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.

  • if you need to deploy in order, you're violating it
  • if your app wont work because one service is down, you're violating it
  • if one goes down, causing others to go down with it, you're violating it

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.

Collapse
 
spo0q profile image
spO0q • Edited

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.