Course contentsShow
← C#
Lesson 1567 of 1,90814. Background Work and MessagingFree lesson

Vertical vs. Horizontal Worker Scaling

Determine the optimal ratio between CPU-bound and I/O-bound background worker replicas.

In this lesson, you will learn how to determine the optimal balance between scaling your CPU-bound and I/O-bound background worker replicas to maximize throughput without resource contention.

When scaling background workers, you must distinguish between the two primary bottlenecks: CPU-bound tasks (e.g., image processing, encryption) and I/O-bound tasks (e.g., calling external APIs, database writes).

Horizontal vs. Vertical Scaling Logic

  • Horizontal Scaling (adding more instances) is the default strategy for distributed systems. However, adding more replicas when the bottleneck is I/O often leads to "thread starvation" or connection pool exhaustion.
  • Vertical Scaling (increasing resources per instance) is often more efficient for I/O-bound tasks where the bottleneck is network latency or thread context switching, as it reduces the overhead of managing distributed state across multiple processes.

The Optimal Ratio

The goal is to align your MaxDegreeOfParallelism or container replica count with the task profile. For I/O-bound work, your concurrency limit is dictated by the upstream service capacity, not your CPU cores.

Consider this heuristic for your C# workers:

// For CPU-bound tasks:
// Scale horizontally based on available cores.
var workerCount = Environment.ProcessorCount;

// For I/O-bound tasks:
// Scale based on latency and external throughput limits.
// High concurrency is fine because threads are parked waiting for I/O.
var semaphore = new SemaphoreSlim(initialCount: 100); 

If you have a mixed-workload background service, you should physically separate the tasks into different worker pools. If you keep them together, CPU-bound tasks will saturate the thread pool, starving the I/O tasks that could otherwise handle thousands of concurrent requests while waiting for network responses.

Monitor your ThreadPool.PendingWorkItemCount and ThreadPool.CompletedWorkItemCount. If your CPU usage is low but latency is high, you are I/O-bound; add more concurrency within the process (Vertical) before spawning another replica. If your CPU usage is consistently near 80-90% and queue processing is falling behind, you are CPU-bound; deploy more replicas (Horizontal).

Key Takeaway: Scale CPU-bound workers horizontally based on core count, while scaling I/O-bound workers vertically by increasing concurrency limits to match the throughput capacity of the external dependencies.