TimescaleDB
What it is
TimescaleDB extends PostgreSQL for time-series and event workloads. Its hypertables automatically partition rows into chunks while remaining queryable as ordinary PostgreSQL tables.
What it provides
- Automatic time-based partitioning through hypertables
- Retention, compression, and tiering policies
- Continuous aggregates and time-series analytical functions
Use it for application metrics, IoT readings, financial ticks, observability events, audit histories, or any append-heavy table primarily queried by time range.
Use it with Rent
extension timescaledb {
name = "timescaledb"
}The RSL declaration makes installation and migration intent part of the reviewed application schema. Register the typed pack at runtime to call its APIs:
let timescale = extensions.register_pack::<TimescaleDb>()?;
let client = timescale.client(&pool);
let metrics = client.hypertable("metrics");
metrics.create("recorded_at").await?;
metrics.retain_for("90 days").await?;
metrics.remove_retention().await?;Retention and compression policies return the 32-bit job ID as an Option<i32>. Removing a retention policy returns ().
Durations are PostgreSQL interval strings, such as "90 days". Invalid intervals return database errors.
Enable compression on the hypertable before calling metrics.compress_after("7 days"). Compression settings
depend on the table's workload and selected TimescaleDB version.
Use a TimescaleDB-enabled PostgreSQL server. These functions are not included in stock PostgreSQL.
Integer time axes
Some event streams use a bigint counter or Unix timestamp instead of a timestamp column. Use ticks consistently: the column, chunk width, retention window, and clock function must all use the same unit.
For a table post_ticks with a bigint tick column, register an existing zero-argument SQL clock that returns
bigint. For example, an epoch-seconds clock can return extract(epoch FROM CURRENT_TIMESTAMP)::bigint.
let events = client.hypertable("public.post_ticks");
events.create_integer("tick", 3_600).await?;
events.integer_clock("public.epoch_seconds").await?;
let retention_job = events.retain_ticks(86_400).await?;Here each chunk spans an hour and the retention window is one day. retain_ticks returns a non-null job ID.
Use retain_for("1 day") for timestamp columns. An interval is not interchangeable with integer ticks.
Missing clocks, mismatched time axes, and duplicate policies return errors. Policy registration uses the bound
transaction, so rolling back also rolls back registration. Background jobs execute separately after commit.
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_timescaledb
cargo runRetention and compression policies register background jobs. They do not immediately remove or compress data. Choose intervals for your retention requirements before applying policies.
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-timescaledb"
version = "0.1.0"
edition = "2024"
[workspace]
[[bin]]
name = "rent-extension-timescaledb"
path = "main.rs"
[dependencies]
rent = { git = "RENT_REPOSITORY_URL", tag = "v0.8.6", default-features = false, features = ["postgres", "extensions", "migrate"] }
rent-ext-timescaledb = { 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 timescaledb {
name = "timescaledb"
}
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_timescaledb::{TimescaleDb, TimescaleDbClientExt};
let pack = extensions.register_pack::<TimescaleDb>()?;
pool.execute("CREATE TABLE IF NOT EXISTS public.post_metrics (recorded_at timestamptz PRIMARY KEY, value double precision NOT NULL)").await?;
let metrics = pack.client(&pool);
metrics.hypertable("public.post_metrics").create("recorded_at").await?;
metrics.hypertable("public.post_metrics").retain_for("90 days").await?;
metrics.hypertable("public.post_metrics").remove_retention().await?;
println!("Post metrics use a time-series hypertable.");
Ok(())
}