8  Base de datos analítica

Si bien es cierto que heredamos de los sistemas operacionales la estructura base, las necesidades de un sistema analítico son muy distintas. No se prima tanto la velocidad si no la capacidad de manejar y calcular usando grandes volúmenes de datos.

8.1 Ordenación de los datos

Las bases de datos operacionales para facilitar el acceso concurrente a los datos, ordenan estos en filas a nivel de almacenamiento y acceso.

id nombre apellido edad
1 Iraitz Montalbán 18
2 Javier Garcia 19

En los ficheros convencionales ordenados por filas la información la representaríamos así.

1;Iraitz;Montalbán;18;2;Javier;Garcia;19

Mientras que las consultas analíticas precisan acceder a la información en columnas. Por ejemplo, si quisiéramos hacer un promedio de la edad de nuestros alumnos, solo necesitamos esa cuarta columna. Es decir, sabiendo qué número de filas tenemos solo debemos posicionarnos donde empieza la cuarta columna y leer los siguientes 2 datos, obviando todo lo anterior.

1;2;Iraitz;Javier;Montalbán;Garcia;18;19

De forma que la ordenación columnar nos permite no tener que cargar la información innecesaria en la operación AVG(edad).

La idea no es nueva: el trabajo sobre C-Store (Stonebraker et al. 2005) ya defendía en 2005 que un gestor pensado para lectura debía almacenar por columnas, y de ahí desciende buena parte de los sistemas actuales.

En el mundo de los sistemas de código abierto, Apache Parquet para el almacenamiento en disco y Apache Arrow para el mismo concepto en memoria se han convertido en un estándar.

8.2 Restricciones

Para el caso general no existen cuestiones como las restricciones de clave primaria y clave foránea, ya que el sistema origen es quien impone estas restricciones y el sistema analítico recoge la información tal y como este la provea.

Del mismo modo, de cara a hacer las operaciones más rápidas, los índices son clave en los sistemas operacionales. Las bases de datos analíticas no presentan esta necesidad ya que solemos consumir la información en bloques y se asumen las latencias de estos procesos por no impacta a procesos de negocio. Tener una buena ordenación nos ayuda a no leer información que no vayamos a utilizar y aligerar la carga del proceso, de ahí que si sea común tener políticas de particionado.

8.2.1 Particionado

La forma más sencilla de ver el particionado es con un ejemplo que emplee la fecha como parámetro de filtrado (cláusula WHERE). Si solo precisamos la información de 2025 en adelante, podemos buscar por cada registro cuales cumplen ese criterio. Podemos construir un índice que nos permita emplear solo esa información con el almacenamiento adicional y tiempos de inserción mayores como impacto. Sin embargo hay un modo quizás más sencillo entendiendo que la información ya viene de otros sistemas y actuamos sobre ella al ser ingestada. Podemos crear distintas estructuras (tablas, ficheros,…) con el sufijo _YYYY haciendo referencia al año, de forma que solo empleemos esas tablas en nuestro proceso. El particionado se refiere a componer estas estructuras que aunque abstraigan al usuario de estar consultando más de una tabla, tienen un efecto importante en el filtrado de información cuando este es indicado en algún cálculo.

8.3 Embebido o servidor

Hay una distinción que no suele aparecer en las comparativas y que condiciona el despliegue entero: si el sistema es un servidor al que uno se conecta por red, o una biblioteca que se ejecuta dentro del propio proceso.

Casi todos los de la lista que viene a continuación son servidores. DuckDB no: es una biblioteca, igual que SQLite. No hay demonio que arrancar, ni puerto, ni usuarios que administrar; se importa y se consulta. Esa es la razón de que aparezca en tantos sitios donde antes hacía falta infraestructura.

La contrapartida está en el modelo de concurrencia. Sobre un mismo fichero DuckDB admite varios lectores o un único escritor, nunca las dos cosas a la vez, porque toma un bloqueo exclusivo del fichero. No es un defecto que vayan a corregir, es la consecuencia de no tener un servidor que arbitre: sin proceso central, el único árbitro posible es el sistema de ficheros.

En la práctica eso se nota en cuanto dos herramientas quieren el mismo fichero. Un panel de BI que lo mantiene abierto impide que el proceso de transformación escriba, y el error que aparece (Conflicting lock is held) despista bastante la primera vez. Hay un caso concreto documentado en el apéndice del lago de datos.

8.3.1 Ponerle un servidor delante

Cuando varias herramientas necesitan leer a la vez desde máquinas distintas, la salida habitual es envolver el motor en un servicio mínimo que centralice el acceso:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import duckdb

app = FastAPI(title="DuckDB HTTP")
RUTA = "/data/warehouse.duckdb"


class Consulta(BaseModel):
    sql: str


@app.post("/query")
def consultar(peticion: Consulta):
    # Solo lectura: así varios procesos pueden atacar el fichero a la vez
    # sin pelearse por el bloqueo de escritura.
    con = duckdb.connect(RUTA, read_only=True)
    try:
        cursor = con.execute(peticion.sql)
        columnas = [d[0] for d in cursor.description]
        return {"columnas": columnas, "filas": cursor.fetchall()}
    except duckdb.Error as e:
        raise HTTPException(status_code=400, detail=str(e))
    finally:
        con.close()

Merece la pena ser honestos sobre lo que es esto: un apaño útil, no una arquitectura. Resuelve el acceso remoto de lectura y poco más. En cuanto haga falta que varios procesos escriban de verdad a la vez, con control de acceso y transacciones, lo que se está pidiendo es un sistema servidor, y conviene coger uno en lugar de seguir añadiendo capas al que no lo es. La alternativa intermedia, si se quiere conservar DuckDB como motor, es sacar los metadatos a un catálogo transaccional, que es justo lo que hace DuckLake.

Y un aviso que ese ejemplo hace evidente: aceptar SQL por HTTP es abrir una puerta. Sin autenticación, sin límites de tiempo y sin restringir lo que se puede ejecutar, eso no sale de una red interna de confianza.

8.4 Sistemas comunes

Todos estos principios deben ser implementados por los proveedores y cada uno tendrá su filosofía de cómo deben realizarse estas acciones pero os dejamos un listado no exhaustivo de tecnologías destinadas a sistemas analíticos que merece la pena explorar.

8.4.1 Nube

Sistemas propietarios en las principales nubes.

  • Azure Synpase SQL
  • Redshift
  • Google BigQuery

8.4.2 Multi-nube

Sistemas que empleando recursos de infraestructura en el proveedor nube de elección, implementan su sistema gestor propietarios. Son sin duda las soluciones corporativas más demandadas en la actualidad.

  • Snowflake
  • Databricks
  • Motherduck

8.4.3 On-premise

Estos sistemas permiten el despliegue en recursos nube o infraestructura propietaria, siendo en muchos casos una opción más asequible si se dispone de la madurez IT necesaria.

8.4.4 Motores de consulta

Si disponemos de infraestructura escalable de almacenamiento, podemos emplear sistemas que únicamente nos den la capa de gestión y computación necesaria. Son particularmente útiles cuando empleamos formatos de almacenamiento columnar y abierto (Apache Parquet, CSV, JSON).

  • Trino
  • Pinot
  • Apache Spark
  • Starburst
  • Daft

Los sistemas de almacenamiento de forma paralela a los motores de consulta han evolucionado gracias a las tendencias sobre lagos de datos de la década de los 2010.