DEV Community

Cover image for Designing a Distributed Task Queue Architecture for Code Execution at Scale
Amitesh0512
Amitesh0512

Posted on Originally published at amiteshsurwar.com

Designing a Distributed Task Queue Architecture for Code Execution at Scale

Quick Answer

distributed task queue architecture for code execution: A distributed task queue architecture decouples ingestion, queuing, and execution, using Service Bus with session routing, Azure Container Apps warm pools, and caching to deliver <1 s latency for 13k QPS while keeping costs under $0.02 per 1k jobs.

Distributed Task Queue Architecture for Code Execution: Lessons from a Live Contest Platform

Monolithic Runners: Resource, Security, Cost Limits

Competitive‑coding services that see 10–20 k submissions per minute cannot rely on a single process that compiles and runs user code. The naive "run‑in‑process" model quickly becomes a bottleneck in three ways:

  • Resource contention – a single VM or container must juggle CPU, memory, and disk for every job, leading to unpredictable latency spikes.
  • Security bleed – a malicious submission can escape the sandbox if the process runs with elevated privileges.
  • Cost blow‑up – scaling a monolithic runner horizontally is expensive; you pay for idle cores while the queue is idle.

The solution is to decouple ingestion, queuing, and execution into independent, horizontally Scalable components that can be tuned for isolation, performance, and cost.

Real‑World Example

During a 2‑hour coding contest in 2026, a platform serving 12 k concurrent users generated 13.2 k jobs per second. The architecture below handled the load while keeping 95 th‑percentile latency under 1 s and CPU cost per 1 k jobs below $0.02:

  • API Layer: ASP.NET Core on Azure App Service (autoscaled).
  • Broker: Azure Service Bus Topics, one partition per language (e.g., csharp, java, python).
  • Worker Runtime: Azure Container Apps running language‑specific Docker images.
  • Storage: Cosmos DB for submission metadata; Blob Storage for test suites.
  • Observability: OpenTelemetry Collector → Log Analytics, custom metrics for queue depth and job latency.

Key metrics (peak):

Metric Peak
Submission latency 1,450 ms
Jobs per second 13,200
Container spin‑up 300 ms
CPU cost per 1 k jobs $0.022

Trade‑offs

Broker Choice

  • Azure Service Bus – built‑in duplicate detection, dead‑letter queues, and session affinity. Good for per‑language routing but limited message size (1 kB). Mitigate by storing payloads in Blob and sending a reference.
  • Kafka – raw throughput, log replay, but requires extra tooling for dead‑letter handling and has higher operational overhead.
  • RabbitMQ – simple, but scaling partitions is non‑trivial; not ideal for >10 k QPS.

For the contest, Service Bus hit the sweet spot: its duplicate detection matched our idempotent producer, and the session feature allowed us to route jobs to language‑specific worker pools without a custom router.

Worker Runtime

  • Azure Container Apps – serverless, warm‑pool, per‑instance cost granularity. Cold starts < 200 ms after the first warm instance, suitable for <2 s SLA.
  • Kubernetes Pods – full control, but requires managing node pools and image pull latency. Pre‑pulling images on each node reduces startup to <150 ms.
  • Functions – great for small tasks, but the 1–2 s cold start on the first invocation would break the contest SLA.

We chose Container Apps because we could keep a small warm pool (5–10 instances) and scale out on demand, while still paying only for the CPU seconds actually used.

Isolation & Security

  • Run containers as an unprivileged user (--user sandbox) and mount the source as read‑only.
  • Use --security-opt=no-new-privileges and a read‑only root filesystem.
  • Leverage cgroup v2 to enforce CPU and memory quotas per container.
  • Wrap the execution in a run.sh that sets ulimit -t 5 and kills the process if it exceeds the wall‑clock limit.

These hardening steps add ~5 ms per job – negligible compared to the 1 s SLA, but they prevent a sandbox escape that could compromise the entire cluster.

Cost vs Performance

  • Container Apps: $0.0005 per 1 s of CPU on average. Spin‑up cost is amortized across many jobs if the warm pool is kept.
  • Service Bus: $0.0008 per 1,000 messages. For 13 k QPS, the cost is ~ $0.01 per minute, which is trivial compared to compute.
  • Blob Storage for test suites: negligible cost; the main cost driver is container CPU time.

Trade‑off: keeping a larger warm pool reduces cold starts but increases idle cost. We found a 10‑instance warm pool to be optimal during contests.

Component Selection Matrix

Below is a quick matrix to decide each component based on your constraints.

Dimension Option 1 Option 2 When to choose
Broker throughput Kafka (10 k+ QPS) Service Bus (1–2 k QPS per partition) Use Kafka if you need sub‑ms ordering or replay; otherwise Service Bus is simpler.
Container startup latency AKS with DaemonSet pre‑pull Container Apps warm pool Choose Container Apps for <2 s SLA; use AKS if you need custom networking or GPU.
Isolation strictness gVisor / Kata Containers Docker with seccomp + no‑new‑privileges Use gVisor only if you have a threat model that requires it; Docker is sufficient for most contests.
Cost sensitivity AKS (pay per node) Container Apps (pay per CPU second) Container Apps win for bursty workloads; AKS is cheaper for sustained high throughput.
Operational overhead Self‑managed Kafka cluster Managed Service Bus Use Service Bus unless you already own a Kafka cluster.

