Migrate Timescale Hypertables To Vanilla Postgres

My reasons for migration are due to lack of deeper understanding (and time for it) of the mechanics related to timescale internal hypertables maintenance and an incident where I had data loss during an upgrade. Life is busy so reducing the complexity and returning to vanilla postgres is the best way forward.

Timescale, hypertables and the migration problem

Timescale enhances Postgres for time-series data by introducing hypertables, which are virtual tables that automatically partition data into smaller, manageable tables called chunks, typically based on a time dimension. A common tool like pg_dump interacts only with the main hypertable, which is essentially an empty parent table containing metadata and pointers to its chunks. Since the actual data resides within these child chunk tables, a standard pg_dump captures the schema of the empty parent but fails to include the data-filled chunks.

Solution

This data is visible when queried directly with psql, allowing the COPY with a TO 'table.csv' approach, but this is a highly concurrent where +98% of the operations are inserts, and restoring this to a new database when clients are performing operations without any better approach (ON CONFLICT .. DO NOTHING) would result in errors during the process. A simpler approach is to let the database itself handle the conflicts.

With this in mind the best way forward is to convert the table by just creating a new table with the same structure and content, assuming a hypertable named my_table, the migration goes as follows:

CREATE TABLE my_table_raw AS SELECT * FROM my_table;
ALTER TABLE my_table RENAME TO my_table_hypertable;
ALTER TABLE my_table_raw RENAME TO my_table;

Finally pg_dump can dump the database properly and migrate it to a new vanilla Postgres instance:


pg_dump \
  --data-only \
  --column-inserts \
  --table=ticker \
  -U "user" -h "postgres-host.domain.com" -p "5432" \
  -d "database_name" \
    | sed 's/);$/) ON CONFLICT (col1, col2) DO NOTHING;/' > table.sql

Alternative solution the csv export

Just to leave it documented in case is useful for someone reading this:

psql \
  -U "user" -h "postgres-host.domain.com" -p "5432" \
  -d "database_name"
  -c "\COPY (SELECT * FROM my_table) TO 'data.csv' WITH CSV HEADER"