Skip to content

Management

Krister-Johansson edited this page Jun 17, 2026 · 1 revision

$timescale management

The extension adds a $timescale namespace for runtime management. It's also the way to manage policies on the manual config path (no annotations).

Typo-safe. The model/view names passed to every $timescale method are checked against your registered hypertables and continuous aggregates — refreshContinuousAggregate("SensorHrly") is a compile error, not a runtime surprise. This works automatically from the generated registry (and from inline timescaledb({ hypertables: [...] }) literals); only names built from runtime string variables fall back to unchecked string.

Continuous aggregates

await prisma.$timescale.refreshContinuousAggregate("SensorHourly");                 // full refresh
await prisma.$timescale.refreshContinuousAggregate("SensorHourly", { start, end }); // window

await prisma.$timescale.addContinuousAggregatePolicy("SensorHourly", {
  startOffset: "1 month", endOffset: "1 hour", scheduleInterval: "1 hour",
});
await prisma.$timescale.removeContinuousAggregatePolicy("SensorHourly");

If a scheduled refresh policy happens to be running on the same cagg, a manual refresh would otherwise fail with SQLSTATE 55P03 ("could not refresh … due to a concurrent refresh"). refreshContinuousAggregate retries this transient case with bounded exponential backoff (default up to 8 attempts); if contention persists, the original 55P03 is rethrown.

Retention & compression policies

await prisma.$timescale.addRetentionPolicy("SensorReading", { dropAfter: "30 days" });
await prisma.$timescale.removeRetentionPolicy("SensorReading");

await prisma.$timescale.addCompressionPolicy("SensorReading", { after: "7 days", segmentBy: "deviceId", orderBy: "time DESC" });
await prisma.$timescale.removeCompressionPolicy("SensorReading");

See Retention & compression for the annotation equivalents and the "changing an existing policy" caveats.

Inspecting & dropping chunks

// List chunks (relation names), optionally bounded by interval — feeds compress/decompress/drop.
const chunks = await prisma.$timescale.showChunks("SensorReading");                 // string[]
const old    = await prisma.$timescale.showChunks("SensorReading", { olderThan: "30 days" });

// Convert a single chunk to/from the columnstore on demand (needs the columnstore enabled).
await prisma.$timescale.compressChunk(chunks[0]);
await prisma.$timescale.decompressChunk(chunks[0]);

// On-demand chunk drop (manual retention) — returns the dropped chunk names.
const dropped = await prisma.$timescale.dropChunks("SensorReading", { olderThan: "30 days" });

// Size, row-count & compression introspection — sizes/counts come back as exact `bigint`.
const bytes  = await prisma.$timescale.hypertableSize("SensorReading");         // bigint
const detail = await prisma.$timescale.hypertableDetailedSize("SensorReading"); // { tableBytes, indexBytes, toastBytes, totalBytes }
const rowest = await prisma.$timescale.approximateRowCount("SensorReading");    // bigint (planner estimate; fast)
const stats  = await prisma.$timescale.compressionStats("SensorReading");       // { totalChunks, compressedChunks, beforeTotalBytes, afterTotalBytes }

showChunks returns show_chunks relation names (e.g. _timescaledb_internal._hyper_1_2_chunk) — pass one straight to compressChunk / decompressChunk (idempotent). Compressing a chunk requires the columnstore enabled on the hypertable (via @timescale.compression or addCompressionPolicy). showChunks / dropChunks bounds (olderThan / newerThan) are intervals relative to now (combine both for a window) — absolute-timestamp cutoffs aren't supported.

Resizing chunks (setChunkInterval)

await prisma.$timescale.setChunkInterval("SensorReading", "6 hours");

Changes the time-dimension chunk interval of a live hypertable (set_partitioning_interval). It affects only chunks created after the call — existing chunks keep their interval, so it never rewrites data. This is the runtime complement to the chunkInterval annotation (which only sets the size at create time).

Background jobs & policies

Every policy this package creates (retention, compression, cagg refresh) runs as a TimescaleDB background job. These helpers inspect and control them — the "did my retention actually run?" / "pause compression for now" surface.

// Inspect — filter by model (a hypertable or a cagg), or omit for all jobs.
const jobs  = await prisma.$timescale.listJobs("SensorReading");   // [{ jobId, procName, scheduleInterval, scheduled, config, nextStart, ... }]
const jstats = await prisma.$timescale.jobStats("SensorReading");  // last/next run, status, totalRuns/Successes/Failures (bigint)
const errs  = await prisma.$timescale.jobErrors("SensorReading");  // recent failures (SQLSTATE + message), newest first

// Control — by numeric job id (from listJobs).
const { jobId } = jobs[0];
await prisma.$timescale.alterJob(jobId, { scheduled: false });                            // pause
await prisma.$timescale.alterJob(jobId, { scheduled: true, scheduleInterval: "2 days" }); // resume + reschedule
await prisma.$timescale.runJob(jobId);                                                    // run now, synchronously
await prisma.$timescale.deleteJob(jobId);                                                 // remove a custom job

listJobs/jobStats/jobErrors read the timescaledb_information.jobs / job_stats / job_errors views and resolve the model name to its hypertable table or cagg view (so the filter spans both). alterJob only changes the fields you pass (scheduled, scheduleInterval, nextStart, maxRetries, retryPeriod, maxRuntime, config) and is a no-op if the job is already gone. For the package's own policies, prefer the dedicated remove*Policy methods over deleteJob; alterJob/runJob work on all of them by id.

Clone this wiki locally