Quick Start
GanosBase TSDB is a time-series database plug-in for PolarDB for PostgreSQL. It inherits all capabilities of a PolarDB for PostgreSQL cluster, including shared storage, one-write-multiple-reads, and backup and recovery. It is fully compatible with TimescaleDB (Apache 2.0) and adds advanced features — continuous aggregation, data compression, and OSS tiered storage for hot and cold data — that are not available in the open-source edition.
Prerequisites
Before you begin, ensure that you have:
A PolarDB for PostgreSQL cluster running PostgreSQL 14 (minor engine version 2.0.14.13.26.0 or later) or PostgreSQL 16 (minor engine version 2.0.16.9.8.0 or later)
To check your minor engine version, run SHOW polardb_version; or view it in the console. If the version does not meet the requirement, upgrade the minor engine version.Key concepts
Two concepts are central to using GanosBase TSDB effectively:
Hypertable: A special table type that automatically partitions time-series data by time (and optionally by other dimensions). All standard SQL operations work on hypertables, with added time-series capabilities. The default partition interval is 7 days.
Continuous aggregate: An automated precomputation mechanism backed by a materialized view. It incrementally updates predefined aggregations — such as hourly averages — so queries read precomputed results instead of scanning raw data.
Enable the time-series database
Add
timescaledbto theshared_preload_librariesparameter for your cluster. Follow the steps in modify the shared_preload_libraries parameter.ImportantModifying
shared_preload_librariesrestarts the cluster. Plan your maintenance window before making this change.
Create the Plug-in
Upgrade the plug-in
Work with hypertables
A hypertable partitions time-series data into chunks by time. Each chunk covers a specific time range and stores only data from that range. When you insert data for a time range that has no chunk yet, the system creates one automatically.
Hypertables coexist with regular tables and support all standard SQL operations. The default partition interval is 7 days.
Create a hypertable
Convert a standard table to a hypertable using create_hypertable:
SELECT create_hypertable(
'table_name',
'time_column_name',
'partitioning_column_name',
number_partitions,
'associated_schema_name',
'associated_table_prefix',
chunk_time_interval,
create_default_indexes,
if_not_exists,
partitioning_func,
migrate_data,
chunk_target_size,
chunk_sizing_func,
time_partitioning_func
);| Parameter | Required | Description |
|---|---|---|
table_name | Yes | Name or OID of the source table. The hypertable keeps the same name after conversion. |
time_column_name | Yes | Name of the time column. Supported types: TIMESTAMP, TIMESTAMPTZ, DATE, integer (interpreted as microseconds), or INTERVAL. |
partitioning_column_name | No | Name of the partitioning column. Defaults to the time column. |
number_partitions | No | Number of partitions. |
associated_schema_name | No | Schema where the hypertable resides. |
associated_table_prefix | No | Prefix for chunk tables. |
chunk_time_interval | No | Time interval for chunks. Default is 7 days. |
create_default_indexes | No | Whether to create a B-tree index on the time column. true (default): creates the index. false: skips it. |
if_not_exists | No | Whether to suppress an error if the hypertable already exists. true: warns but does not raise an error. false (default): raises an error if the hypertable already exists. |
partitioning_func | No | Custom spatial partitioning function, if using spatial partitioning. |
migrate_data | No | Whether to move existing data from the source table. true: transfers existing data. false (default): keeps data in the source table. |
chunk_target_size | No | Target size for chunks (for example, '1000MB', 'estimate', or 'off'). |
chunk_sizing_func | No | Custom function to calculate the chunk time interval. Used with chunk_target_size. |
time_partitioning_func | No | Custom partition function for time partitioning. |
Change the partition interval
To change the chunk time interval after creating a hypertable:
SELECT set_chunk_time_interval(
'table_name',
chunk_time_interval,
'dimension_name'
);| Parameter | Description |
|---|---|
table_name | Name of the hypertable. |
chunk_time_interval | New time interval for chunks. |
dimension_name | (Optional) The partition dimension to update. Default is NULL. |
Example: create a hypertable for transaction data
This example creates a hypertable for financial transaction data, partitioned by day.
Create a standard table:
CREATE TABLE transaction_data( tm TIMESTAMPTZ NOT NULL, id INT NOT NULL, price double precision);Set the replication identity:
ALTER TABLE transaction_data REPLICA IDENTITY DEFAULT;Convert to a hypertable with a 1-day chunk interval:
If the table already contains data, set
migrate_data => true. Migration may take a long time for large tables.SELECT create_hypertable('transaction_data', 'tm', chunk_time_interval => INTERVAL '1 day');(Optional) Change the chunk interval to 2 days:
SELECT set_chunk_time_interval('transaction_data', chunk_time_interval => INTERVAL '2 day');
Work with continuous aggregates
Choose an aggregation approach
Before creating a continuous aggregate, choose the right aggregation type for your use case:
| Type | How it works | Best for |
|---|---|---|
| Materialized view | Standard PostgreSQL. Refreshed manually with REFRESH MATERIALIZED VIEW. | One-time or infrequent aggregations where freshness is not critical. |
| Continuous aggregate | GanosBase TSDB feature. Refreshed incrementally in the background. Only new or changed data is recomputed. | Regular aggregations on large time-series datasets where query speed matters. |
| Real-time aggregate | A continuous aggregate with real-time query enabled. Combines precomputed results with the latest raw data not yet aggregated. | Queries that need up-to-the-second results on top of historical aggregations. |
Use a continuous aggregate when you need fast, recurring queries on large datasets. Enable real-time query on top if you also need the most recent, unaggregated data.
Create a continuous aggregate
A continuous aggregate is declared as a materialized view with timescaledb.continuous:
CREATE MATERIALIZED VIEW transaction_min_cagg
WITH (timescaledb.continuous) -- Declare as a continuous aggregate
AS
SELECT id,
time_bucket(INTERVAL '1 min', tm) AS bucket, -- Aggregate by 1-minute intervals
AVG(price),
MAX(price),
MIN(price)
FROM transaction_data
GROUP BY id, bucket;Results are stored in time-aligned chunks that match the underlying hypertable's partitioning. Continuous aggregates support incremental updates — only buckets with new or modified data are recomputed.
Enable real-time queries
By default, querying a continuous aggregate returns only completed aggregations. Enable real-time query to include newly ingested data that has not yet been aggregated:
-- Enable real-time query
ALTER MATERIALIZED VIEW transaction_min_cagg SET (timescaledb.materialized_only = false);
-- Disable real-time query
ALTER MATERIALIZED VIEW transaction_min_cagg SET (timescaledb.materialized_only = true);Create nested aggregates
Build continuous aggregates on top of other continuous aggregates to reduce redundancy and improve performance. For example, create hourly aggregates from per-minute ones:
Create a per-minute aggregate:
CREATE MATERIALIZED VIEW transaction_min_cagg WITH (timescaledb.continuous) AS SELECT id, time_bucket(INTERVAL '1 min', tm) AS bucket, AVG(price) AS avg, MAX(price) AS max, MIN(price) AS min FROM transaction_data GROUP BY id, bucket;Create an hourly aggregate from the per-minute aggregate:
CREATE MATERIALIZED VIEW transaction_one_hour_mview WITH (timescaledb.continuous) AS SELECT id, time_bucket(INTERVAL '1 hour', bucket) AS bucket, AVG(avg) AS avg, MAX(max) AS max, MIN(min) AS min FROM transaction_min_cagg GROUP BY id, time_bucket(INTERVAL '1 hour', bucket);
Refresh a continuous aggregate
Refresh manually
Call refresh_continuous_aggregate to refresh a specific time window:
refresh_continuous_aggregate(
cagg REGCLASS,
window_start "any",
window_end "any"
);| Parameter | Description |
|---|---|
cagg | Name or OID of the continuous aggregate to refresh. |
window_start | Start of the refresh window. Must match the time column type. If not aligned to a unit boundary, use date_trunc — for example, for minute-level aggregation, set 2024-03-11 13:01:29 to 2024-03-11 13:01:00. Avoid NULL: it triggers a full table scan, which can be slow on large tables. |
window_end | End of the refresh window. Set to NULL to refresh up to the latest data. |
Usage notes:
The refresh window must align exactly with the time buckets. Only fully covered buckets are refreshed; partial buckets are skipped.
If multiple refresh calls have overlapping windows, only buckets with new or modified data are updated.
refresh_continuous_aggregateis a stored procedure — call it withCALL.
Example:
CALL refresh_continuous_aggregate('transaction_min_cagg', '2024-03-11 00:00:00+08'::timestamptz, NULL);Refresh automatically
Two options are available for automatic refresh.
Use add_continuous_aggregate_policy
Manage scheduled jobs
GanosBase TSDB uses a job scheduling framework to run functions or stored procedures on a schedule. The following functions manage jobs.
Create a job
integer add_job(
proc REGPROC,
schedule_interval INTERVAL,
config JSONB DEFAULT NULL,
initial_start TIMESTAMPTZ DEFAULT NULL,
scheduled BOOL DEFAULT true,
check_config REGPROC DEFAULT NULL,
fixed_schedule BOOL DEFAULT TRUE,
timezone TEXT DEFAULT NULL
);| Parameter | Description |
|---|---|
proc | Name or OID of the function or stored procedure to run. |
schedule_interval | Interval between job runs. |
config | Job configuration parameters. |
initial_start | Start time for the job. Default is now. |
scheduled | Whether to run automatically. Default is true. |
check_config | Function to validate the config parameter. Default is NULL. |
fixed_schedule | Whether to run at fixed intervals. Default is true. |
timezone | Time zone. Default is NULL. |
Example:
Create the stored procedure to run:
CREATE OR REPLACE PROCEDURE user_defined_action(job_id int, config jsonb) LANGUAGE PLPGSQL AS $$ BEGIN RAISE NOTICE 'Executing action % with config %', job_id, config; END $$;Create the job. The function returns the job ID:
-- Run every hour, starting immediately SELECT add_job('user_defined_action', '1 hour'); -- Run every hour, starting at a specific time SELECT add_job('user_defined_action', '1h', initial_start => '2024-03-11 00:00:00');
Modify a job
void alter_job(
job_id INTEGER,
schedule_interval INTERVAL = NULL,
max_runtime INTERVAL = NULL,
max_retries INTEGER = NULL,
retry_period INTERVAL = NULL,
scheduled BOOL = NULL,
config JSONB = NULL,
next_start TIMESTAMPTZ = NULL,
if_exists BOOL = FALSE,
check_config REGPROC = NULL,
fixed_schedule BOOL = NULL,
initial_start TIMESTAMPTZ = NULL,
timezone TEXT DEFAULT NULL
);Key parameters (see add_job for the full list):
| Parameter | Description |
|---|---|
job_id | ID of the job to modify. |
max_runtime | Maximum runtime for the job. Default is NULL. |
max_retries | Maximum number of retries after failure. Default is NULL. |
retry_period | Time between retries after failure. Default is NULL. |
Example:
SELECT alter_job(1002, schedule_interval => INTERVAL '2 hours');Run a job manually
void run_job(job_id int);| Parameter | Description |
|---|---|
job_id | ID of the job to run. |
Example:
CALL run_job(1002);Delete a job
void delete_job(job_id int);| Parameter | Description |
|---|---|
job_id | ID of the job to delete. |
Example:
SELECT delete_job(1002);Enable time-series compression
When a time-series partition becomes historical data, compress it to reduce storage costs. GanosBase TSDB supports compressing entire tables or individual partitions, reducing storage space by over 70%.
Compressed data is read-only.
Uninstall the plug-in
DROP EXTENSION ganos_tsdb CASCADE;
DROP EXTENSION timescaledb CASCADE;