Por qué una base de series temporales
Una serie temporal es una sucesión de valores fechados: la temperatura de una sonda cada diez segundos, el número de pedidos por minuto. Se escribe mucho, se modifica poco y casi siempre se consulta por intervalo de tiempo.
QuestDB está construida en torno a ese uso. Cada tabla tiene una columna de tiempo designada y se divide en particiones (por día o por hora), lo que mantiene rápidas las consultas sobre un periodo y hace inmediata la eliminación de los datos antiguos. Su SQL sigue siendo SQL, con algunas palabras clave propias del tiempo.
Lo que aporta QuestDB
- SQL de series temporales —
SAMPLE BY,LATEST ONyASOF JOINademás del SQL estándar. - Ingesta continua — InfluxDB Line Protocol sobre HTTP, con clientes oficiales para Python, Go, Java, Node.js, Rust y .NET.
- Consola web — editor SQL, importación CSV arrastrando el archivo, gráficos de los resultados.
- API REST — consultas, importación y exportación CSV por HTTP.
- Protocolo PostgreSQL — tus controladores y herramientas PostgreSQL de siempre.
- Particiones de tiempo — consultas rápidas sobre un periodo y borrado de datos antiguos en un comando.
QuestDB frente a una tabla PostgreSQL clásica
Desplace la tabla
| QuestDB | Solo PostgreSQL | |
|---|---|---|
| Pensada para | Mediciones continuas con marca de tiempo | Datos relacionales generales |
| Agregar por intervalo | SAMPLE BY 1h | date_trunc y GROUP BY |
| Último valor por serie | LATEST ON | Subconsulta o DISTINCT ON |
| Alinear dos flujos en el tiempo | ASOF JOIN | Join lateral que escribir |
| Borrar el historial antiguo | DROP PARTITION | DELETE y después VACUUM |
| Ingesta | Line Protocol, CSV, SQL | SQL y COPY |
Requisitos previos
Un VPS Ubuntu 24.04 con al menos 2 GB de RAM y 20 GB de disco, Docker y el plugin Compose instalados, y un nombre (subdominio) que apunte al VPS para el HTTPS. QuestDB funciona sobre la JVM: prevé 4 GB de RAM si ingieres muchos datos o lanzas consultas pesadas.
Instalar QuestDB con Docker Compose
Preparar el directorio y la contraseña
Crea el directorio de la aplicación y un archivo
.envcon la contraseña del usuarioadmin.mkdir -p /opt/questdb && cd /opt/questdb echo "ADMIN_PASSWORD=$(openssl rand -hex 16)" > .env chmod 600 .envEscribir el docker-compose.yml
Los puertos solo se publican en la interfaz de loopback: solo nginx llega a la consola y a la API.
QDB_HTTP_USERyQDB_HTTP_PASSWORDprotegen toda la API HTTP,QDB_PG_PASSWORDsustituye la contraseña de fábrica del protocolo PostgreSQL. La ingesta por TCP, sin autenticación en la edición de código abierto, queda desactivada, yQDB_TELEMETRY_ENABLEDdesactiva la telemetría anónima.services: questdb: image: questdb/questdb:10.1.0 restart: unless-stopped ports: - "127.0.0.1:9000:9000" - "127.0.0.1:8812:8812" environment: QDB_HTTP_USER: "admin" QDB_HTTP_PASSWORD: "${ADMIN_PASSWORD}" QDB_PG_USER: "admin" QDB_PG_PASSWORD: "${ADMIN_PASSWORD}" QDB_LINE_TCP_ENABLED: "false" QDB_HTTP_SETTINGS_READONLY: "true" QDB_TELEMETRY_ENABLED: "false" volumes: - questdb-data:/var/lib/questdb volumes: questdb-data: {}Arrancar y comprobar
El arranque tarda unos segundos. Sin credenciales la API responde 401; con ellas devuelve la versión instalada.
docker compose up -d curl -s -o /dev/null -w '%{http_code}\n' 'http://127.0.0.1:9000/exec?query=SELECT%201' curl -u admin:VOTRE_MOT_DE_PASSE 'http://127.0.0.1:9000/exec?query=SELECT%20build()'Publicar en HTTPS con nginx
En el vhost de tu dominio (certificado obtenido con certbot), redirige al puerto local. Aumenta
client_max_body_sizepara las importaciones CSV.location / { proxy_pass http://127.0.0.1:9000; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; client_max_body_size 256m; }
Sin QDB_HTTP_USER, la API queda abierta a todos
Sin usuario declarado, QuestDB acepta cualquier petición HTTP: quien conozca la dirección puede leer tus datos y crear, modificar o borrar tablas en SQL. Mantén la autenticación activa, cambia la contraseña de fábrica del protocolo PostgreSQL y no publiques nunca los puertos 9000, 8812 o 9009 directamente en la IP pública: pasa siempre por el proxy inverso HTTPS.
Enviar datos
Lo más sencillo es el InfluxDB Line Protocol sobre HTTP: una línea por medición, con el nombre de la tabla, etiquetas y valores. La tabla y sus columnas se crean con la primera escritura.
curl -u admin:VOTRE_MOT_DE_PASSE -X POST \
--data-binary 'capteurs,sonde=s1 temperature=21.5' \
https://votre-domaine/writeDesde tu código, un cliente oficial agrupa las líneas y gestiona los reintentos. En Python, tras pip install questdb:
from questdb.ingress import Sender, TimestampNanos
conf = "https::addr=votre-domaine:443;username=admin;password=VOTRE_MOT_DE_PASSE;"
with Sender.from_conf(conf) as sender:
sender.row(
"capteurs",
symbols={"sonde": "s1"},
columns={"temperature": 21.5},
at=TimestampNanos.now(),
)Consultar tus series en SQL
En la consola web o mediante la API REST (/exec?query=…), el SQL estándar funciona y las extensiones de tiempo evitan consultas complicadas.
-- Moyenne horaire par sonde
SELECT timestamp, sonde, avg(temperature)
FROM capteurs
SAMPLE BY 1h;
-- Dernière valeur connue de chaque sonde
SELECT * FROM capteurs LATEST ON timestamp PARTITION BY sonde;Un programa que se ejecute en el propio VPS también puede conectarse por el protocolo PostgreSQL en 127.0.0.1:8812, base qdb, usuario admin y la misma contraseña.
Modelar bien una tabla de series temporales
La creación automática con la primera escritura basta para empezar. Para una tabla que va a durar, declárala tú mismo: eliges los tipos, la columna de tiempo y la partición.
CREATE TABLE capteurs (
timestamp TIMESTAMP,
sonde SYMBOL,
temperature DOUBLE
) TIMESTAMP(timestamp) PARTITION BY DAY WAL
DEDUP UPSERT KEYS(timestamp, sonde);Tres decisiones importan. La columna de tiempo designada (TIMESTAMP(...)) es la que QuestDB usa para ordenar y particionar: todas las extensiones de tiempo se apoyan en ella. El tipo SYMBOL conviene a las etiquetas repetidas (nombre de sonda, código de dispositivo, símbolo bursátil): el valor se guarda una vez y se referencia, lo que ahorra disco y acelera los filtros. La partición (PARTITION BY DAY u HOUR) se elige según el volumen: una partición debe tener un tamaño razonable, y es la unidad que borrarás más adelante.
La cláusula DEDUP UPSERT KEYS hace la ingesta idempotente: si un cliente reenvía la misma medición tras un corte de red, la fila se sustituye en lugar de duplicarse.
Importar y exportar archivos CSV
La consola acepta archivos CSV arrastrándolos. Desde la línea de comandos, /imp importa un archivo en una tabla y /exp exporta el resultado de una consulta.
curl -u admin:VOTRE_MOT_DE_PASSE -F [email protected] 'https://votre-domaine/imp?name=mesures'
curl -u admin:VOTRE_MOT_DE_PASSE -G --data-urlencode 'query=SELECT * FROM mesures' https://votre-domaine/exp > export.csvRetención, disco y copia de seguridad
Nada caduca por sí solo. Elimina las particiones antiguas en SQL y programa el comando si hace falta. Para una copia coherente, congela el estado de la base mientras copias el volumen questdb-data.
ALTER TABLE capteurs DROP PARTITION
WHERE timestamp < dateadd('d', -90, now());
CHECKPOINT CREATE;
-- copiez le volume questdb-data, puis :
CHECKPOINT RELEASE;Actualizar QuestDB
Lee las notas de la versión antes de pasar a una versión mayor, sustituye la etiqueta de la imagen y reinicia: los datos del volumen se conservan.
cd /opt/questdb
# changez le tag de l'image dans docker-compose.yml, puis :
docker compose pull && docker compose up -dErrores frecuentes y solución de problemas
401 en cada petición. El usuario o la contraseña no coinciden con QDB_HTTP_USER y QDB_HTTP_PASSWORD. Revisa el archivo .env y reinicia el contenedor tras cualquier cambio: las variables solo se leen al arrancar.
413 al importar un CSV. El archivo supera el límite de nginx. Aumenta client_max_body_size en el vhost, recarga nginx o divide el archivo.
El contenedor se reinicia en bucle. Lee docker compose logs questdb. La causa más habitual es la memoria: QuestDB funciona sobre la JVM y un VPS de 1 GB no basta. Pasa a 2 GB como mínimo.
El disco se llena. Nada caduca por defecto: programa el borrado de las particiones antiguas y vigila el tamaño del volumen questdb-data con docker system df -v.
Una aplicación del VPS no consigue conectarse por PostgreSQL. Usa 127.0.0.1:8812, la base qdb, el usuario admin y la contraseña del .env: la contraseña de fábrica quest se rechaza.
Instalación en un clic desde el Marketplace de ServOrbit
En un VPS de ServOrbit, QuestDB se instala desde el Marketplace: el script despliega el contenedor oficial, genera la contraseña del usuario admin, activa la autenticación en la API HTTP y el protocolo PostgreSQL, desactiva la telemetría y configura nginx en HTTPS, en un subdominio de tu dominio o en el subdominio gratuito del VPS. La contraseña aparece en tu área de cliente.