Skip to content

[Deferred] Concurrency limits for deferred tasks #368

Description

@rsamoilov

Description

Currently, all deferred tasks share the same execution pool with no way to limit how many instances of a specific task run concurrently. This can be problematic when tasks interact with rate-limited external services or shared resources that can't handle unbounded parallel access.

For example, consider a task that calls a third-party API with a rate limit of 10 requests per second. If 500 of these tasks are enqueued at once, they'll all compete for execution, overwhelming the external service and likely triggering rate-limit errors — which in turn causes retries, compounding the problem.

A per-task concurrency setting would let users declare the maximum number of concurrent executions for a given task class:

Example:

class SyncToExternalService
  include Rage::Deferred::Task
  max_concurrency 5

  def perform(record_id:)
    ExternalAPI.sync(Record.find(record_id))
  end
end

With this configuration, no more than 5 instances of SyncToExternalService would run at the same time. Additional tasks would remain in the queue and execute as slots become available.

Design considerations

  • Queueing behavior. When the concurrency limit is reached, excess tasks should stay in the queue rather than being dropped. The scheduler needs a mechanism to re-check limited tasks as running ones complete.
  • Interaction with backpressure. The existing backpressure mechanism controls overall queue size. Concurrency limits are orthogonal — they constrain execution, not enqueuing. The two features should work independently without interfering.
  • Scope of the limit. Since Rage::Deferred runs in-process, the limit naturally applies per worker. Consider whether the API should make this explicit or whether cross-worker coordination is out of scope for an initial implementation.
  • Counting active tasks. The implementation needs an efficient way to track how many instances of a task class are currently running. This counter must be updated reliably, including when tasks fail or are interrupted during shutdown.

Tips

  • Review the Deferred docs to understand how task scheduling and execution work.
  • Check the architecture doc to see how Rage's core components interact and to understand the design principles.
  • Read the contributing guide for coding conventions and design principles used across the codebase.
  • Before starting the implementation, please share your proposed design approach. Discussing the approach early will drastically increase the chances of acceptance and help avoid rework.
  • Feel free to ask any questions or request help in the comments below!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions