DEV Community

Cover image for Optimizing Hangfire Queues for AI Agent Tasks: 10k QPS Benchmark
Amitesh0512
Amitesh0512

Posted on Originally published at amiteshsurwar.com

Optimizing Hangfire Queues for AI Agent Tasks: 10k QPS Benchmark

Quick Answer

Optimizing Hangfire queues for AI agent tasks: Keep AI job latency under 200 ms at 10 k QPS by sharding queues, using Redis, throttling LLM calls, and instrumenting with OpenTelemetry.

Hangfire Queue Delay Breaches SLA

When you expose an AI agent over HTTP, the request handler becomes a thin front‑end that hands the heavy lifting to a background system. In our production recommendation service, a 30‑second queue delay caused a 200 ms SLA breach, inflated token costs, and forced a feature rollback. The culprit was not the LLM but the Hangfire configuration for high‑frequency AI jobs.

Real‑World Example: 10 k Jobs/Second, 200 ms Latency Target

We deployed a chat‑bot that enqueues a job for every user message. At peak traffic the system generated 10 k jobs per second. Using Hangfire with SQL Server and a single "default" queue, the average dequeue latency spiked to 350 ms. The queue length grew beyond 5 k items, and the dashboard flagged a red spike. The downstream Azure-openai-service-vs-gpt4-api-for-net-microservices-a-deepdive-for-architects-20260830" class="internal-link">Azure OpenAI endpoint was saturated, and retries multiplied token consumption by 2.5×.

Trade‑offs in Queue Design

  • Queue Sharding vs. Single Queue – Sharding into "high", "medium", "low" queues isolates latency‑sensitive jobs but adds operational complexity (multiple worker configurations, separate monitoring). A single queue is simpler but suffers from priority inversion.
  • Redis vs. SQL Server Storage – Redis offers <0.5 ms dequeue latency and millions of ops/sec, but durability relies on AOF or snapshots. SQL Server guarantees ACID but hits 5 k ops/sec under contention; lock escalation can stall workers.
  • Worker Count vs. CPU Core Utilization – Setting WorkerCount = CPU*2 saturates the thread pool, causing ThreadPool.QueueUserWorkItem delays. Fewer workers reduce contention but increase queue backlog. The sweet spot depends on the model inference time and outbound API rate limits.
  • Retry Policy vs. Cost Control – Unlimited retries flood the queue and double token usage. Limiting to 2–3 attempts with exponential back‑off balances reliability and cost but may hide transient failures if not monitored.
  • Idempotency vs. Throughput – Storing a hash of the prompt prevents duplicate inference but introduces an extra DB round‑trip. In high‑volume scenarios, a cache (e.g., Redis) can be used to avoid the DB hit for repeated prompts.

Persisting & Prioritizing AI Tasks

  1. Choose a Persistence Layer
    • For <10 k QPS and strict durability: hardened SQL Server with row‑level security.
    • For >10 k QPS: Redis Cluster with AOF + RDB snapshot; enable FlushOnShutdown to avoid data loss.
  2. Define Queue Priorities
    • "high" – real‑time inference (chat, recommendation).
    • "medium" – embedding extraction, feature engineering.
    • "low" – batch retraining, archival.
  3. Configure Workers
    • Bind dedicated workers to "high" queue; limit to 8 cores per pod to avoid thread‑pool starvation.
    • Use WorkerCount = Environment.ProcessorCount for "medium" and "low" queues.
    • Inject a SemaphoreSlim to throttle outbound calls to the LLM API.
  4. Implement Idempotency
    • Maintain a AiJobResults table keyed by JobHash in Redis or SQL.
    • Check existence before invoking the model; if present, skip inference and return cached result.
  5. Set Retry Limits
    • Use AutomaticRetryAttribute with Attempts = 3 and exponential back‑off.
    • Emit OpenTelemetry JobFailed events to a dead‑letter queue for manual triage.
  6. Observability
    • Instrument PerformingContext and PerformedContext with OpenTelemetry.
    • Push metrics to Prometheus; alert on queue length > 500 or job duration > 200 ms.
    • Correlate Hangfire job IDs with downstream tracing context.
  7. Warm‑up Strategy
    • On pod startup, enqueue a dummy job that calls the LLM endpoint; this pre‑warm TLS handshake and model warm‑up.

When This Fails in Production

  • Redis Cluster Partition – A network split can orphan a queue segment; jobs become invisible to workers until re‑synchronization.
  • Semaphore Starvation – If the external API throttles us to 429, the semaphore can block all workers, causing a cascading backlog.
  • Idempotency Hash Collision – Using a weak hash (e.g., MD5) can lead to false positives, skipping legitimate jobs.
  • Thread‑pool Saturation – Over‑provisioned workers in a low‑CPU pod lead to ThreadPool.QueueUserWorkItem delays, making queue latency worse than the model inference latency.

Common Mistakes Engineers Make

  • Assuming a single queue is sufficient; this ignores priority inversion.
  • Enabling unlimited retries; this floods the queue and inflates token costs.
  • Using SQL Server without tuning isolation levels; default READ COMMITTED can cause lock escalation.
  • Ignoring the need for trace context propagation; Hangfire’s dashboard does not forward the Activity ID.
  • Underestimating the cost of large payloads; JSON serialization of a 1 k token prompt inflates row size and degrades index scans.

