Designing Idempotent Background Workers for LLM Calls in Rails

Learn how to design idempotent background workers for LLM calls in Rails to avoid duplicate billing and inconsistent state. Status gates and atomic claims

jueves, 30 de julio de 2026 • 4 min read • Q2BSTUDIO Team

Cómo Evitar Costos Duplicados y Condiciones de Carrera

When integrating language models (LLMs) into enterprise applications, operational complexity skyrockets. A normal Sidekiq worker can retry without consequences, but when that worker invokes GPT-4 or Claude, each retry means an extra API cost, a potentially different response, and inconsistent database state. At Q2BSTUDIO, we build custom software solutions that face this challenge daily, and we have learned that idempotency is not a luxury but a production requirement.

Imagine a support ticket triage system. A worker picks up a ticket, calls the LLM to classify it, and right after getting the response but before writing to the database, the worker dies. Sidekiq correctly retries, but that triggers a second model call and a second charge. If the same ticket is enqueued twice by mistake, both workers run parallel classifications, duplicating costs and overwriting results. This is the scenario we must avoid.

Idempotency, in this context, means that running the worker twice for the same ticket must produce the same final state and the same side effects as running it once. It is not about avoiding duplication by response value — which is non-deterministic — but about protecting the write and the side effects. At Q2BSTUDIO we apply this principle in our AI and intelligent agent projects, where each LLM call represents a cost and a risk of inconsistency.

The first line of defense is a state-based gate: before invoking the model, the worker checks if the ticket is already completed. If so, it returns immediately. This covers sequential retries, the most common case. However, it does not protect against concurrent duplicates. For example, two workers can read 'completed = false' at the same time, both invoke the LLM, and both write. To close that gap, the solution is an atomic claim in the database: a conditional update that transitions the ticket from 'pending' to 'processing'. Only one worker can win that race; the loser sees zero affected rows and exits without spending tokens. We frequently implement this pattern in our custom software applications to ensure transactional consistency.

Implementing this in Rails is straightforward. Using Sidekiq, the worker receives the ticket's UUID (not a sequential ID, to avoid write windows). The state gate relies on a 'status' field with values like pending, processing, completed, failed. The atomic claim is done with an update_all that modifies only if the status is pending or failed. This prevents two workers from processing the same ticket simultaneously. Additionally, we log every model call in a per-ticket audit trail, which in the future will allow us to reuse stored responses instead of re-invoking after a write failure.

A subtle detail: when the model responds correctly but the write fails (e.g., a deadlock), the worker retries and pays again. The persistent audit trail is key to closing that hole: if the response is already saved, the worker can retrieve it without calling the LLM again. This is a logical next step in our practice of cloud AWS/Azure development, where resilience and cost control are critical.

Beyond the worker, idempotency must extend to other side effects, such as outgoing webhooks. For these, a unique delivery identifier (idempotency key) is the solution. At Q2BSTUDIO we design complete automation systems that integrate BI / Power BI and AI agents, where every chained action needs at-least-once guarantees from a business perspective, even though the underlying infrastructure only offers 'at least once'.

Common mistakes include: basing idempotency on a hash of the response (fails due to non-determinism), using find_or_create_by without a unique index (concurrency races), or swallowing exceptions to avoid duplicates (hides real failures). In our cybersecurity projects, where data integrity is paramount, we apply these lessons rigorously.

Testing sequential idempotency is simple: call perform twice in a row and assert the model was called only once. Testing real concurrency requires parallel database connections, something we do in our integration suites. In production, we monitor retry rates and failed tickets as signals of provider issues or classification problems.

In summary, the hardest part of an AI feature is not the prompt but the contract around an unreliable external model. Treat it as a fragile external dependency, put it behind a queue, and accept that every worker will run more than once. Idempotency — with a stable identifier, an atomic state transition, and a thorough audit trail — turns retries from costly to safe. At Q2BSTUDIO, we help companies build these robust architectures, combining AI agents, cloud, and custom software development so that their systems are reliable from the very first retry.

A BREAK?

Play for a moment before you go

OUR SERVICES

How we can help you

Do you have a project in mind?

Tell us your vision and we'll turn it into a software solution. Whatever the scope, we make your idea real.