TimescaleDB es una extensión de PostgreSQL pensada para series temporales: métricas de servidores, sensores IoT, datos financieros o eventos de aplicaciones. Particiona automáticamente las tablas por tiempo (hypertables), comprime los datos antiguos y mantiene agregados precalculados, todo con SQL estándar. En este tutorial instalarás PostgreSQL 17 y TimescaleDB en Ubuntu 24.04, crearás una hypertable de métricas, un agregado continuo por horas y políticas automáticas de compresión y retención.
Requisitos previos
Para seguir esta guía necesitas:
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
- Un usuario no root con privilegios
sudo. - Al menos 2 GB de RAM (TimescaleDB usa la memoria de PostgreSQL para caché e índices) y espacio en disco suficiente para tus datos.
- Conocimientos básicos de SQL y de
psql.
Paso 1: Añadir el repositorio oficial de PostgreSQL
Ubuntu 24.04 incluye PostgreSQL 16, pero los paquetes de TimescaleDB se compilan contra las versiones del repositorio oficial de PostgreSQL (PGDG). El paquete postgresql-common trae un script que configura ese repositorio con su clave:
sudo apt update
sudo apt install -y postgresql-common curl gnupg lsb-release
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh
Pulsa Enter cuando el script lo pida. Comprueba que el repositorio ha quedado registrado:
apt-cache policy postgresql-17 | head -n 3
postgresql-17:
Installed: (none)
Candidate: 17.6-1.pgdg24.04+1
Paso 2: Añadir el repositorio de TimescaleDB e instalar los paquetes
Descarga la clave GPG de Timescale en /etc/apt/keyrings y crea el archivo del repositorio vinculado a esa clave:
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://packagecloud.io/timescale/timescaledb/gpgkey | sudo gpg --dearmor -o /etc/apt/keyrings/timescaledb.gpg
echo "deb [signed-by=/etc/apt/keyrings/timescaledb.gpg] https://packagecloud.io/timescale/timescaledb/ubuntu/ $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/timescaledb.list
Instala PostgreSQL 17, la extensión y las herramientas de TimescaleDB (incluyen timescaledb-tune):
sudo apt update
sudo apt install -y postgresql-17 timescaledb-2-postgresql-17 timescaledb-tools
El paquete de PostgreSQL crea y arranca automáticamente el clúster 17/main. Verifica que está en marcha:
pg_lsclusters
Ver Cluster Port Status Owner Data directory Log file
17 main 5432 online postgres /var/lib/postgresql/17/main /var/log/postgresql/postgresql-17-main.log
Paso 3: Ajustar PostgreSQL con timescaledb-tune
TimescaleDB necesita cargarse al arrancar PostgreSQL mediante shared_preload_libraries. La herramienta timescaledb-tune añade esa línea y ajusta memoria, workers y WAL según la RAM y las CPU del servidor:
sudo timescaledb-tune --pg-config=/usr/lib/postgresql/17/bin/pg_config --yes
La herramienta edita /etc/postgresql/17/main/postgresql.conf y muestra cada cambio que aplica. Reinicia PostgreSQL para cargar la extensión:
sudo systemctl restart postgresql
Comprueba que la biblioteca se carga:
sudo -u postgres psql -c "SHOW shared_preload_libraries;"
shared_preload_libraries
--------------------------
timescaledb
(1 row)
Notasi más adelante cambias la RAM del servidor, vuelve a ejecutar
timescaledb-tunepara recalcularshared_buffers,effective_cache_sizeywork_mem.
Paso 4: Crear la base de datos y activar la extensión
Crea un rol para tu aplicación y una base de datos que le pertenezca. Sustituye your_strong_password por una contraseña robusta:
sudo -u postgres psql -c "CREATE ROLE metrics_user WITH LOGIN PASSWORD 'your_strong_password';"
sudo -u postgres createdb -O metrics_user metrics
La extensión debe crearla un superusuario, así que actívala como postgres dentro de la nueva base de datos:
sudo -u postgres psql -d metrics -c "CREATE EXTENSION IF NOT EXISTS timescaledb;"
Verifica la versión instalada:
sudo -u postgres psql -d metrics -c "\dx timescaledb"
List of installed extensions
Name | Version | Schema | Description
-------------+---------+--------+---------------------------------------------------------------------
timescaledb | 2.22.1 | public | Enables scalable inserts and complex queries for time-series data
(1 row)
A partir de aquí trabajarás como metrics_user. Conéctate por TCP a localhost, que usa autenticación por contraseña:
psql -h localhost -U metrics_user -d metrics
Todas las sentencias SQL de los pasos siguientes se ejecutan en esta sesión de psql.
Paso 5: Crear una hypertable
Una hypertable se usa como una tabla normal, pero TimescaleDB la divide internamente en fragmentos (chunks) por intervalos de tiempo. Así las consultas sobre un rango de fechas solo leen los chunks necesarios y borrar datos antiguos consiste en eliminar chunks enteros.
Crea una tabla de métricas y conviértela en hypertable particionada por la columna time, con chunks de un día:
CREATE TABLE system_metrics (
time TIMESTAMPTZ NOT NULL,
host_id INTEGER NOT NULL,
cpu_usage DOUBLE PRECISION,
memory_usage DOUBLE PRECISION
);
SELECT create_hypertable('system_metrics', by_range('time', INTERVAL '1 day'));
TimescaleDB crea automáticamente un índice sobre time. Añade uno compuesto para las consultas filtradas por host:
CREATE INDEX ON system_metrics (host_id, time DESC);
Inserta dos semanas de datos de prueba para tres hosts, un punto por minuto:
INSERT INTO system_metrics (time, host_id, cpu_usage, memory_usage)
SELECT t, h, random() * 100, 40 + random() * 50
FROM generate_series(now() - INTERVAL '14 days', now(), INTERVAL '1 minute') AS t,
generate_series(1, 3) AS h;
Comprueba que los datos se han repartido en chunks:
SELECT count(*) AS chunks FROM show_chunks('system_metrics');
chunks
--------
15
(1 row)
Paso 6: Consultar series temporales
La función time_bucket() agrupa filas en intervalos de tiempo arbitrarios, algo que date_trunc() no permite. Esta consulta calcula la CPU media y máxima cada 15 minutos durante la última hora:
SELECT time_bucket('15 minutes', time) AS bucket,
host_id,
round(avg(cpu_usage)::numeric, 1) AS avg_cpu,
round(max(cpu_usage)::numeric, 1) AS max_cpu
FROM system_metrics
WHERE time > now() - INTERVAL '1 hour'
GROUP BY bucket, host_id
ORDER BY bucket DESC, host_id;
Para obtener el último valor registrado de cada host, usa el agregado last():
SELECT host_id, last(cpu_usage, time) AS last_cpu
FROM system_metrics
WHERE time > now() - INTERVAL '10 minutes'
GROUP BY host_id;
Si faltan puntos en un intervalo, time_bucket_gapfill() genera los huecos y locf() los rellena con el último valor conocido:
SELECT time_bucket_gapfill('5 minutes', time) AS bucket,
locf(avg(cpu_usage)) AS cpu
FROM system_metrics
WHERE host_id = 1
AND time > now() - INTERVAL '1 hour' AND time < now()
GROUP BY bucket
ORDER BY bucket;
Paso 7: Crear un agregado continuo
Un agregado continuo es una vista materializada que TimescaleDB actualiza de forma incremental: solo recalcula los intervalos que han recibido datos nuevos. Es la forma habitual de alimentar paneles que muestran medias horarias o diarias sin recorrer millones de filas.
Crea un agregado horario por host:
CREATE MATERIALIZED VIEW system_metrics_hourly
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 hour', time) AS bucket,
host_id,
avg(cpu_usage) AS avg_cpu,
max(cpu_usage) AS max_cpu,
avg(memory_usage) AS avg_memory
FROM system_metrics
GROUP BY bucket, host_id
WITH NO DATA;
Añade una política que lo refresque cada hora, recalculando la ventana entre hace 3 horas y hace 1 hora (la hora en curso todavía está recibiendo datos):
SELECT add_continuous_aggregate_policy('system_metrics_hourly',
start_offset => INTERVAL '3 hours',
end_offset => INTERVAL '1 hour',
schedule_interval => INTERVAL '1 hour');
La política solo cubre las últimas horas, así que materializa el histórico una vez de forma manual:
CALL refresh_continuous_aggregate('system_metrics_hourly', NULL, now() - INTERVAL '1 hour');
Consulta el agregado:
SELECT * FROM system_metrics_hourly WHERE host_id = 1 ORDER BY bucket DESC LIMIT 3;
bucket | host_id | avg_cpu | max_cpu | avg_memory
------------------------+---------+--------------------+-------------------+-------------------
2026-09-25 09:00:00+00 | 1 | 49.81736212846231 | 99.77013391259872 | 64.93850211098512
2026-09-25 08:00:00+00 | 1 | 51.20398817344709 | 99.61120849287353 | 65.40017650120022
2026-09-25 07:00:00+00 | 1 | 48.66902417399152 | 98.95023128330924 | 64.12378923340917
(3 rows)
Por defecto, el agregado también combina en tiempo real los datos más recientes que aún no se han materializado, así que las consultas devuelven resultados actualizados.
Paso 8: Activar la compresión
La compresión convierte los chunks antiguos a un formato columnar que suele ocupar una fracción del original. Primero hay que activarla en la hypertable, indicando por qué columna segmentar (normalmente el identificador que usas en los filtros) y cómo ordenar:
ALTER TABLE system_metrics SET (
timescaledb.compress,
timescaledb.compress_segmentby = 'host_id',
timescaledb.compress_orderby = 'time DESC'
);
Añade una política que comprima automáticamente los chunks con más de 7 días:
SELECT add_compression_policy('system_metrics', INTERVAL '7 days');
La política se ejecuta en segundo plano. Para ver el efecto ya, comprime a mano los chunks que cumplen la condición:
SELECT compress_chunk(c, if_not_compressed => true)
FROM show_chunks('system_metrics', older_than => INTERVAL '7 days') AS c;
Compara el tamaño antes y después:
SELECT pg_size_pretty(before_compression_total_bytes) AS antes,
pg_size_pretty(after_compression_total_bytes) AS despues
FROM hypertable_compression_stats('system_metrics');
antes | despues
--------+---------
2552 kB | 312 kB
(1 row)
Notaen las versiones 2.18 y posteriores, Timescale denomina a esta función "columnstore" y ofrece alias como
add_columnstore_policy(). Las funciones de compresión usadas aquí siguen funcionando.
Paso 9: Configurar la retención de datos
Las métricas en bruto rara vez se necesitan para siempre. Una política de retención elimina los chunks enteros más antiguos que el intervalo indicado, lo que es mucho más rápido que un DELETE:
SELECT add_retention_policy('system_metrics', INTERVAL '90 days');
Los datos del agregado continuo no se borran con esta política, así que puedes conservar las medias horarias durante más tiempo que los datos originales. Si quieres limitar también el agregado, añádele su propia política:
SELECT add_retention_policy('system_metrics_hourly', INTERVAL '2 years');
Revisa todos los trabajos programados y su último resultado:
SELECT j.job_id, j.proc_name, j.hypertable_name, s.last_run_status, s.next_start
FROM timescaledb_information.jobs j
LEFT JOIN timescaledb_information.job_stats s USING (job_id)
WHERE j.job_id >= 1000;
job_id | proc_name | hypertable_name | last_run_status | next_start
--------+------------------------------------+----------------------------+-----------------+-------------------------------
1000 | policy_refresh_continuous_aggregate | _materialized_hypertable_2 | Success | 2026-09-25 10:12:03+00
1001 | policy_compression | system_metrics | Success | 2026-09-26 09:40:11+00
1002 | policy_retention | system_metrics | | 2026-09-26 09:41:02+00
1003 | policy_retention | _materialized_hypertable_2 | | 2026-09-26 09:41:30+00
(4 rows)
Solución de problemas
ERROR: extension "timescaledb" must be preloaded: faltatimescaledbenshared_preload_libraries. Vuelve a ejecutartimescaledb-tune(paso 3) o añade la línea a mano en/etc/postgresql/17/main/postgresql.confy reinicia PostgreSQL.psql: error: ... password authentication failed: la contraseña no coincide. Cámbiala consudo -u postgres psql -c "ALTER ROLE metrics_user PASSWORD 'nueva_password';".- Los trabajos no se ejecutan: comprueba
SHOW timescaledb.max_background_workers;y quemax_worker_processessea mayor que ese valor más los workers paralelos.timescaledb-tuneajusta ambos. - Errores tras actualizar el paquete: después de un
apt upgradeque cambie la versión de TimescaleDB, conéctate conpsql -Xcomopostgresy ejecutaALTER EXTENSION timescaledb UPDATE;en cada base de datos.
Conclusión
Has instalado PostgreSQL 17 con TimescaleDB en Ubuntu 24.04, has creado una hypertable de métricas, un agregado continuo horario y políticas automáticas de compresión y retención. Con esto, la base de datos mantiene el volumen de disco bajo control mientras las consultas recientes siguen siendo rápidas.
Como siguientes pasos, puedes:
- Conectar Grafana a PostgreSQL y usar
system_metrics_hourlycomo fuente de los paneles. - Configurar copias de seguridad con
pg_dumpo con herramientas como pgBackRest. - Permitir conexiones remotas editando
listen_addressesypg_hba.conf, limitando el acceso por IP con UFW.
