taoensso / carmine

Redis client + message queue for Clojure
https://www.taoensso.com/carmine
Eclipse Public License 1.0
1.15k stars 130 forks source link

Carmine message-queue: allow throughput control #308

Open sanguivore-easyco opened 3 weeks ago

sanguivore-easyco commented 3 weeks ago

I have a use case where a third-party API I'm interacting with has a strict rate limit, and rather than responding with a 429 for each request past the limit, it responds with 429 for every request for ten seconds whenever the limit is exceeded.

Thus, I want to eagerly throttle my workers according to this limit. I imagine this can be useful in other cases where a dependency can only handle a certain level of throughput and lacks good resilience characteristics such that the consumer needs to handle load management.

Current Workaround

By setting :throttle-ms to a function against a redis-backed rate-limiter which returns the number of milliseconds to wait to pick up a job, you can get close to adhering to the rate limit. However, :throttle-ms is currently considered after picking up a message, so you end up throttling to + . Ideally, the rate limit settings do not need to know the number of workers.

Possible Solution Suggestions

Clojurians' Slack thread for extra context: https://clojurians.slack.com/archives/C05M8GRL65Q/p1717003308524139?thread_ts=1716841423.174899&cid=C05M8GRL65Q

ptaoussanis commented 3 weeks ago

@sanguivore-easyco Thanks for the nice, clear issue - and for the solution suggestions - that's helpful šŸ™

Definitely keen to get something like this in. Have only thought about it for a moment, but my first inclination would be closest to your third suggestion.

For simplicity/symmetry, what do you think of calling it something like :pre-throttle with the same args and semantics as :throttle:

  `:throttle-ms`      - msecs, or (fn [queue-size])=>?msecs (default `default-throttle-ms-fn`)
  `:pre-throttle-ms`  - msecs, or (fn [queue-size])=>?msecs (default `nil`)

What do you think?

sanguivore-easyco commented 3 weeks ago

@sanguivore-easyco Thanks for the nice, clear issue - and for the solution suggestions - that's helpful šŸ™

Definitely keen to get something like this in. Have only thought about it for a moment, but my first inclination would be closest to your third suggestion.

For simplicity/symmetry, what do you think of calling it something like :pre-throttle with the same args and semantics as :throttle:

  `:throttle-ms`      - msecs, or (fn [queue-size])=>?msecs (default `default-throttle-ms-fn`)
  `:pre-throttle-ms`  - msecs, or (fn [queue-size])=>?msecs (default `nil`)

What do you think?

That sounds great to me! This would definitely work for my use case, and remains reasonably straightforward for documentation and the like.

ptaoussanis commented 3 weeks ago

@sanguivore-easyco Thanks for the confirmation šŸ‘ What's your level of urgency on this? Are you happy with your workaround in the meantime, or is that troublesome?

sanguivore-easyco commented 3 weeks ago

@sanguivore-easyco Thanks for the confirmation šŸ‘ What's your level of urgency on this? Are you happy with your workaround in the meantime, or is that troublesome?

My workaround will be fine for several months in all likelihood, and with your design, it's trivial for me to update once the change is in :) No rush