Recording to Redis
Store recorded output in Redis. Redis is a good fit for transient results because it supports opaque binary values and expiration on every key.
This backend uses Redix. Add the dependency and start a named connection in your supervision tree:
def deps do
[
{:redix, "~> 1.0"}
]
end{Redix, name: MyApp.Redix, host: System.get_env("REDIS_HOST", "localhost")}The Module
The module accepts a supervised connection name and a TTL applied to every recorded payload. It
fetches a batch with MGET and omits keys that are missing or expired.
defmodule MyApp.Redis do
@behaviour Oban.Pro.Storage
@impl Oban.Pro.Storage
def init(opts) do
conn = Keyword.get(opts, :conn, MyApp.Redix)
ttl = Keyword.get(opts, :ttl, to_timeout(day: 1))
%{conn: conn, ttl: ttl}
end
@impl Oban.Pro.Storage
def put(key, payload, %{conn: conn, ttl: ttl}) do
case Redix.command(conn, ["SET", key, payload, "PX", ttl]) do
{:ok, "OK"} -> :ok
{:ok, other} -> {:error, other}
{:error, reason} -> {:error, reason}
end
end
@impl Oban.Pro.Storage
def fetch_all(keys, %{conn: conn}) do
case Redix.command(conn, ["MGET" | keys]) do
{:ok, values} ->
payloads =
for {key, value} <- Enum.zip(keys, values), not is_nil(value), into: %{} do
{key, value}
end
{:ok, payloads}
{:error, reason} ->
{:error, reason}
end
end
@impl Oban.Pro.Storage
def delete_all(keys, %{conn: conn}) do
case Redix.command(conn, ["DEL" | keys]) do
{:ok, _count} -> :ok
{:error, reason} -> {:error, reason}
end
end
endEvery write has an expiry, so Redis cleans up output even if its job isn't pruned. To retain output
for the full TTL after a job is pruned, make delete_all/2 a no-op.
Using It
Configure the backend on a recorded worker, overriding the connection or TTL as needed:
use Oban.Pro.Worker, recorded: [storage: {MyApp.Redis, ttl: to_timeout(hour: 1)}]To send every worker's output to Redis without annotating each one, set it as the compile-time default:
config :oban_pro, Oban.Pro.Worker, recorded: [storage: MyApp.Redis]