Implementation Checklist

  • Idempotent producer: set MessageId to the submission GUID.
  • Duplicate detection: enable Service Bus duplicate detection window of 5 minutes.
  • Session routing: use SessionId = language to guarantee language‑specific workers.
  • Cache test suites in Redis (TTL 5 min) to avoid Blob throttling.
  • Pre‑pull container images on each node via a DaemonSet or Container Apps image cache.
  • Use custom metrics (queue depth, CPU usage) for HPA.
  • Set a hard timeout on the container (ulimit -t 5 + watchdog).
  • Enable dead‑letter queues for malformed jobs.
  • Instrument OpenTelemetry for job latency, sandbox failures, and cost estimation.
  • Alert on queue depth > 10 k for >30 s or sandbox failure rate > 2 %.

When This Fails in Production

  • Blob storage throttling – simultaneous pulls of a 10 MB test suite can hit 429. Mitigation: cache locally in Redis or use Azure Front Door for edge caching.
  • Service Bus partition hot‑spot – a sudden surge of a single language overloads its partition. Mitigation: enable auto‑partitioning or add more language‑specific topics.
  • Container image pull latency – new image versions cause >2 s pull times. Mitigation: use a DaemonSet to pre‑pull or leverage Container Apps image cache.
  • Queue back‑pressure – no max size set on topics, leading to memory exhaustion. Mitigation: set maxSizeInMegabytes and use dead‑letter for overflow.
  • Worker starvation – a single worker gets stuck in a runaway loop. Mitigation: enforce ulimit -t and a watchdog that kills the container after the wall‑clock timeout.

Common Mistakes Engineers Make

  • Forgetting idempotency – duplicate network retries lead to double execution and skewed scores.
  • Using a single queue for all languages – causes lock contention and uneven scaling.
  • Blocking I/O inside the sandbox – synchronous file reads block the thread pool, reducing throughput.
  • Not setting a hard timeout – infinite loops tie up workers forever.
  • Ignoring cache locality – pulling the same test suite from Blob for every job incurs network and I/O overhead.

Better Approach Based on Experience

  • Adopt SessionId routing on Service Bus to keep language‑specific queues isolated.
  • Cache the test suite in a sidecar Redis instance per worker pod; evict after a job completes.
  • Use a lightweight run.sh that performs compile, test, and JSON reporting in one step; keep it <200 B.
  • Keep a small warm pool (5–10 instances) of Container Apps; scale out based on queue depth > 5 k.
  • Implement a global health check that verifies sandbox isolation by running a known malicious payload during deployment.

Performance Considerations

  • Queue latency is dominated by broker delivery time (<50 ms for Service Bus). Use SessionId to reduce lock contention.
  • Container spin‑up is the next bottleneck – aim for <200 ms. Pre‑pull images and use --cpus=0.5 to keep the runtime lightweight.
  • CPU quotas directly affect wall‑clock time; a 0.5‑CPU container will run a 5 s timeout job in ~10 s if it hogs the CPU.
  • Memory limits prevent OOM kills; set --memory=256m and monitor sandbox_failure_rate.
  • Network egress for result storage is negligible if you use Azure Blob's internal endpoint.

Scaling Notes

  • Horizontal scaling: HPA on queue depth – scale out when queue_depth > 5 k for >15 s.
  • Vertical scaling: increase CPU quota per container during contests if the job mix is CPU‑heavy.
  • Sharding: split Service Bus topics per language; add more partitions if a single language dominates.
  • Cache eviction: keep Redis caches warm for 5 min; evict after each contest to avoid stale test suites.
  • Cost control: track cpu_seconds_per_job and set budgets; adjust worker pool size to stay within budget.

In a production environment, the combination of a session‑aware broker, a warm pool of lightweight containers, and aggressive caching yields a robust, low‑latency, and cost‑efficient code‑execution platform that scales to tens of thousands of submissions per second.

How does Service Bus duplicate detection work and why is it important?

Service Bus keeps a duplicate detection window (default 5 min). When a message with the same MessageId arrives, it is silently dropped, preventing double execution and keeping job counts accurate.

What are the trade‑offs between Azure Container Apps and AKS for worker runtime?

Container Apps offer a serverless warm pool and pay‑per‑CPU‑second billing, great for bursty contests; AKS gives full control and lower cost for sustained high throughput but requires node‑pool and image‑pull management.

How can we mitigate Blob storage throttling in high‑throughput contests?

Cache test suites in Redis or Azure Front Door edge caches, use short TTLs (5 min), and pre‑download large suites during contest warm‑up to avoid simultaneous 429 responses.

Why is session routing on Service Bus preferred over topic partitioning per language?

Session routing guarantees that all messages for a language go to the same consumer group without custom routing logic, reduces lock contention, and lets you scale partitions per language easily.

How to enforce strict sandbox isolation without incurring significant latency?

Run containers as an unprivileged user, use read‑only root FS, apply cgroup v2 limits, and a lightweight run.sh that sets ulimit and a watchdog. These add ~5 ms per job, negligible for a 1 s SLA.

Related Articles

Top comments (0)