IHP includes built-in functionality for creating and running background jobs, perfect for tasks that can be split into small units and run in parallel.
Creating a Job
Use the “Background Job” option in the IHP IDE codegen tool to generate job code.
Implementing a Job
Jobs are created at <Application>/Job/<Name>.hs:
module Web.Job.EmailCustomers where
import Web.Controller.Prelude
instance Job EmailCustomersJob where
perform EmailCustomersJob { .. } = do
putStrLn "Hello World!"
The job type is generated from a table added to Schema.sql. Add columns to the table to include additional data in your job.
Example: Email Marketing Job
module Web.Job.EmailCustomers where
import Web.Controller.Prelude
import IHP.Mail (sendMail)
instance Job EmailCustomersJob where
perform EmailCustomersJob { .. } = do
customers <- query @Customer |> fetch
forEach customers sendToCustomer
where
sendToCustomer customer = sendMail (MarketingMail customer)
Running Jobs
newRecord @EmailCustomersJob |> create
Scheduled Execution
Schedule a job for future execution:
import Data.Time.Clock (addUTCTime, getCurrentTime, nominalDay)
now <- getCurrentTime
newRecord @EmailCustomersJob
|> set #runAt (addUTCTime nominalDay now) -- 24 hours from now
|> create
IHP polls for scheduled jobs approximately every minute and executes jobs whose runAt time has passed.
Registering Workers
In Main.hs, register the workers:
import Web.Worker
instance Worker RootApplication where
workers _ = workers WebApplication
Development vs Production
Development
Workers start automatically with the dev server.
Production
Build the RunJobs binary:nix build .#optimized-prod-server
# or
nix build .#unoptimized-prod-server
Deploy result/bin/RunJobs alongside your app.
Viewing Job Status
Inspect job status in the database:
#status - Current job status
#lastError - Error message if the job failed
Configuration
Maximum Attempts
Default: 10 attempts
instance Job EmailCustomersJob where
perform EmailCustomersJob { .. } = do
customers <- query @Customer |> fetch
forEach customers sendToCustomer
where
sendToCustomer customer = sendMail (MarketingMail customer)
maxAttempts = 3
Backoff Strategy
Linear backoff (default: 30 seconds):
backoffStrategy :: BackoffStrategy
backoffStrategy = LinearBackoff { delayInSeconds = 5 }
Exponential backoff:
backoffStrategy :: BackoffStrategy
backoffStrategy = ExponentialBackoff { delayInSeconds = 5 }
With exponential backoff, delays are: 5s, 25s, 625s, etc.
Timeout
Default: No timeout
instance Job EmailCustomersJob where
perform EmailCustomersJob { .. } = do
-- job implementation
pure ()
timeoutInMicroseconds = Just $ 1000000 * 60 -- 1 minute
Timed out jobs are retried unless maxAttempts is set to 0.
Concurrency
Default: 16 concurrent jobs
instance Job EmailCustomersJob where
perform EmailCustomersJob { .. } = do
-- job implementation
pure ()
maxConcurrency = 1 -- Process one at a time
Scheduling with Cron
Create a script to trigger jobs:
#!/usr/bin/env run-script
module Application.Script.RunEmailCustomersJob where
import Application.Script.Prelude
run :: Script
run = do
newRecord @EmailCustomersJob |> create
pure ()
Build the script:
nix build .#optimized-prod-server
The script binary will be at result/bin/RunEmailCustomersJob.
Add to crontab:
# Run daily at 5am
0 5 * * * root /path/to/your/app/result/bin/RunEmailCustomersJob
Use the full path to the binary in your cron entry.
Jobs Dashboard
IHP includes a built-in dashboard for viewing and managing jobs.
Quick Start
In Web/FrontController.hs:
import IHP.Job.Dashboard
type MyJobDashboard = JobsDashboardController
(BasicAuthStatic "jobs" "jobs")
'[]
instance FrontController WebApplication where
controllers =
[ startPage HomeView
, parseRoute @ProductsController
, parseRoute @MyJobDashboard -- Mount at /jobs/
]
Link to the dashboard:
import IHP.Job.Dashboard
<a href={ListJobsAction}>Jobs</a>
Authentication Options
No Authentication:
type MyJobDashboard = JobsDashboardController NoAuth '[]
HTTP Basic Auth (static):
type MyJobDashboard = JobsDashboardController
(BasicAuthStatic "username" "password")
'[]
HTTP Basic Auth (environment variables):
type MyJobDashboard = JobsDashboardController BasicAuth '[]
Custom Authentication:
data CustomAuth
instance AuthenticationMethod CustomAuth where
authenticate = do
myCustomAuthenticateFunction
type MyJobDashboard = JobsDashboardController CustomAuth '[]
Custom Job Views
Specify jobs to display:
type MyJobDashboard = JobsDashboardController
(BasicAuthStatic "jobs" "jobs")
'[UpdateUserJob, CleanDatabaseJob, EmailCustomersJob]
Customize display by implementing DisplayableJob:
instance DisplayableJob EmailCustomersJob where
makeDashboardSection = -- custom dashboard section
makePageView = -- custom list page
makeDetailView job = -- custom detail view
makeNewJobView = -- custom creation form
createNewJob = -- custom creation logic
See the API documentation for full details on customization.