pg_cron schedules
What it is
pg_cron is a cron-based job scheduler that runs inside PostgreSQL. Jobs are stored in database tables and execute SQL commands on familiar cron schedules.
What it provides
- Recurring SQL jobs using cron syntax or intervals
- Job metadata that can be inspected and managed with SQL
- Database-local scheduling without a separate scheduler process
Use it for refreshing materialized views, removing expired sessions, rolling up analytics, maintaining partitions, or invoking a stored procedure on a schedule.
Use it with Rent
extension pg_cron {
name = "pg_cron"
}The RSL declaration makes installation and migration intent part of the reviewed application schema. Register the typed pack at runtime to call its APIs:
use rent_ext_pg_cron::{CronSchedule, PgCron, PgCronClientExt};
let pack = extensions.register_pack::<PgCron>()?;
let cron = pack.client(&pool);
let hourly = CronSchedule::new("0 * * * *")?;
let job_id = cron
.schedule_job(
"refresh-search",
&hourly,
"SELECT refresh_search()",
)
.await?;
let removed = cron.unschedule_job("refresh-search").await?;CronSchedule::new rejects empty values and embedded NUL characters. PostgreSQL validates the schedule grammar
when the job is submitted. A malformed schedule returns an error. unschedule_job returns false when no job
with that name belongs to the current user.
Scheduling participates in your transaction. Register the pack against a transaction to publish a job only when the surrounding work commits:
let tx = client.tx().await?;
let job_id = pack
.client(&tx.client().system())
.schedule_job(
"refresh-post-counts",
&hourly,
"SELECT refresh_post_counts()",
)
.await?;
tx.commit().await?;Rolling back also rolls back the new job. The job itself runs later in a separate database session, not in the transaction that scheduled it. Treat its SQL command as trusted application code, never user input.
Run the application
Add the extension declaration above to your application's schema. Confirm that your PostgreSQL provider offers the extension and that your migration role can install it, then apply the migration and run your application:
rent migrate dev --name enable_pg_cron
cargo runEnsure pg_cron is preloaded and cron.database_name names this database. Configure worker execution or connection authentication for scheduled jobs.
Complete example
Download this project, or copy the files below into an empty directory.
Replace RENT_REPOSITORY_URL in Cargo.toml with the URL supplied with your preview access. Use the matching Rent CLI release.
Use an empty PostgreSQL database with the extension binaries available on its server. Set DATABASE_URL
to that database and follow any server setup requirements described above.
rent extension check --provider self-hosted
rent generate
rent migrate dev --name initial
cargo runIf your host preinstalls other extensions, declare those in RSL as well. For a database with existing application tables, baseline it first.
Cargo.toml
[package]
name = "rent-extension-pg-cron"
version = "0.1.0"
edition = "2024"
[workspace]
[[bin]]
name = "rent-extension-pg-cron"
path = "main.rs"
[dependencies]
rent = { git = "RENT_REPOSITORY_URL", tag = "v0.8.6", default-features = false, features = ["postgres", "extensions", "migrate"] }
rent-ext-pg-cron = { git = "RENT_REPOSITORY_URL", tag = "v0.8.6" }
anyhow = "1.0.104"
tokio = { version = "1.53.1", features = ["full"] }rent.toml
schema = "schema.rsl"
generated = "generated"
migrations = "migrations"
[database]
dialect = "postgres"
backends = ["postgres"]schema.rsl
datasource db {
provider = "postgresql"
}
extension pg_cron {
name = "pg_cron"
}
model Post {
id BigInt @id
title String
body String @default("")
}main.rs
use anyhow::Result;
use rent::{driver::DatabasePool, extension::ExtensionRegistry};
#[allow(dead_code, unused_imports)]
#[rustfmt::skip]
#[path = "generated/mod.rs"]
mod generated;
#[tokio::main]
async fn main() -> Result<()> {
let pool = DatabasePool::connect_url(&std::env::var("DATABASE_URL")?).await?;
let mut extensions = ExtensionRegistry::default();
use rent_ext_pg_cron::{CronSchedule, PgCron, PgCronClientExt};
let pack = extensions.register_pack::<PgCron>()?;
let cron = pack.client(&pool);
let hourly = CronSchedule::new("0 * * * *")?;
let job = cron.schedule_job("count-posts", &hourly, "SELECT count(*) FROM posts").await?;
println!("Scheduled job {job}");
anyhow::ensure!(cron.unschedule_job("count-posts").await?);
Ok(())
}