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 (30)

Collapse
 
adamthedeveloper profile image
Adam - The Developer • Edited

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-services.

Collapse
 
polliog profile image
Polliog

You've hit the nail on the head. The "distributed monolith" anti-pattern is everywhere, and it's often worse than an actual monolith because you get all the downsides of distributed systems with none of the benefits.

What you're describing are classic violations of the Fallacies of Distributed Computing. The moment you have tight coupling between services, whether through deployment ordering, cascading failures, or synchronous dependencies, you've basically created a monolith that just happens to communicate over HTTP instead of function calls. And that's the worst of both worlds.
The deployment ordering issue is particularly telling. I've seen teams with elaborate orchestration scripts to deploy services in the "correct" sequence, and it's a giant red flag. If Service A absolutely requires Service B to be deployed first, they're not independent services, they're modules that should live in the same codebase.

Your 90% estimate for startups is generous, I'd argue it's closer to 95%. Most startups don't have the scale, team size, or organizational complexity that justify microservices. They're optimizing for problems they don't have yet while ignoring the ones they do have (shipping features, finding product-market fit, managing burn rate).

The irony is that by the time a startup actually needs microservices, they'll have enough engineering maturity to know when and how to extract them properly. If you're pre Series A and building microservices, you're probably doing it wrong.

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
 
art_light profile image
Art light

This is a fantastic breakdown—clear, data-driven, and refreshingly honest about the real trade-offs instead of repeating buzzwords. I really like your conclusion around modular monoliths; it aligns with my own experience that architecture should solve organizational needs, not just follow trends. I’d love to see a follow-up on modular monolith patterns or real migration stories, because this kind of grounded analysis is incredibly valuable.

Collapse
 
polliog profile image
Polliog

happy to hear^^

Collapse
 
art_light profile image
Art light

😎

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.

Collapse
 
polliog profile image
Polliog

Exactly. The cargo cult problem is real. Teams see Netflix/Amazon use microservices and think "we should too" without understanding the why.

When Netflix talks about microservices, they have infinite more resources than an avarage company. The context is completely different. It's like seeing an F1 team and thinking your daily commuter needs a $10M racing car. You need a Toyota, not a Formula 1.

Conference talks show the success stories but hide the prerequisites: dedicated platform teams, massive ops budgets, mature DevOps culture. A 10-person startup copying Netflix's architecture is setting up for failure.

The right question isn't "should we use microservices?" It's "what's the simplest solution to our actual problem?" Most of the time, microservices isn't it.

Collapse
 
apisurfer profile image
apisurfer

I think a decade of development was largely wasted on microservices dogma. For a while, everyone built everything as microservices, regardless of scale.

One overlooked issue is uptime. High availability, say 99%, is already hard to achieve for a single service. Once you start chaining services, downtime compounds fast. Three services in the chain at 99% uptime each give you ~97% overall uptime, and it only gets worse from there.

Collapse
 
polliog profile image
Polliog

I totally agree

Collapse
 
maixuanhan profile image
Han Mai • Edited

Complexity comparison is totally incorrect. I rather maintain 100 microservices with a clear design than maintain an equivalent monolithic app.

Collapse
 
lukerrr profile image
Luke

L take. Maintaining 100 microservices by yourself is essentially impossible. Maintaining a monolith with a similar number of routes and a single DB is objectively simpler. If the opposite were true, startups would launch MVP products as microservices, not monoliths. However, that almost never happens for a reason.

Collapse
 
polliog profile image
Polliog

I mean... It's a personal thing, if you can do it, do it.

Collapse
 
phcostabh profile image
Philippe Santana Costa • Edited

That's it! Great write-up. Very objective and straight to the point. Until you get the bounded context and granularity right to properly decompose your monolith, there's no point in starting with a microservice. Breaking up a monolith must be an empirical process. We need enough knowledge from real feedback before rushing into rearranging things.

Also, one must ALWAYS be aware of The Fallacies of Distributed Computing.

Collapse
 
aniruddhaadak profile image
ANIRUDDHA ADAK

Useful

Collapse
 
gramli profile image
Daniel Balcarek

Great post! I immediately liked it after reading just this sentence: "The reality: For most systems, microservices add latency, complexity, and failure points without meaningful benefits."

The decision matrix is also great!

Just one thing to consider: in the Database Contention section, you're assuming that all microservices share the same database, but each service should ideally have its own database => Database per Service pattern. So this might not be a well-suited metric for microservices.

Collapse
 
nandofm profile image
Fernando Fornieles

Really interesting article!

I'm curious about the microservices chain. Is it really necessary or this integration/communication between microservices could be decoupled through a event system like Kafka?

My rules of thumb for distributed architectures are:

  • Launch domain/integration events to communicate services. This promote a loosely coupled system.
  • If one service needs another one to finish its processing maybe both services belong to the same domain and should be merged into a single service.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.