QuestDB es una base de datos de series temporales en formato columnar, pensada para ingerir millones de filas por segundo y consultarlas con SQL. Acepta datos por el protocolo de línea de InfluxDB (ILP) y por el protocolo de PostgreSQL, así que puedes usar clientes y herramientas que ya conoces. En este tutorial desplegarás QuestDB en Ubuntu 24.04 con Docker Compose, crearás una tabla para datos de sensores con particionado y deduplicación, enviarás datos por ILP y los consultarás desde la consola web y desde psql.
Requisitos previos
- Un servidor con Ubuntu 24.04 LTS con al menos 2 GB de RAM y disco SSD, por ejemplo un VPS de CubePath.
- Un usuario no root con privilegios
sudoque pertenezca al grupodocker. - Docker Engine y el plugin de Docker Compose instalados desde el repositorio oficial de Docker.
Paso 1: Ajustar los límites del kernel
QuestDB mapea en memoria muchos archivos de columnas y particiones. Con los valores por defecto de Linux puede quedarse sin mapas de memoria cuando la base de datos crece, así que la documentación de QuestDB recomienda subir vm.max_map_count:
echo 'vm.max_map_count = 1048576' | sudo tee /etc/sysctl.d/60-questdb.conf
sudo sysctl --system
Comprueba el valor:
sysctl vm.max_map_count
vm.max_map_count = 1048576
El límite de archivos abiertos se ajusta para el contenedor en el archivo de Compose del paso siguiente.
Paso 2: Crear el proyecto de Docker Compose
Crea un directorio para el despliegue:
mkdir -p ~/questdb
cd ~/questdb
QuestDB expone por el protocolo de PostgreSQL el usuario admin con contraseña quest por defecto. Genera una contraseña propia y guárdala en un archivo .env que Compose leerá automáticamente:
echo "QDB_PG_PASSWORD=$(openssl rand -hex 24)" > .env
chmod 600 .env
Crea el archivo de Compose:
nano compose.yaml
services:
questdb:
image: questdb/questdb:10.0.1
container_name: questdb
restart: unless-stopped
ports:
- "127.0.0.1:9000:9000"
- "127.0.0.1:8812:8812"
environment:
QDB_PG_USER: admin
QDB_PG_PASSWORD: ${QDB_PG_PASSWORD}
QDB_TELEMETRY_ENABLED: "false"
volumes:
- questdb_data:/var/lib/questdb
ulimits:
nofile:
soft: 1048576
hard: 1048576
volumes:
questdb_data:
Algunos detalles de esta configuración:
- Consulta la última versión en la página de releases de QuestDB y fija la etiqueta de la imagen, en lugar de
latest, para controlar cuándo actualizas. - El puerto 9000 sirve la consola web, la API REST y la ingesta ILP por HTTP. El 8812 es el protocolo de PostgreSQL.
- Ambos puertos se publican solo en
127.0.0.1. La consola web y el endpoint de ingesta de QuestDB open source no piden autenticación, así que no deben quedar abiertos a Internet. - Cualquier opción de
server.confse puede pasar como variable de entorno con el prefijoQDB_, en mayúsculas y con_en lugar de puntos:pg.passwordse convierte enQDB_PG_PASSWORD.
Paso 3: Arrancar QuestDB
Descarga la imagen y arranca el contenedor en segundo plano:
docker compose up -d
Comprueba que está en marcha:
docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
questdb questdb/questdb:10.0.1 "/docker-entrypoint.…" questdb 12 seconds ago Up 11 seconds 127.0.0.1:8812->8812/tcp, 127.0.0.1:9000->9000/tcp
Lanza una consulta de prueba por la API REST. El endpoint /exec recibe SQL en el parámetro query y devuelve JSON:
curl -G http://127.0.0.1:9000/exec --data-urlencode "query=SELECT now()"
{"query":"SELECT now()","columns":[{"name":"now","type":"TIMESTAMP"}],"timestamp":-1,"dataset":[["2026-09-25T10:31:07.412593Z"]],"count":1}
Si no responde, revisa los registros con docker compose logs --tail 50 questdb.
Paso 4: Abrir la consola web por un túnel SSH
Como el puerto 9000 solo escucha en localhost, accede a la consola web desde tu ordenador mediante un túnel SSH. Ejecuta esto en tu máquina local, no en el servidor:
ssh -L 9000:127.0.0.1:9000 your_user@your_server_ip
Mientras la sesión esté abierta, abre http://localhost:9000 en el navegador. Verás el editor SQL de QuestDB con el listado de tablas a la izquierda. Puedes ejecutar en él todas las consultas SQL de esta guía.
Paso 5: Crear una tabla de series temporales
En QuestDB cada tabla de series temporales tiene una columna de marca de tiempo designada, que determina el orden físico de los datos y el particionado. Crea una tabla para lecturas de sensores:
curl -G http://127.0.0.1:9000/exec --data-urlencode "query=
CREATE TABLE sensores (
ts TIMESTAMP,
sensor SYMBOL,
ubicacion SYMBOL,
temperatura DOUBLE,
humedad DOUBLE
) TIMESTAMP(ts) PARTITION BY DAY WAL
DEDUP UPSERT KEYS(ts, sensor);"
{"ddl":"OK"}
Qué hace cada parte:
SYMBOLes un tipo para cadenas repetitivas (identificadores, ubicaciones). Se guarda como un diccionario de enteros, lo que ahorra espacio y acelera los filtros.TIMESTAMP(ts)marcatscomo marca de tiempo designada.PARTITION BY DAYguarda cada día en un directorio aparte, así que las consultas por rango de fechas solo leen las particiones necesarias y borrar datos antiguos es inmediato.WALactiva el registro de escritura previa, necesario para la deduplicación y para aceptar escrituras concurrentes.DEDUP UPSERT KEYS(ts, sensor)hace que una fila con el mismotsysensorque otra ya existente la sustituya en lugar de duplicarla. Es útil cuando un dispositivo reenvía datos tras un corte.
Paso 6: Ingerir datos con ILP sobre HTTP
El protocolo de línea de InfluxDB (ILP) es la forma más rápida de escribir en QuestDB. Cada línea tiene el formato tabla,simbolo=valor campo=valor marca_de_tiempo. Envía dos lecturas al endpoint /write; al omitir la marca de tiempo, QuestDB usa la hora del servidor:
curl -i -X POST http://127.0.0.1:9000/write --data-binary $'sensores,sensor=s1,ubicacion=almacen temperatura=21.4,humedad=48.2\nsensores,sensor=s2,ubicacion=oficina temperatura=23.1,humedad=41.0\n'
HTTP/1.1 204 No Content
Un 204 significa que QuestDB aceptó las filas. En las tablas WAL los datos se aplican de forma asíncrona y aparecen en las consultas en unos milisegundos. En producción no escribas ILP a mano: usa los clientes oficiales de QuestDB (Python, Java, Go, Rust, Node.js, C/C++, .NET), que agrupan filas, reintentan y gestionan los errores, o Telegraf con su salida influxdb_v2 apuntando a http://127.0.0.1:9000.
Comprueba ahora la deduplicación. Envía dos veces la misma lectura del sensor s9 con una marca de tiempo explícita en nanosegundos, cambiando solo la temperatura:
curl -s -X POST http://127.0.0.1:9000/write --data-binary 'sensores,sensor=s9,ubicacion=lab temperatura=20.0,humedad=50.0 1790000000000000000'
curl -s -X POST http://127.0.0.1:9000/write --data-binary 'sensores,sensor=s9,ubicacion=lab temperatura=20.5,humedad=50.0 1790000000000000000'
curl -G http://127.0.0.1:9000/exec --data-urlencode "query=SELECT ts, temperatura FROM sensores WHERE sensor = 's9'"
{"query":"SELECT ts, temperatura FROM sensores WHERE sensor = 's9'","columns":[{"name":"ts","type":"TIMESTAMP"},{"name":"temperatura","type":"DOUBLE"}],"timestamp":0,"dataset":[["2026-09-21T14:13:20.000000Z",20.5]],"count":1}
Solo hay una fila, con el último valor recibido.
Paso 7: Consultar series temporales con SQL
QuestDB añade a SQL varias extensiones para series temporales. Ejecuta estas consultas en la consola web o con curl como en los pasos anteriores.
SAMPLE BY agrega por intervalos de tiempo. Esta consulta calcula la media de temperatura y la humedad máxima por sensor en intervalos de un minuto durante la última hora:
SELECT ts, sensor, avg(temperatura) AS temp_media, max(humedad) AS humedad_max
FROM sensores
WHERE ts > dateadd('h', -1, now())
SAMPLE BY 1m;
LATEST ON devuelve la última lectura de cada sensor, algo habitual en paneles de estado:
SELECT * FROM sensores
LATEST ON ts PARTITION BY sensor;
Para filtrar por intervalo de fechas, el operador IN con una cadena de tiempo solo lee las particiones afectadas:
SELECT count() FROM sensores WHERE ts IN '2026-09';
Paso 8: Conectarte por el protocolo de PostgreSQL
Cualquier cliente de PostgreSQL puede consultar QuestDB por el puerto 8812, lo que permite usar Grafana con su fuente de datos de PostgreSQL, librerías como psycopg o herramientas de BI. Instala el cliente psql:
sudo apt install postgresql-client
Conéctate con el usuario admin a la base de datos qdb, usando como contraseña el valor de QDB_PG_PASSWORD del archivo .env:
psql -h 127.0.0.1 -p 8812 -U admin -d qdb
qdb=> SELECT sensor, temperatura FROM sensores LATEST ON ts PARTITION BY sensor;
sensor | temperatura
--------+-------------
s1 | 21.4
s2 | 23.1
s9 | 20.5
(3 rows)
Si la conexión se rechaza con invalid username/password, comprueba que el contenedor recibió la variable con docker compose exec questdb env | grep QDB_PG.
Paso 9: Limitar la retención de datos
Las series temporales crecen sin parar, así que conviene decidir cuánto tiempo guardarlas. Con particiones diarias puedes configurar un TTL para que QuestDB borre automáticamente las particiones más antiguas que el periodo indicado:
ALTER TABLE sensores SET TTL 30 DAYS;
También puedes borrar particiones a mano, por ejemplo antes de un cambio de política:
ALTER TABLE sensores DROP PARTITION WHERE ts < dateadd('d', -30, now());
Borrar una partición elimina su directorio completo, así que es instantáneo aunque contenga millones de filas.
Solución de problemas
- El contenedor se reinicia con errores de
mmapotoo many open files: revisa quevm.max_map_countestá aplicado en el host (paso 1) y que el bloqueulimitsestá encompose.yaml. curl: (7) Failed to connect to 127.0.0.1 port 9000: QuestDB todavía está arrancando o se ha detenido. Consultadocker compose logs questdb.- Los datos enviados por ILP no aparecen: la respuesta de
/writeincluye el error si una línea es inválida (tipo de columna distinto al existente, por ejemplo). Envía concurl -iy lee el cuerpo de la respuesta. - La consola web no carga por el túnel: el túnel debe seguir abierto y el puerto local 9000 libre en tu ordenador. Prueba con otro puerto local:
ssh -L 19000:127.0.0.1:9000 ...y abrehttp://localhost:19000.
Conclusión
QuestDB está funcionando en Ubuntu 24.04 con Docker Compose, accesible solo desde localhost, con una tabla particionada que deduplica lecturas, ingesta por ILP y consultas por REST, consola web y psql. Como siguientes pasos, conecta Grafana con la fuente de datos de PostgreSQL o el plugin oficial de QuestDB, envía métricas reales con uno de los clientes ILP y planifica copias de seguridad del volumen questdb_data.
