-
Notifications
You must be signed in to change notification settings - Fork 0
Hypertables
Annotate a model with @timescale.hypertable and the generator converts the table into a TimescaleDB hypertable in a reset-safe migration.
/// @timescale.hypertable(column: "time", chunkInterval: "1 day")
model SensorReading {
time DateTime
deviceId Int
temperature Float
@@id([deviceId, time])
@@index([deviceId, time])
}-
column(required) — the time partitioning column (aDateTimefield). -
chunkInterval(optional, default"7 days") — the chunk size, an interval.
Insert with the normal Prisma API; query with normal Prisma or the typed timeBucket helper. Add retention / compression policies on the same model.
To change the chunk interval of a live hypertable later, use
$timescale.setChunkInterval— re-generating the schema can't change it (create_hypertableis a no-op once the table exists).
Add partitionColumn + partitions for a hash space dimension (add_dimension(..., by_hash(column, partitions))) on top of the time dimension:
/// @timescale.hypertable(column: "time", chunkInterval: "1 day", partitionColumn: "deviceId", partitions: 4)
model SensorReading {
time DateTime
deviceId Int
@@id([deviceId, time]) // a partitioning column must be part of the table's PK / unique key
}partitionColumn takes a Prisma field name (mapped to its @map column); partitions is a positive integer. It's transparent to timeBucket queries and survives prisma migrate reset. The generator rejects a partitionColumn that isn't in every PK / unique constraint (TimescaleDB requires all partitioning columns in unique indexes).
For a compressed hypertable, chunk skipping records per-chunk min/max range stats for a secondary (non-time) column, so the planner can exclude whole chunks that can't match a filter on that column — a big win for WHERE deviceId = … / WHERE eventId BETWEEN … on data that correlates with time. Add chunkSkipping (one Prisma field name, or several comma-separated):
/// @timescale.hypertable(column: "time", chunkInterval: "1 day", chunkSkipping: "eventId")
/// @timescale.compression(after: "7 days", segmentBy: "deviceId")
model SensorReading {
time DateTime
deviceId Int
eventId BigInt
temperature Float
@@id([deviceId, time])
}The generator emits a reset-safe block that sets the timescaledb.enable_chunk_skipping GUC (SET LOCAL) and calls enable_chunk_skipping(...) inside one self-contained DO block:
DO $$ BEGIN
SET LOCAL timescaledb.enable_chunk_skipping = on;
PERFORM enable_chunk_skipping('"SensorReading"', 'eventId', if_not_exists => TRUE);
END $$;You can also toggle it at runtime (the path to use with the manual config); the column is a Prisma field name mapped to its @map column:
await prisma.$timescale.enableChunkSkipping("SensorReading", "eventId");
await prisma.$timescale.disableChunkSkipping("SensorReading", "eventId");Important
Three things to know — chunk skipping is otherwise a silent no-op:
-
Turn the GUC on at query time too. The planner only skips chunks when
timescaledb.enable_chunk_skippingis on for the querying session. Set it as a persistent default (run once, e.g. in a manual migration):ALTER DATABASE your_db SET timescaledb.enable_chunk_skipping = on;, or per connection via…?options=-c%20timescaledb.enable_chunk_skipping%3Don. This package doesn't emit theALTER DATABASEautomatically — it's database-wide and may need elevated privileges (e.g. on managed/Tiger Cloud). - Range stats only exist on compressed chunks. Pair it with compression; uncompressed chunks are always scanned.
-
Not a partitioning or
segmentBycolumn.segmentByalready gives per-segment min/max, and enabling skipping there returns wrong (empty) results — the generator rejects it, along with the time column and the hash space-partition column.
See also: $timescale chunk management (inspect / compress / drop / resize chunks).