Better Approach Based on Experience

In production, the most resilient pattern is:

  1. Persist jobs in a Redis Cluster with AOF enabled and a maxmemory-policy of volatile-ttl to keep the queue size bounded.
  2. Shard queues by priority and bind dedicated workers; keep the high‑priority worker count low enough to avoid thread‑pool contention but high enough to keep latency <200 ms.
  3. Implement a distributed idempotency cache in Redis; use SETNX with an expiry matching the model’s TTL.
  4. Throttle outbound calls with a semaphore that respects the LLM provider’s rate limits; back‑off on 429 responses.
  5. Instrument every job with OpenTelemetry; push metrics to Prometheus and set up Grafana alerts on queue length and job duration.
  6. Run a warm‑up job on pod startup; schedule it as a recurring job that runs every minute during the first 5 minutes of a pod’s life.
Strategy Primary Benefit Key Implementation Detail Considerations
Sharding Queues Reduce contention, lower latency Split jobs across multiple queues and workers More workers needed, added complexity
Using Redis In‑memory storage, fast access Configure Hangfire to use Redis as the storage backend Memory cost, persistence trade‑offs
Throttling LLM Calls Avoid API rate limits, stable throughput Implement rate limiter and exponential backoff Possible latency increase, throughput trade‑off
OpenTelemetry Instrumentation Enhanced observability, trace latency Add OTEL SDK and exporters to Hangfire jobs Instrumentation overhead, configuration effort

Performance Considerations

  • Redis dequeue latency <0.5 ms; SQL Server average 3 ms under load.
  • Worker CPU usage spikes when model inference takes >200 ms; keep WorkerCount ≤ CPU cores to avoid ThreadPool starvation.
  • Serialization overhead: JSON payloads >2 kB increase DB round‑trip time; consider binary serialization or compressing the prompt.
  • Network latency to the LLM endpoint dominates; place the Hangfire workers in the same region and use connection pooling.

Scaling Notes

  • Horizontal scaling: add more Hangfire Server pods; each pod registers the same queues, letting Redis balance the load.
  • Vertical scaling: increase pod CPU/memory only if the worker count is saturated; otherwise, add pods to avoid contention.
  • Queue sharding: keep the high‑priority queue small (<10 k items) to maintain <200 ms latency; use separate Redis keyspaces for each priority to avoid cross‑queue contention.
  • Database scaling: if using SQL Server, move to a dedicated high‑IO tier or use Azure SQL Managed Instance with elastic pools.

Checklist: Harden Your Hangfire AI Pipeline Today

  1. Switch to Redis Cluster with AOF and snapshotting.
  2. Create "high", "medium", "low" queues and bind dedicated workers.
  3. Implement distributed idempotency cache in Redis.
  4. Set AutomaticRetry to 3 attempts with exponential back‑off.
  5. Inject a semaphore‑based throttle for outbound LLM calls.
  6. Add OpenTelemetry instrumentation for Performing and Performed events.
  7. Configure Prometheus alerts: queue length > 500, job duration > 200 ms.
  8. Schedule a warm‑up job on pod start‑up.
  9. Verify trace context propagation across Hangfire and the LLM SDK.

How do I decide between Redis and SQL Server for Hangfire when handling high‑frequency AI jobs?

Redis delivers sub‑millisecond dequeue latency and scales to millions of ops/sec, making it ideal for 10k+ QPS. SQL Server offers ACID durability but throttles under heavy contention; it’s suitable for <10k QPS with strict durability needs.

What is the best way to shard queues to keep latency under 200 ms?

Create separate "high", "medium", and "low" queues. Bind a dedicated worker pool to the high queue, limit its size (<10k items), and use Redis keyspaces per priority to avoid cross‑queue contention.

How can I configure worker count to avoid thread‑pool starvation?

Set WorkerCount to the number of CPU cores per pod (or Environment.ProcessorCount). For high‑priority queues, cap workers to 8 cores per pod and use a SemaphoreSlim to throttle outbound LLM calls.

How do I implement idempotency without extra DB round‑trips?

Store a JobHash in Redis with SETNX and a TTL that matches the model’s cache time. Check the key before invoking the LLM; if it exists, return the cached result immediately.

What retry strategy balances reliability and token cost?

Use AutomaticRetryAttribute with Attempts = 3 and exponential back‑off. Emit JobFailed events to a dead‑letter queue and monitor them; this limits retries while still capturing transient failures.

Conclusion

Optimizing Hangfire queues for AI agent tasks is a trade‑off game. You’re balancing persistence guarantees against latency, worker concurrency against CPU limits, and retry reliability against cost. The real bottleneck is rarely the LLM inference; it’s the plumbing that moves the job. By sharding queues, choosing the right storage backend, throttling external calls, and instrumenting end‑to‑end, you can keep latency under 200 ms even at 10 k jobs per second. That is the true test of an AI agent’s scalability in production.

Related Articles

Top comments (0)