ADR-0001 — Almacenamiento de series temporales: TimescaleDB¶
Contexto¶
La plataforma necesita un motor de almacenamiento para las lecturas de sensores (crudas y limpias) con un perfil de carga muy marcado: muchas escrituras sostenidas, datos que son hechos que solo se añaden (no se actualizan lecturas históricas) e indexados por sensor y por instante de tiempo.
Las consultas dominantes son rangos temporales, agregaciones por intervalo (minuto, hora, día) por sensor o por tipo de sensor, y cruces con los metadatos de cada sensor. A esto se suman requisitos operacionales exigentes: retención agresiva (descartar lo crudo pasados unos días y conservar los agregados durante años), compresión de los datos antiguos y agregados continuos materializados que el dashboard pueda consultar directamente sin recalcular.
La elección condiciona toda la capa de datos: los trabajos batch que la leen y escriben, las herramientas de visualización, la gestión de migraciones y la interfaz que consume el entrenamiento de modelos.
Decisión¶
Se adopta TimescaleDB sobre PostgreSQL como motor único de series temporales. Las tablas de lecturas se declaran como hipertablas particionadas por tiempo de forma transparente; se definen agregados continuos materializados para los distintos intervalos, políticas de retención declarativas y compresión de los fragmentos más antiguos. Todo funciona en un único nodo, suficiente con holgura para el régimen de trabajo previsto.
No se introduce un segundo motor analítico columnar.
La idea de fondo
TimescaleDB es PostgreSQL con una extensión. Se conserva el mismo lenguaje SQL, el mismo controlador y el mismo modelo relacional que el resto del sistema, pero se ganan las capacidades de series temporales (particionado automático por tiempo, agregados continuos, retención y compresión) declaradas en unas pocas líneas.
Consecuencias¶
Positivas:
- Un solo motor de datos cubre el almacenamiento de lecturas y sirve además de respaldo para los metadatos de otras piezas, lo que reduce el número de servicios y de puntos de fallo.
- Las capacidades de series temporales (particionado, agregados continuos, retención y compresión) se resuelven de forma declarativa, sin scripts propios de mantenimiento.
- Los cruces con metadatos de sensores son de primera clase: no hay desajuste entre el dominio operacional y el de series temporales.
- El camino de evolución es aditivo: si algún día se supera el techo del nodo único, se puede pasar a hipertablas distribuidas o incorporar un motor columnar como capa de solo lectura, sin reescribir nada.
Negativas o costes aceptados:
- El nodo único tiene un techo real de escritura. Más allá de cierta escala hay que recurrir a hardware mayor o a particionado distribuido.
- La compresión, aunque muy buena, no alcanza la densidad de un motor columnar puro.
- Las consultas analíticas ad-hoc muy pesadas sobre años de datos crudos son subóptimas frente a un columnar; se mitiga por completo para los agregados conocidos gracias a los agregados continuos.
Alternativas descartadas¶
- ClickHouse (columnar puro): brilla a escalas de cientos de GB al día y ofrece compresión muy superior, pero exige una pila separada (cliente, dialecto SQL propio, coordinación, tooling), tiene cruces débiles y no es transaccional. Su coste de complejidad solo se justifica en escenarios donde TimescaleDB ya no llega, y el proyecto está deliberadamente por debajo de ese punto.
- InfluxDB: pensado para series temporales, pero con fragmentación entre versiones y lenguajes de consulta, sin cruces relacionales reales y con una pila separada del ecosistema Postgres.
- PostgreSQL puro sin extensión: obligaría a reimplementar a mano el particionado por tiempo, los agregados y la retención; viable solo a escala de juguete.
- Servicios gestionados en la nube: introducen dependencia de proveedor y coste recurrente, y no demuestran el diseño de sistemas que es objetivo del proyecto.