--- title: "Cómo instalar TimescaleDB en Ubuntu 24.04 con PostgreSQL" description: "Instala TimescaleDB sobre PostgreSQL 17 en Ubuntu 24.04, crea hypertables, agregados continuos y políticas de compresión y retención para series temporales." category: "Base de Datos Avanzada" language: es source: https://cubepath.com/es/docs/base-de-datos-avanzada/instalacion-de-timescaledb-para-datos-de-series-de-tiempo updated: 2026-09-25 --- # Cómo instalar TimescaleDB en Ubuntu 24.04 con PostgreSQL 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: ```bash 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: ```bash apt-cache policy postgresql-17 | head -n 3 ``` ```output 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: ```bash 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`): ```bash 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: ```bash pg_lsclusters ``` ```output 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: ```bash 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: ```bash sudo systemctl restart postgresql ``` Comprueba que la biblioteca se carga: ```bash sudo -u postgres psql -c "SHOW shared_preload_libraries;" ``` ```output shared_preload_libraries -------------------------- timescaledb (1 row) ``` > Nota: si más adelante cambias la RAM del servidor, vuelve a ejecutar `timescaledb-tune` para recalcular `shared_buffers`, `effective_cache_size` y `work_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: ```bash 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: ```bash sudo -u postgres psql -d metrics -c "CREATE EXTENSION IF NOT EXISTS timescaledb;" ``` Verifica la versión instalada: ```bash sudo -u postgres psql -d metrics -c "\dx timescaledb" ``` ```output 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: ```bash 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: ```sql 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: ```sql CREATE INDEX ON system_metrics (host_id, time DESC); ``` Inserta dos semanas de datos de prueba para tres hosts, un punto por minuto: ```sql 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: ```sql SELECT count(*) AS chunks FROM show_chunks('system_metrics'); ``` ```output 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: ```sql 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()`: ```sql 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: ```sql 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: ```sql 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): ```sql 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: ```sql CALL refresh_continuous_aggregate('system_metrics_hourly', NULL, now() - INTERVAL '1 hour'); ``` Consulta el agregado: ```sql SELECT * FROM system_metrics_hourly WHERE host_id = 1 ORDER BY bucket DESC LIMIT 3; ``` ```output 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: ```sql 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: ```sql 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: ```sql 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: ```sql 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'); ``` ```output antes | despues --------+--------- 2552 kB | 312 kB (1 row) ``` > Nota: en 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`: ```sql 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: ```sql SELECT add_retention_policy('system_metrics_hourly', INTERVAL '2 years'); ``` Revisa todos los trabajos programados y su último resultado: ```sql 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; ``` ```output 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`**: falta `timescaledb` en `shared_preload_libraries`. Vuelve a ejecutar `timescaledb-tune` (paso 3) o añade la línea a mano en `/etc/postgresql/17/main/postgresql.conf` y reinicia PostgreSQL. - **`psql: error: ... password authentication failed`**: la contraseña no coincide. Cámbiala con `sudo -u postgres psql -c "ALTER ROLE metrics_user PASSWORD 'nueva_password';"`. - **Los trabajos no se ejecutan**: comprueba `SHOW timescaledb.max_background_workers;` y que `max_worker_processes` sea mayor que ese valor más los workers paralelos. `timescaledb-tune` ajusta ambos. - **Errores tras actualizar el paquete**: después de un `apt upgrade` que cambie la versión de TimescaleDB, conéctate con `psql -X` como `postgres` y ejecuta `ALTER 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_hourly` como fuente de los paneles. - Configurar copias de seguridad con `pg_dump` o con herramientas como pgBackRest. - Permitir conexiones remotas editando `listen_addresses` y `pg_hba.conf`, limitando el acceso por IP con UFW.