QuestDB: Base de Datos de Series Temporales de Alto Rendimiento
QuestDB es una base de datos de series temporales diseñada para ingesta de datos a velocidades extremas, capaz de procesar millones de filas por segundo mediante su motor de almacenamiento columnar optimizado para hardware moderno. Ofrece una interfaz SQL compatible con PostgreSQL y soporte para el protocolo de línea de InfluxDB, facilitando la migración desde otros sistemas y la integración con herramientas como Grafana para casos de uso IoT y monitorización en Linux.
Requisitos Previos
- Ubuntu 20.04/22.04 o CentOS/Rocky Linux 8+
- Java 11+ (JDK o JRE)
- Al menos 2 GB de RAM (4 GB recomendado)
- SSD para almacenamiento de datos (muy recomendado)
- Acceso root o usuario con privilegios sudo
Instalación de QuestDB
Instalación con Docker (recomendada)
# Ejecutar QuestDB con Docker
docker run -d \
--name questdb \
-p 9000:9000 \ # Interfaz web
-p 8812:8812 \ # PostgreSQL wire protocol
-p 9009:9009 \ # InfluxDB line protocol
-v questdb_data:/var/lib/questdb \
--restart unless-stopped \
questdb/questdb:latest
# Verificar que está corriendo
docker logs questdb --tail=20
curl http://localhost:9000/ping
Instalación del binario
# Instalar Java 11+ si no está disponible
sudo apt-get install -y openjdk-11-jre
# Descargar QuestDB
QUESTDB_VERSION="7.3.9"
curl -L "https://github.com/questdb/questdb/releases/download/${QUESTDB_VERSION}/questdb-${QUESTDB_VERSION}-no-jre-bin.tar.gz" \
-o questdb.tar.gz
tar -xzf questdb.tar.gz
sudo mv questdb-${QUESTDB_VERSION}-no-jre-bin /opt/questdb
# Crear usuario del sistema
sudo useradd -r -s /bin/false questdb
sudo chown -R questdb:questdb /opt/questdb
# Crear servicio systemd
sudo cat > /etc/systemd/system/questdb.service << 'EOF'
[Unit]
Description=QuestDB Time-Series Database
After=network.target
[Service]
Type=simple
User=questdb
Group=questdb
Environment="JAVA_HOME=/usr"
ExecStart=/opt/questdb/bin/questdb.sh start -d /var/lib/questdb -f
ExecStop=/opt/questdb/bin/questdb.sh stop
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
sudo mkdir -p /var/lib/questdb
sudo chown questdb:questdb /var/lib/questdb
sudo systemctl daemon-reload
sudo systemctl enable questdb
sudo systemctl start questdb
Configuración Básica
Configurar QuestDB para producción:
# Archivo de configuración principal
sudo cat > /var/lib/questdb/conf/server.conf << 'EOF'
# ================================================================
# Configuración de QuestDB
# ================================================================
# ---- INTERFAZ WEB ----
http.bind.to=0.0.0.0:9000
http.min.enabled=false
# ---- PROTOCOLO POSTGRESQL ----
pg.net.bind.to=0.0.0.0:8812
pg.user=admin
pg.password=contraseña_segura
pg.max.connections=100
# ---- PROTOCOLO LINE (InfluxDB compatible) ----
line.tcp.net.bind.to=0.0.0.0:9009
line.tcp.net.connection.limit=256
# ---- RENDIMIENTO ----
# Tamaño del pool de escritura
line.tcp.writer.worker.count=4
# Memoria de trabajo para consultas
cairo.sql.copy.buffer.size=16m
# Tamaño del caché de páginas
cairo.sql.sort.key.page.size=16m
# ---- TELEMETRÍA (opcional, para estadísticas de uso) ----
telemetry.enabled=false
EOF
sudo systemctl restart questdb
Interfaz SQL
QuestDB ofrece SQL con extensiones para series temporales:
# Acceder a la consola web: http://localhost:9000
# O mediante psql (protocolo PostgreSQL)
psql -h localhost -p 8812 -U admin -d qdb
# También se puede usar la API REST HTTP:
curl -G http://localhost:9000/exec \
--data-urlencode "query=SELECT 1"
Crear tablas optimizadas para series temporales:
-- Crear tabla de métricas de servidores
CREATE TABLE metricas_servidores (
timestamp TIMESTAMP,
host SYMBOL,
region SYMBOL,
cpu_uso DOUBLE,
memoria_mb DOUBLE,
disco_gb DOUBLE,
red_rx_mbps DOUBLE,
red_tx_mbps DOUBLE
) timestamp(timestamp) PARTITION BY DAY WAL;
-- SYMBOL: tipo optimizado para valores repetidos (equivalente a etiquetas de InfluxDB)
-- timestamp(timestamp): especifica la columna de tiempo designada
-- PARTITION BY DAY: particionar por día para mejor rendimiento
-- WAL: Write-Ahead Log para mayor durabilidad
-- Insertar datos de prueba
INSERT INTO metricas_servidores VALUES (
now(), 'web-01', 'eu-west', 45.2, 2048.0, 150.5, 125.3, 45.8
);
-- Consultar los últimos datos
SELECT * FROM metricas_servidores
WHERE timestamp > dateadd('h', -1, now())
ORDER BY timestamp DESC
LIMIT 10;
Consultas SQL con funciones de series temporales:
-- Muestreo temporal: media de CPU por intervalos de 5 minutos
SELECT
timestamp,
host,
avg(cpu_uso) AS cpu_media,
max(cpu_uso) AS cpu_pico,
min(cpu_uso) AS cpu_minimo
FROM metricas_servidores
WHERE timestamp > dateadd('h', -24, now())
SAMPLE BY 5m; -- Función exclusiva de QuestDB
-- LATEST ON: obtener el último valor de cada host
SELECT * FROM metricas_servidores
LATEST ON timestamp PARTITION BY host;
-- Detectar anomalías (CPU por encima del percentil 99)
SELECT
timestamp,
host,
cpu_uso,
avg(cpu_uso) OVER (
PARTITION BY host
ORDER BY timestamp
ROWS BETWEEN 100 PRECEDING AND CURRENT ROW
) AS media_movil
FROM metricas_servidores
WHERE timestamp > dateadd('d', -7, now())
AND cpu_uso > 90;
-- Correlación entre dos métricas
SELECT
m1.timestamp,
m1.host,
m1.cpu_uso,
m2.memoria_mb
FROM metricas_servidores m1
ASOF JOIN metricas_servidores m2 ON m1.host = m2.host;
-- ASOF JOIN: une por el tiempo más cercano (exclusivo de QuestDB)
Ingesta de Alta Velocidad
Enviar datos a alta velocidad mediante el protocolo de línea:
# Protocolo de línea compatible con InfluxDB (TCP port 9009)
# Formato: tabla,etiqueta=valor campo=valor timestamp_nanosegundos
# Enviar punto único via netcat
echo "temperatura,sensor=sala1,edificio=A celsius=22.5 $(date +%s)000000000" \
| nc -q1 localhost 9009
# Script de ingesta continua (simulación de sensores IoT)
cat > /usr/local/bin/simulate-iot.sh << 'EOF'
#!/bin/bash
# Simular datos de sensores IoT enviados a QuestDB
QUESTDB_HOST="localhost"
QUESTDB_PORT=9009
while true; do
TIMESTAMP=$(date +%s)000000000
# Simular temperatura (entre 18 y 30 grados)
TEMP=$(echo "scale=1; 18 + $RANDOM % 120 / 10" | bc)
# Simular humedad (entre 40 y 80%)
HUMEDAD=$(echo "scale=1; 40 + $RANDOM % 400 / 10" | bc)
# Simular consumo eléctrico (entre 100 y 500W)
WATTS=$(( 100 + RANDOM % 400 ))
# Enviar datos a QuestDB
printf "sensor_ambiental,ubicacion=oficina-1,piso=2 temperatura=${TEMP},humedad=${HUMEDAD},watts=${WATTS}i ${TIMESTAMP}\n" \
| nc -q0 "$QUESTDB_HOST" "$QUESTDB_PORT"
sleep 1
done
EOF
chmod +x /usr/local/bin/simulate-iot.sh
# Ingesta masiva con la API HTTP (para carga de datos históricos)
curl -G http://localhost:9000/exec \
--data-urlencode "query=INSERT INTO metricas_servidores VALUES ('2024-01-01T00:00:00', 'web-01', 'eu', 45.2, 2048.0, 150.0, 100.0, 50.0)"
Ingesta desde Python:
# Instalar cliente Python para QuestDB
pip install questdb
# Script de ingesta en Python
cat > /tmp/ingesta_questdb.py << 'EOF'
from questdb.ingress import Sender, TimestampNanos
from datetime import datetime
import time
import random
# Conectar al endpoint del protocolo de línea
with Sender('localhost', 9009) as sender:
for i in range(1000):
# Enviar datos de temperatura de múltiples sensores
for sala in ['sala1', 'sala2', 'sala3']:
sender.row(
'temperatura_edificio',
symbols={'sala': sala, 'edificio': 'principal'},
columns={
'celsius': round(20.0 + random.gauss(0, 2), 2),
'humedad': round(55.0 + random.gauss(0, 5), 2),
'co2_ppm': random.randint(400, 1000)
},
at=TimestampNanos.now()
)
# Flush cada 100 filas para eficiencia
if i % 100 == 0:
sender.flush()
time.sleep(0.01) # 100 puntos por segundo por sala
# Flush final
sender.flush()
print("Ingesta completada: 3000 puntos en 3 sensores")
EOF
python3 /tmp/ingesta_questdb.py
Integración con Grafana
# Instalar Grafana si no está disponible
sudo apt-get install -y grafana
# En Grafana, añadir el datasource de QuestDB:
# 1. Configuration > Data Sources > Add data source
# 2. Seleccionar "PostgreSQL" (QuestDB usa el protocolo PostgreSQL)
# 3. Configurar:
# - Host: localhost:8812
# - Database: qdb
# - User: admin
# - Password: contraseña_segura
# - TLS/SSL Mode: disable
# - PostgreSQL Version: 15.0
# Query de ejemplo para un panel de Grafana:
cat << 'GRAFANA_QUERY'
-- Query para panel de temperatura en tiempo real
SELECT
$__time(timestamp),
sala AS metric,
avg(celsius) AS "Temperatura (°C)"
FROM temperatura_edificio
WHERE $__timeFilter(timestamp)
SAMPLE BY $__interval
ORDER BY timestamp;
GRAFANA_QUERY
Particionamiento de Datos
Optimizar el rendimiento mediante particionamiento:
-- Crear tabla con particionamiento semanal (para datos muy voluminosos)
CREATE TABLE logs_acceso (
timestamp TIMESTAMP,
ip SYMBOL,
metodo SYMBOL,
ruta STRING,
codigo INT,
tiempo_ms INT,
bytes_sent LONG
) timestamp(timestamp) PARTITION BY WEEK WAL;
-- Opciones de particionamiento:
-- PARTITION BY NONE: sin particionamiento (para tablas pequeñas)
-- PARTITION BY HOUR: datos con alta frecuencia (>100k filas/hora)
-- PARTITION BY DAY: uso general (recomendado)
-- PARTITION BY WEEK: datos históricos de larga duración
-- PARTITION BY MONTH: datos de retención muy larga
-- PARTITION BY YEAR: archivado
-- Ver particiones de una tabla
SELECT * FROM table_partitions('metricas_servidores');
-- Eliminar particiones antiguas (limpieza de datos históricos)
ALTER TABLE metricas_servidores
DROP PARTITION LIST '2023-01-01', '2023-01-02';
-- Modificar retención a nivel de partición
ALTER TABLE metricas_servidores SET MAX AGE 30d KEEP 5;
Deduplicación
Gestionar datos duplicados en ingesta de alta velocidad:
-- Crear tabla con soporte de deduplicación (DEDUP)
CREATE TABLE sensores_criticos (
timestamp TIMESTAMP,
sensor_id SYMBOL,
valor DOUBLE,
calidad INT
) timestamp(timestamp) PARTITION BY DAY WAL
DEDUP UPSERT KEYS(timestamp, sensor_id);
-- Con DEDUP UPSERT KEYS: si llega un punto con el mismo timestamp y sensor_id,
-- se actualiza en lugar de crear un duplicado
-- Verificar el estado de deduplicación
SELECT dedup_enabled, timestamp_column
FROM tables()
WHERE name = 'sensores_criticos';
Casos de Uso IoT
Arquitectura completa para IoT con QuestDB:
# Stack IoT completo: Mosquitto MQTT -> Telegraf -> QuestDB -> Grafana
# Configuración de Telegraf para leer de MQTT y escribir en QuestDB
cat > /etc/telegraf/telegraf-iot.conf << 'EOF'
# Entrada: Broker MQTT para datos de sensores
[[inputs.mqtt_consumer]]
servers = ["tcp://localhost:1883"]
topics = ["sensores/+/datos"]
data_format = "json"
json_time_key = "timestamp"
json_time_format = "unix"
tag_keys = ["sensor_id", "ubicacion"]
# Salida: QuestDB via protocolo de línea (InfluxDB compatible)
[[outputs.influxdb]]
urls = ["http://localhost:9000"]
database = "sensores"
# QuestDB acepta el protocolo de InfluxDB v1 sin autenticación por defecto
EOF
sudo systemctl restart telegraf
# Consulta de análisis de datos IoT
curl -G http://localhost:9000/exec \
--data-urlencode "query=
SELECT
sensor_id,
count() AS lecturas_totales,
avg(valor) AS media,
min(valor) AS minimo,
max(valor) AS maximo,
stddev(valor) AS desviacion_tipica
FROM sensores_criticos
WHERE timestamp > dateadd('d', -7, now())
SAMPLE BY 1d
ORDER BY timestamp DESC"
Solución de Problemas
QuestDB no arranca (error de Java):
# Verificar versión de Java
java -version # Debe ser 11+
# Ver logs de inicio
sudo journalctl -u questdb -n 50
cat /var/lib/questdb/log/questdb.log | tail -50
Ingesta lenta o errores de timeout:
# Aumentar el número de workers de escritura
# En server.conf:
# line.tcp.writer.worker.count=8
# Verificar el espacio en disco
df -h /var/lib/questdb
# Ver métricas de ingesta
curl -G http://localhost:9000/exec \
--data-urlencode "query=SELECT * FROM telemetry ORDER BY created DESC LIMIT 10"
Consultas lentas:
# Usar EXPLAIN para analizar el plan de consulta
curl -G http://localhost:9000/exec \
--data-urlencode "query=EXPLAIN SELECT * FROM metricas WHERE timestamp > now() - 3600000000"
# Asegurarse de usar siempre filtros de tiempo en la columna designada
# WHERE timestamp > dateadd('h', -1, now()) -- CORRECTO, usa índice
# WHERE cpu > 80 -- PUEDE SER LENTO sin filtro de tiempo
Conclusión
QuestDB destaca por ofrecer el mayor rendimiento de ingesta de su categoría gracias a su motor columnar optimizado para CPU modernas, con la ventaja de usar SQL estándar en lugar de lenguajes de consulta propietarios. Su compatibilidad con el protocolo de línea de InfluxDB facilita la migración desde otros sistemas de series temporales, mientras que el soporte de PostgreSQL wire protocol permite usar cualquier cliente SQL existente. Es la opción ideal para casos de uso IoT y analítica en tiempo real donde la velocidad de ingesta y la capacidad de consulta son críticas.


