Background Tasks and Jobs
Once-off background tasks
When you just want to run some code in a background task, use PetalPro.BackgroundTask.run/1:
PetalPro.BackgroundTask.run(fn ->
do_some_time_intensive_stuff()
end)
This is useful for speeding up responses in an HTTP request or LiveView action — delegate anything that takes time to a background task (e.g. sending emails, posting to Slack, creating audit logs). Since we want to respond to a user request as soon as possible, you run these non-essential tasks in the background and it won’t hold up the response.
Background jobs with Oban
A background job is something you want to run outside of a normal HTTP request/response cycle — things like sending weekly emails or processing images. We use Oban for this.
A background job is performed by a “worker”. Each worker gets its own file. Petal Pro comes with an example worker you can duplicate:
defmodule PetalPro.Workers.ExampleWorker do
@moduledoc """
Example of how to do async work with Oban.
Run with:
Oban.insert(PetalPro.Workers.ExampleWorker.new(%{}))
"""
use Oban.Worker, queue: :default
require Logger
@impl Oban.Worker
def perform(%Oban.Job{} = _job) do
today = Timex.now() |> Timex.to_date()
Logger.info("ExampleWorker: Today is #{today}")
:ok
end
end
You can run this worker by inserting a job into the database:
Oban.insert(PetalPro.Workers.ExampleWorker.new(%{}))
Once in the database, Oban will try running the task and retry it if it fails. You can see any failed jobs by checking the database:
You can inspect job status directly in the oban_jobs database table.
In dev mode, you can use Oban Web:
http://localhost:4000/admin/oban
Jobs that have been completed are deleted every 24 hours so your database won’t swell over time.
Cron jobs
You can tell your workers to run at certain times with Oban’s cron functionality. See the examples in config.exs:
config :petal_pro, Oban,
repo: PetalPro.Repo,
queues: [default: 5],
plugins: [
{Oban.Plugins.Pruner, max_age: (3600 * 24)},
{Oban.Plugins.Cron,
crontab: [
{"@daily", PetalPro.Workers.ExampleWorker}
# {"* * * * *", PetalPro.EveryMinuteWorker},
# {"0 * * * *", PetalPro.EveryHourWorker},
# {"0 */6 * * *", PetalPro.EverySixHoursWorker},
# {"0 0 * * SUN", PetalPro.EverySundayWorker},
# More examples: https://crontab.guru/examples.html
]}
]
Daily Slack digest worker
Petal Pro v4 ships PetalPro.Workers.DailySlackDigestWorker — an Oban cron job that sends a summary of yesterday’s activity to a Slack channel. It pulls new user signups, audit log action counts, and top page views, then formats them as a single Slack message.
Enabling the digest
Add the worker to the cron schedule in config.exs:
{Oban.Plugins.Cron,
crontab: [
{"0 13 * * *", PetalPro.Workers.DailySlackDigestWorker} # 8am ET / 1pm UTC
]}
Slack configuration
The digest uses PetalPro.Slack to post the message. You need two environment variables:
# OAuth token from your Slack app's "OAuth and Permissions" page
fly secrets set SLACK_OAUTH_TOKEN="xoxb-..."
# The ID of the Slack channel to post to
fly secrets set SLACK_CHANNEL_ID="C0123456789"
To find your channel ID: run PetalPro.Slack.read_channels() in IEX — it returns a list of [id, name] pairs. If the digest is not configured (no SLACK_CHANNEL_ID), it silently skips sending and logs a message instead.
To test locally:
PetalPro.Slack.message("Hello from Petal Pro!")
Slow query telemetry (dev only)
In development, Petal Pro logs any database queries that exceed a configurable time threshold. This makes it easy to spot N+1 queries and unexpectedly slow operations without any additional tooling.
The threshold is set in config/dev.exs:
# Log queries slower than this many milliseconds (dev only)
config :petal_pro, :dev_query_threshold_ms, 100
Queries that exceed the threshold are logged to the console with their duration and SQL. To tighten the threshold and catch more queries, lower the value:
config :petal_pro, :dev_query_threshold_ms, 50
Set it to 0 to log every query. This config has no effect in production.