dimitarvp

dimitarvp

Oban config: am I doing this right?

We have jobs with the following requirements:

  • Jobs need to be retried exactly once every 24h or slightly less, with no exceptions – I am still working on the logic of another service (that we also control) returning tentative successes / failures because it has its own retry mechanism. We will capture the actual error response and do an Oban retry only then; in all other cases our other service either reports success or a tentative error – the latter means that it will use its own retry mechanism to try and do the job and only return a true error later. In the meantime: is snoozing the job the right tool in this case?
  • Retries must always happen on schedule and we can’t afford to miss the window due to some extra time spent somewhere. See the remark below about a slightly tweaked backoff for my stab at it.
  • Jobs should be attempted maximum 7 times.
  • We don’t need a varying backoff; when it’s time to retry, the job should be retried immediately with no extra waiting. Hence, the backoff is a flat value (24h or slightly sless).
  • We should not allow more than 50 parallel jobs running per second to not overwhelm the other service.
  • Jobs args are a string-keyed map, which makes the unique’s :keys option not applicable. I’ll rewrite the code to make the maps atom-keyed.

Here’s what I have in config/config.exs:

config :something, Oban,
  repo: Something.Repo,
  queues: [
    some_queue: [limit: 50, dispatch_cooldown: :timer.seconds(1)]
  ]

and in the worker:

defmodule Something.Worker do
  use Oban.Worker,
    queue: :some_queue,
    unique: [
      period: :infinity,
      states: [:available, :scheduled, :executing],
      keys: [:user_id]
    ]

  @timeout :timer.seconds(60)
  @backoff 86400

  @impl Oban.Worker
  def timeout(_job), do: @timeout

  @impl Worker
  def backoff(%Job{attempt: _attempt}), do: @backoff

  # `perform` and others follow.
end

and the enqueueing:

# This is not a compile-time value, the code is omitted for brevity.
Something.Worker.new(args, max_attempts: 7)

One thing I am not super clear on: is it possible to miss the retry window and so the next retry becomes effectively ~48h in the future?

I also thought of making sure I won’t miss the window of retrying via defensively deducting the timeout and some extra time, like so:

  def backoff(%Job{attempt: _attempt}) do
    # If we set a very low backoff value then pick the timeout value
    # to avoid negative or too small a retry interval.
    Enum.max([
      @backoff - @timeout - 10,
      @timeout
    ])
  end

Questions:

  • Is snoozing the job the right tool for when we have a tentative failure in our other service but we know it will keep retrying and so we can request information from it later? This also touches on analytics because we’d like to know how quickly and reliably do the jobs get executed (so we can also modify / improve the other service and/or tweak our timing values). EDIT: Seems like snoozing actually increases attempt so it might not be what we’re looking for. :confused:
  • Maybe I should just lower the retry period of the other service to f.ex. 23h55m so I don’t have to worry about missing retry windows? This too is in my control and started sounding like the better idea the more I was progressing with writing this post.
  • Can we use string-keyed maps for args and enforce uniqueness on them?

Apropos, am I doing this whole thing right? Am I missing something?

Marked As Solved

sorentwo

sorentwo

Oban Core Team

Addressing your questions as best I can (in roughly the order they were asked):

Remember, the queue limit is per-node. If you’re running multiple nodes, or you’re doing rolling deploys, then the total number of allowed jobs will spike to 50*N, where N is the number of nodes.

The cooldown is used internally to prevent the queue’s producer from fetching jobs too rapidly. The default is 5ms. After jobs finish it will wait up to that amount of time before fetching more. This is the first I’ve seen anybody tweak that value, and bumping it to 1s could significantly slow down your processing if you have a backlog.

If the app is down, the queue is paused, or the backlog is deep enough the job may not run within that window. Guaranteeing that the job can run on schedule is up to your system.

Snoozing is a great approach for that, and exactly what we’d suggest you reach for.

Snoozing itself doesn’t increase the attempts, that’s from fetching the job in the first place. There’s a section in the Oban.Worker docs that covers how to compensate for the difference with backoffs. Pro’s Smart engine actually rolls back snooze attempts and records how many times a job has snoozed, FWIW.

Absolutely. The args map is always converted to string keys, because it’s stored as JSON in the database. The keys are always converted to strings for unique queries as well.

Also Liked

sorentwo

sorentwo

Oban Core Team

It is a demarcation line. Rate limiting in Pro is implemented at the engine level, so there isn’t any additional churn from fetching, snoozing, and retrying jobs that can’t really be ran. Rolling your own or using a library won’t be as efficient, but of course you can build whatever you like to on top of Oban :slightly_smiling_face:.

dimitarvp

dimitarvp

Thank you.

dimitarvp

dimitarvp

For future readers: it is not possible to specify string keys in the unique fields configuration or you’d get ** (ArgumentError) invalid value for :unique, expected :keys to be a list of atoms.

However, specifying the keys as atoms works fine. Just wrote a few tests where I am trying to insert duplicate jobs and Oban correctly ignores the duplicates.

Where Next?

Popular in Questions Top

aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
nobody
How to bind a phoenix app to a specific ip address? could not find anything about that, nowhere, unfortunately, but for me this is quite...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
yawaramin
In the Dialyzer docs ( dialyzer — OTP 29.0.2 (dialyzer 6.0.1) ), there is a way to turn off a specific warning for a function: -dialyzer...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New

Other popular topics Top

skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39297 209
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 47930 226
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New

We're in Beta

About us Mission Statement